From e96347466962943f277ae44ee49d6eaa5b345fde Mon Sep 17 00:00:00 2001 From: 100gle <569590461@qq.com> Date: Thu, 9 Mar 2023 09:43:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E6=89=A9=E5=B1=95N15=E4=B8=80=E7=AB=A0=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E6=BA=90=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/newsletter/N15/01_basic_app.py | 9 ++++ code/newsletter/N15/02_widgets.py | 70 ++++++++++++++++++++++++++ code/newsletter/N15/03_actions.py | 74 ++++++++++++++++++++++++++++ code/newsletter/N15/04_events.py | 62 +++++++++++++++++++++++ code/newsletter/N15/05_reactivity.py | 55 +++++++++++++++++++++ code/newsletter/N15/css/events.css | 16 ++++++ 6 files changed, 286 insertions(+) create mode 100644 code/newsletter/N15/01_basic_app.py create mode 100644 code/newsletter/N15/02_widgets.py create mode 100644 code/newsletter/N15/03_actions.py create mode 100644 code/newsletter/N15/04_events.py create mode 100644 code/newsletter/N15/05_reactivity.py create mode 100644 code/newsletter/N15/css/events.css diff --git a/code/newsletter/N15/01_basic_app.py b/code/newsletter/N15/01_basic_app.py new file mode 100644 index 0000000..8fc0e08 --- /dev/null +++ b/code/newsletter/N15/01_basic_app.py @@ -0,0 +1,9 @@ +from textual.app import App + + +class SimpleApp(App): + pass + +if __name__== '__main__': + app = SimpleApp() + app.run() diff --git a/code/newsletter/N15/02_widgets.py b/code/newsletter/N15/02_widgets.py new file mode 100644 index 0000000..f1c7e43 --- /dev/null +++ b/code/newsletter/N15/02_widgets.py @@ -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("(?PLorem 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() diff --git a/code/newsletter/N15/03_actions.py b/code/newsletter/N15/03_actions.py new file mode 100644 index 0000000..ed6d98e --- /dev/null +++ b/code/newsletter/N15/03_actions.py @@ -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("(?PLorem 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() diff --git a/code/newsletter/N15/04_events.py b/code/newsletter/N15/04_events.py new file mode 100644 index 0000000..a0fe0d3 --- /dev/null +++ b/code/newsletter/N15/04_events.py @@ -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() diff --git a/code/newsletter/N15/05_reactivity.py b/code/newsletter/N15/05_reactivity.py new file mode 100644 index 0000000..4e2f684 --- /dev/null +++ b/code/newsletter/N15/05_reactivity.py @@ -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() diff --git a/code/newsletter/N15/css/events.css b/code/newsletter/N15/css/events.css new file mode 100644 index 0000000..cda7664 --- /dev/null +++ b/code/newsletter/N15/css/events.css @@ -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; +}