feat: 新增技能扩展N15一章相关示例源码
This commit is contained in:
9
code/newsletter/N15/01_basic_app.py
Normal file
9
code/newsletter/N15/01_basic_app.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from textual.app import App
|
||||
|
||||
|
||||
class SimpleApp(App):
|
||||
pass
|
||||
|
||||
if __name__== '__main__':
|
||||
app = SimpleApp()
|
||||
app.run()
|
||||
70
code/newsletter/N15/02_widgets.py
Normal file
70
code/newsletter/N15/02_widgets.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import re
|
||||
from textwrap import dedent
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.reactive import reactive
|
||||
from textual.widgets import Footer, Header, Static
|
||||
|
||||
lorem = """\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Sed eget suscipit ligula.
|
||||
Curabitur id justo imperdiet, pretium orci sed, tempus leo.
|
||||
Integer vulputate vitae diam efficitur tempus.
|
||||
Donec vel est ac purus feugiat ultricies sit amet non sapien.
|
||||
Vivamus faucibus viverra bibendum.
|
||||
Integer congue sed mi et imperdiet. Praesent quis dapibus neque.
|
||||
Cras arcu purus, laoreet eu nibh scelerisque, bibendum tincidunt mauris.
|
||||
Cras congue erat leo, non efficitur tellus auctor a.
|
||||
"""
|
||||
|
||||
|
||||
class LoremWidget(Static):
|
||||
|
||||
is_highlight = reactive(False, layout=True)
|
||||
_RE_LOREM = re.compile("(?P<target>Lorem ipsum)", re.I)
|
||||
|
||||
_cache_lorem = None
|
||||
_raw_lorem = dedent(lorem)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.update(self._raw_lorem)
|
||||
|
||||
async def watch_is_highlight(self, old_value, new_value) -> None:
|
||||
if self.is_highlight:
|
||||
content = self.marked_lorem()
|
||||
self.update(content)
|
||||
else:
|
||||
self.update(self._raw_lorem)
|
||||
|
||||
def marked_lorem(self):
|
||||
if self._cache_lorem:
|
||||
return self._cache_lorem
|
||||
|
||||
content = self._RE_LOREM.sub(r"[u red b]\1[/]", lorem)
|
||||
self._cache_lorem = content
|
||||
return content
|
||||
|
||||
|
||||
class SimpleApp(App):
|
||||
BINDINGS = [
|
||||
("h", "highlight", "Highlight lorem"),
|
||||
("q", "quit", "Quit"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Container(LoremWidget(id="lorem"))
|
||||
yield Footer()
|
||||
|
||||
def action_highlight(self) -> None:
|
||||
widget = self.query_one("#lorem")
|
||||
widget.is_highlight = not widget.is_highlight
|
||||
|
||||
def action_quit(self) -> None:
|
||||
self.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = SimpleApp()
|
||||
app.run()
|
||||
74
code/newsletter/N15/03_actions.py
Normal file
74
code/newsletter/N15/03_actions.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import re
|
||||
from textwrap import dedent
|
||||
|
||||
from textual import events
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.reactive import reactive
|
||||
from textual.widgets import Footer, Header, Static
|
||||
|
||||
lorem = """\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Sed eget suscipit ligula.
|
||||
Curabitur id justo imperdiet, pretium orci sed, tempus leo.
|
||||
Integer vulputate vitae diam efficitur tempus.
|
||||
Donec vel est ac purus feugiat ultricies sit amet non sapien.
|
||||
Vivamus faucibus viverra bibendum.
|
||||
Integer congue sed mi et imperdiet. Praesent quis dapibus neque.
|
||||
Cras arcu purus, laoreet eu nibh scelerisque, bibendum tincidunt mauris.
|
||||
Cras congue erat leo, non efficitur tellus auctor a.
|
||||
"""
|
||||
|
||||
|
||||
class LoremWidget(Static):
|
||||
|
||||
is_highlight = reactive(False, layout=True)
|
||||
_RE_LOREM = re.compile("(?P<target>Lorem ipsum)", re.I)
|
||||
|
||||
_cache_lorem = None
|
||||
_raw_lorem = dedent(lorem)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.update(self._raw_lorem)
|
||||
|
||||
async def watch_is_highlight(self, old_value, new_value) -> None:
|
||||
if self.is_highlight:
|
||||
content = self.marked_lorem()
|
||||
self.update(content)
|
||||
else:
|
||||
self.update(self._raw_lorem)
|
||||
|
||||
def marked_lorem(self):
|
||||
if self._cache_lorem:
|
||||
return self._cache_lorem
|
||||
|
||||
content = self._RE_LOREM.sub(r"[u red b]\1[/]", lorem)
|
||||
self._cache_lorem = content
|
||||
return content
|
||||
|
||||
|
||||
class SimpleApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Container(LoremWidget(id="lorem"))
|
||||
yield Footer()
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
if event.key == "h":
|
||||
self.action_highlight()
|
||||
elif event.key == "q":
|
||||
self.action_quit()
|
||||
elif event.key == "d":
|
||||
self.action_toggle_dark()
|
||||
|
||||
def action_highlight(self) -> None:
|
||||
widget = self.query_one("#lorem")
|
||||
widget.is_highlight = not widget.is_highlight
|
||||
|
||||
def action_quit(self) -> None:
|
||||
self.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = SimpleApp()
|
||||
app.run()
|
||||
62
code/newsletter/N15/04_events.py
Normal file
62
code/newsletter/N15/04_events.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from textual import events
|
||||
from textual.app import App
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
class ClickableColorBlock(Static):
|
||||
background_toggled = False
|
||||
raw_background = ""
|
||||
|
||||
def on_mount(self):
|
||||
self.styles.border = ("heavy", "white")
|
||||
self.raw_background = self.styles.background
|
||||
|
||||
def on_click(self, event: events.Click):
|
||||
event.prevent_default()
|
||||
|
||||
self.background_toggled = not self.background_toggled
|
||||
self.styles.background = (
|
||||
str(self.render()) if self.background_toggled else self.raw_background
|
||||
)
|
||||
|
||||
self.log("block background changed!")
|
||||
|
||||
|
||||
class DisplayApp(App):
|
||||
CSS_PATH = "css/events.css"
|
||||
background_toggled = False
|
||||
|
||||
def compose(self):
|
||||
yield Container(
|
||||
ClickableColorBlock("red", classes="block"),
|
||||
ClickableColorBlock("blue", classes="block"),
|
||||
ClickableColorBlock("green", classes="block"),
|
||||
ClickableColorBlock("cyan", classes="block"),
|
||||
id="display-container",
|
||||
)
|
||||
|
||||
def on_click(self):
|
||||
self.background_toggled = not self.background_toggled
|
||||
target = self.query_one("#display-container")
|
||||
blocks = target.query(".block")
|
||||
background = None
|
||||
border = None
|
||||
|
||||
if self.background_toggled:
|
||||
background = "white"
|
||||
border = ("heavy", "black")
|
||||
else:
|
||||
background = "grey"
|
||||
border = ("heavy", "white")
|
||||
|
||||
target.styles.background = background
|
||||
for block in blocks:
|
||||
block.styles.border = border
|
||||
|
||||
self.log("container background changed!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = DisplayApp()
|
||||
app.run()
|
||||
55
code/newsletter/N15/05_reactivity.py
Normal file
55
code/newsletter/N15/05_reactivity.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Vertical
|
||||
from textual.reactive import reactive
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Input
|
||||
|
||||
|
||||
class WelcomeWidget(Widget):
|
||||
who = reactive("World!")
|
||||
total = reactive(15)
|
||||
|
||||
def on_mount(self):
|
||||
default_styles = """
|
||||
margin: 1;
|
||||
padding: 1;
|
||||
"""
|
||||
self.set_styles(default_styles)
|
||||
|
||||
def render(self):
|
||||
return f":partying_face: Hello, [b red]{self.who}[/]! ({self.total}/15)"
|
||||
|
||||
def watch_who(self, old_value, new_value):
|
||||
self.log(
|
||||
f"who attribute changed: old value: {old_value}, "
|
||||
f"new value: {new_value} "
|
||||
)
|
||||
|
||||
def compute_total(self):
|
||||
return len(self.who)
|
||||
|
||||
|
||||
class InputChangeApp(App):
|
||||
def compose(self):
|
||||
yield Vertical(
|
||||
Input(placeholder="enter your name..."),
|
||||
WelcomeWidget(classes="welcome"),
|
||||
)
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
target = self.query_one(".welcome")
|
||||
value = event.value
|
||||
|
||||
if len(value) > 15:
|
||||
event.input.value = value[:15]
|
||||
return
|
||||
|
||||
if not value:
|
||||
target.who = "World!"
|
||||
else:
|
||||
target.who = value
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = InputChangeApp()
|
||||
app.run()
|
||||
16
code/newsletter/N15/css/events.css
Normal file
16
code/newsletter/N15/css/events.css
Normal file
@@ -0,0 +1,16 @@
|
||||
#display-container {
|
||||
layout: grid;
|
||||
grid-size: 2 2;
|
||||
align: center middle;
|
||||
|
||||
background: grey;
|
||||
border: solid white;
|
||||
margin: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.block {
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
margin: 2;
|
||||
}
|
||||
Reference in New Issue
Block a user