mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Compound docs (#1952)
* compound example * update bit switch * prevent * no css * compound widget example * more diagrams * more diagrams * diagrams * words * words * remove sender * removed priority post * timer fix * test fixes * drop async version of post_message * extended docs * fix no app * Added control properties * changelog * changelog * changelog * fix for stopping timers * changelog * docs update * last byte example * new section * update of byte03 * updae to docs * Added compound examples * Rewording * Use set sender * don't need this * hyphens * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Dave Pearson <davep@davep.org> * Update docs/guide/widgets.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Update docs/guide/widgets.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Update docs/guide/widgets.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Update docs/guide/widgets.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * parenthesis * stack diagram --------- Co-authored-by: Dave Pearson <davep@davep.org> Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
This commit is contained in:
81
docs/examples/guide/compound/byte01.py
Normal file
81
docs/examples/guide/compound/byte01.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Input, Label, Switch
|
||||
|
||||
|
||||
class BitSwitch(Widget):
|
||||
"""A Switch with a numeric label above it."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
BitSwitch {
|
||||
layout: vertical;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
BitSwitch > Label {
|
||||
text-align: center;
|
||||
width: 1fr;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, bit: int) -> None:
|
||||
self.bit = bit
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Label(str(self.bit))
|
||||
yield Switch()
|
||||
|
||||
|
||||
class ByteInput(Widget):
|
||||
"""A compound widget with 8 switches."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
ByteInput {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: blank;
|
||||
layout: horizontal;
|
||||
}
|
||||
ByteInput:focus-within {
|
||||
border: heavy $secondary;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
for bit in reversed(range(8)):
|
||||
yield BitSwitch(bit)
|
||||
|
||||
|
||||
class ByteEditor(Widget):
|
||||
DEFAULT_CSS = """
|
||||
ByteEditor > Container {
|
||||
height: 1fr;
|
||||
align: center middle;
|
||||
}
|
||||
ByteEditor > Container.top {
|
||||
background: $boost;
|
||||
}
|
||||
ByteEditor Input {
|
||||
width: 16;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Container(classes="top"):
|
||||
yield Input(placeholder="byte")
|
||||
with Container():
|
||||
yield ByteInput()
|
||||
|
||||
|
||||
class ByteInputApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield ByteEditor()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = ByteInputApp()
|
||||
app.run()
|
||||
106
docs/examples/guide/compound/byte02.py
Normal file
106
docs/examples/guide/compound/byte02.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.messages import Message
|
||||
from textual.reactive import reactive
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Input, Label, Switch
|
||||
|
||||
|
||||
class BitSwitch(Widget):
|
||||
"""A Switch with a numeric label above it."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
BitSwitch {
|
||||
layout: vertical;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
BitSwitch > Label {
|
||||
text-align: center;
|
||||
width: 1fr;
|
||||
}
|
||||
"""
|
||||
|
||||
class BitChanged(Message):
|
||||
"""Sent when the 'bit' changes."""
|
||||
|
||||
def __init__(self, bit: int, value: bool) -> None:
|
||||
super().__init__()
|
||||
self.bit = bit
|
||||
self.value = value
|
||||
|
||||
value = reactive(0) # (1)!
|
||||
|
||||
def __init__(self, bit: int) -> None:
|
||||
self.bit = bit
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Label(str(self.bit))
|
||||
yield Switch()
|
||||
|
||||
def on_switch_changed(self, event: Switch.Changed) -> None: # (2)!
|
||||
"""When the switch changes, notify the parent via a message."""
|
||||
event.stop() # (3)!
|
||||
self.value = event.value # (4)!
|
||||
self.post_message(self.BitChanged(self.bit, event.value))
|
||||
|
||||
|
||||
class ByteInput(Widget):
|
||||
"""A compound widget with 8 switches."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
ByteInput {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: blank;
|
||||
layout: horizontal;
|
||||
}
|
||||
ByteInput:focus-within {
|
||||
border: heavy $secondary;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
for bit in reversed(range(8)):
|
||||
yield BitSwitch(bit)
|
||||
|
||||
|
||||
class ByteEditor(Widget):
|
||||
DEFAULT_CSS = """
|
||||
ByteEditor > Container {
|
||||
height: 1fr;
|
||||
align: center middle;
|
||||
}
|
||||
ByteEditor > Container.top {
|
||||
background: $boost;
|
||||
}
|
||||
ByteEditor Input {
|
||||
width: 16;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Container(classes="top"):
|
||||
yield Input(placeholder="byte")
|
||||
with Container():
|
||||
yield ByteInput()
|
||||
|
||||
def on_bit_switch_bit_changed(self, event: BitSwitch.BitChanged) -> None:
|
||||
"""When a switch changes, update the value."""
|
||||
value = 0
|
||||
for switch in self.query(BitSwitch):
|
||||
value |= switch.value << switch.bit
|
||||
self.query_one(Input).value = str(value)
|
||||
|
||||
|
||||
class ByteInputApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield ByteEditor()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = ByteInputApp()
|
||||
app.run()
|
||||
130
docs/examples/guide/compound/byte03.py
Normal file
130
docs/examples/guide/compound/byte03.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.geometry import clamp
|
||||
from textual.messages import Message
|
||||
from textual.reactive import reactive
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Input, Label, Switch
|
||||
|
||||
|
||||
class BitSwitch(Widget):
|
||||
"""A Switch with a numeric label above it."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
BitSwitch {
|
||||
layout: vertical;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
BitSwitch > Label {
|
||||
text-align: center;
|
||||
width: 1fr;
|
||||
}
|
||||
"""
|
||||
|
||||
class BitChanged(Message):
|
||||
"""Sent when the 'bit' changes."""
|
||||
|
||||
def __init__(self, bit: int, value: bool) -> None:
|
||||
super().__init__()
|
||||
self.bit = bit
|
||||
self.value = value
|
||||
|
||||
value = reactive(0)
|
||||
|
||||
def __init__(self, bit: int) -> None:
|
||||
self.bit = bit
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Label(str(self.bit))
|
||||
yield Switch()
|
||||
|
||||
def watch_value(self, value: bool) -> None: # (1)!
|
||||
"""When the value changes we want to set the switch accordingly."""
|
||||
self.query_one(Switch).value = value
|
||||
|
||||
def on_switch_changed(self, event: Switch.Changed) -> None:
|
||||
"""When the switch changes, notify the parent via a message."""
|
||||
event.stop()
|
||||
self.value = event.value
|
||||
self.post_message(self.BitChanged(self.bit, event.value))
|
||||
|
||||
|
||||
class ByteInput(Widget):
|
||||
"""A compound widget with 8 switches."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
ByteInput {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: blank;
|
||||
layout: horizontal;
|
||||
}
|
||||
ByteInput:focus-within {
|
||||
border: heavy $secondary;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
for bit in reversed(range(8)):
|
||||
yield BitSwitch(bit)
|
||||
|
||||
|
||||
class ByteEditor(Widget):
|
||||
DEFAULT_CSS = """
|
||||
ByteEditor > Container {
|
||||
height: 1fr;
|
||||
align: center middle;
|
||||
}
|
||||
ByteEditor > Container.top {
|
||||
background: $boost;
|
||||
}
|
||||
ByteEditor Input {
|
||||
width: 16;
|
||||
}
|
||||
"""
|
||||
|
||||
value = reactive(0)
|
||||
|
||||
def validate_value(self, value: int) -> int: # (2)!
|
||||
"""Ensure value is between 0 and 255."""
|
||||
return clamp(value, 0, 255)
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Container(classes="top"):
|
||||
yield Input(placeholder="byte")
|
||||
with Container():
|
||||
yield ByteInput()
|
||||
|
||||
def on_bit_switch_bit_changed(self, event: BitSwitch.BitChanged) -> None:
|
||||
"""When a switch changes, update the value."""
|
||||
value = 0
|
||||
for switch in self.query(BitSwitch):
|
||||
value |= switch.value << switch.bit
|
||||
self.query_one(Input).value = str(value)
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None: # (3)!
|
||||
"""When the text changes, set the value of the byte."""
|
||||
try:
|
||||
self.value = int(event.value or "0")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def watch_value(self, value: int) -> None: # (4)!
|
||||
"""When self.value changes, update switches."""
|
||||
for switch in self.query(BitSwitch):
|
||||
with switch.prevent(BitSwitch.BitChanged): # (5)!
|
||||
switch.value = bool(value & (1 << switch.bit)) # (6)!
|
||||
|
||||
|
||||
class ByteInputApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield ByteEditor()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = ByteInputApp()
|
||||
app.run()
|
||||
52
docs/examples/guide/compound/compound01.py
Normal file
52
docs/examples/guide/compound/compound01.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Input, Label
|
||||
|
||||
|
||||
class InputWithLabel(Widget):
|
||||
"""An input with a label."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
InputWithLabel {
|
||||
layout: horizontal;
|
||||
height: auto;
|
||||
}
|
||||
InputWithLabel Label {
|
||||
padding: 1;
|
||||
width: 12;
|
||||
text-align: right;
|
||||
}
|
||||
InputWithLabel Input {
|
||||
width: 1fr;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, input_label: str) -> None:
|
||||
self.input_label = input_label
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult: # (1)!
|
||||
yield Label(self.input_label)
|
||||
yield Input()
|
||||
|
||||
|
||||
class CompoundApp(App):
|
||||
CSS = """
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
InputWithLabel {
|
||||
width: 80%;
|
||||
margin: 1;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield InputWithLabel("Fist Name")
|
||||
yield InputWithLabel("Last Name")
|
||||
yield InputWithLabel("Email")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CompoundApp()
|
||||
app.run()
|
||||
Reference in New Issue
Block a user