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()
|
||||
@@ -194,10 +194,6 @@ Let's modify the default width for the fizzbuzz example. By default, the table w
|
||||
Note that we've added `expand=True` to tell the `Table` to expand beyond the optimal width, so that it fills the 50 characters returned by `get_content_width`.
|
||||
|
||||
|
||||
## Compound widgets
|
||||
|
||||
TODO: Explanation of compound widgets
|
||||
|
||||
## Line API
|
||||
|
||||
A downside of widgets that return Rich renderables is that Textual will redraw the entire widget when its state is updated or it changes size.
|
||||
@@ -388,3 +384,191 @@ The following builtin widgets use the Line API. If you are building advanced wid
|
||||
- [DataTable](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_data_table.py)
|
||||
- [TextLog](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_text_log.py)
|
||||
- [Tree](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_tree.py)
|
||||
|
||||
## Compound widgets
|
||||
|
||||
Widgets may be combined to create new widgets with additional features.
|
||||
Such widgets are known as *compound widgets*.
|
||||
The stopwatch in the [tutorial](./../tutorial.md) is an example of a compound widget.
|
||||
|
||||
A compound widget can be used like any other widget.
|
||||
The only thing that differs is that when you build a compound widget, you write a `compose()` method which yields *child* widgets, rather than implement a `render` or `render_line` method.
|
||||
|
||||
The following is an example of a compound widget.
|
||||
|
||||
=== "compound01.py"
|
||||
|
||||
```python title="compound01.py" hl_lines="28-30 44-47"
|
||||
--8<-- "docs/examples/guide/compound/compound01.py"
|
||||
```
|
||||
|
||||
1. The `compose` method makes this widget a *compound* widget.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/compound/compound01.py"}
|
||||
```
|
||||
|
||||
The `InputWithLabel` class bundles an [Input](../widgets/input.md) with a [Label](../widgets/label.md) to create a new widget that displays a right-aligned label next to an input control. You can re-use this `InputWithLabel` class anywhere in a Textual app, including in other widgets.
|
||||
|
||||
## Coordinating widgets
|
||||
|
||||
Widgets rarely exist in isolation, and often need to communicate or exchange data with other parts of your app.
|
||||
This is not difficult to do, but there is a risk that widgets can become dependant on each other, making it impossible to reuse a widget without copying a lot of dependant code.
|
||||
|
||||
In this section we will show how to design and build a fully-working app, while keeping widgets reusable.
|
||||
|
||||
### Designing the app
|
||||
|
||||
We are going to build a *byte editor* which allows you to enter a number in both decimal and binary. You could use this a teaching aid for binary numbers.
|
||||
|
||||
Here's a sketch of what the app should ultimately look like:
|
||||
|
||||
!!! tip
|
||||
|
||||
There are plenty of resources on the web, such as this [excellent video from Khan Academy](https://www.khanacademy.org/math/algebra-home/alg-intro-to-algebra/algebra-alternate-number-bases/v/number-systems-introduction) if you want to brush up on binary numbers.
|
||||
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/byte01.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
There are three types of built-in widget in the sketch, namely ([Input](../widgets/input.md), [Label](../widgets/label.md), and [Switch](../widgets/switch.md)). Rather than manage these as a single collection of widgets, we can arrange them in to logical groups with compound widgets. This will make our app easier to work with.
|
||||
|
||||
### Identifying components
|
||||
|
||||
We will divide this UI into three compound widgets:
|
||||
|
||||
1. `BitSwitch` for a switch with a numeric label.
|
||||
2. `ByteInput` which contains 8 `BitSwitch` widgets.
|
||||
3. `ByteEditor` which contains a `ByteInput` and an [Input](../widgets/input.md) to show the decimal value.
|
||||
|
||||
This is not the only way we could implement our design with compound widgets.
|
||||
So why these three widgets?
|
||||
As a rule of thumb, a widget should handle one piece of data, which is why we have an independent widget for a bit, a byte, and the decimal value.
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/byte02.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
In the following code we will implement the three widgets. There will be no functionality yet, but it should look like our design.
|
||||
|
||||
=== "byte01.py"
|
||||
|
||||
```python title="byte01.py" hl_lines="28-30 48-50 67-71"
|
||||
--8<-- "docs/examples/guide/compound/byte01.py"
|
||||
```
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/compound/byte01.py" columns="90" line="30"}
|
||||
```
|
||||
|
||||
Note the `compose()` methods of each of the widgets.
|
||||
|
||||
- The `BitSwitch` yields a [Label](../widgets/label.md) which displays the bit number, and a [Switch](../widgets/switch.md) control for that bit. The default CSS for `BitSwitch` aligns its children vertically, and sets the label's [text-align](../styles/text_align.md) to center.
|
||||
|
||||
- The `ByteInput` yields 8 `BitSwitch` widgets and arranges them horizontally. It also adds a `focus-within` style in its CSS to draw an accent border when any of the switches are focused.
|
||||
|
||||
- The `ByteEditor` yields a `ByteInput` and an `Input` control. The default CSS stacks the two controls on top of each other to divide the screen in to two parts.
|
||||
|
||||
With these three widgets, the [DOM](CSS.md#the-dom) for our app will look like this:
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/byte_input_dom.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
Now that we have the design in place, we can implement the behavior.
|
||||
|
||||
|
||||
### Data flow
|
||||
|
||||
We want to ensure that our widgets are re-usable, which we can do by following the guideline of "attributes down, messages up". This means that a widget can update a child by setting its attributes or calling its methods, but widgets should only ever send [messages](./events.md) to their *parent* (or other ancestors).
|
||||
|
||||
!!! info
|
||||
|
||||
This pattern of only setting attributes in one direction and using messages for the opposite direction is known as *uni-directional data flow*.
|
||||
|
||||
In practice, this means that to update a child widget you get a reference to it and use it like any other Python object. Here's an example of an [action](actions.md) that updates a child widget:
|
||||
|
||||
```python
|
||||
def action_set_true(self):
|
||||
self.query_one(Switch).value = 1
|
||||
```
|
||||
|
||||
If a child needs to update a parent, it should send a message with [post_message][textual.message_pump.MessagePump.post_message].
|
||||
|
||||
Here's an example of posting message:
|
||||
|
||||
```python
|
||||
def on_click(self):
|
||||
self.post_message(MyWidget.Change(active=True))
|
||||
```
|
||||
|
||||
Note that *attributes down and messages up* means that you can't modify widgets on the same level directly. If you want to modify a *sibling*, you will need to send a message to the parent, and the parent would make the changes.
|
||||
|
||||
The following diagram illustrates this concept:
|
||||
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/attributes_messages.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
### Messages up
|
||||
|
||||
Let's extend the `ByteEditor` so that clicking any of the 8 `BitSwitch` widgets updates the decimal value. To do this we will add a custom message to `BitSwitch` that we catch in the `ByteEditor`.
|
||||
|
||||
=== "byte02.py"
|
||||
|
||||
```python title="byte02.py" hl_lines="5-6 26-32 34 44-48 91-96"
|
||||
--8<-- "docs/examples/guide/compound/byte02.py"
|
||||
```
|
||||
|
||||
1. This will store the value of the "bit".
|
||||
2. This is sent by the builtin `Switch` widgets, when it changes state.
|
||||
3. Stop the event, because we don't want it to go to the parent.
|
||||
4. Store the new value of the "bit".
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/compound/byte02.py" columns="90" line="30", press="tab,tab,tab,tab,enter"}
|
||||
```
|
||||
|
||||
- The `BitSwitch` widget now has an `on_switch_changed` method which will handle a [`Switch.Changed`][textual.widgets.Switch.Changed] message, sent when the user clicks a switch. We use this to store the new value of the bit, and sent a new custom message, `BitSwitch.BitChanged`.
|
||||
- The `ByteEditor` widget handles the `BitSwitch.Changed` message by calculating the decimal value and setting it on the input.
|
||||
|
||||
The following is a (simplified) DOM diagram to show how the new message is processed:
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/bit_switch_message.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
|
||||
### Attributes down
|
||||
|
||||
We also want the switches to update if the user edits the decimal value.
|
||||
|
||||
Since the switches are children of `ByteEditor` we can update them by setting their attributes directly.
|
||||
This is an example of "attributes down".
|
||||
|
||||
=== "byte02.py"
|
||||
|
||||
```python title="byte03.py" hl_lines="5 45-47 90 92-94 109-114 116-120"
|
||||
--8<-- "docs/examples/guide/compound/byte03.py"
|
||||
```
|
||||
|
||||
1. When the `BitSwitch`'s value changed, we want to update the builtin `Switch` to match.
|
||||
2. Ensure the value is in a the range of a byte.
|
||||
3. Handle the `Input.Changed` event when the user modified the value in the input.
|
||||
4. When the `ByteEditor` value changes, update all the switches to match.
|
||||
5. Prevent the `BitChanged` message from being sent.
|
||||
6. Because `switch` is a child, we can set its attributes directly.
|
||||
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/compound/byte03.py" columns="90" line="30", press="tab,1,0,0"}
|
||||
```
|
||||
|
||||
- When the user edits the input, the [Input](../widgets/input.md) widget sends a `Changed` event, which we handle with `on_input_changed` by setting `self.value`, which is a reactive value we added to `ByteEditor`.
|
||||
- If the value has changed, Textual will call `watch_value` which sets the value of each of the eight switches. Because we are working with children of the `ByteEditor`, we can set attributes directly without going via a message.
|
||||
|
||||
16
docs/images/attributes_messages.excalidraw.svg
Normal file
16
docs/images/attributes_messages.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
16
docs/images/bit_switch_message.excalidraw.svg
Normal file
16
docs/images/bit_switch_message.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 43 KiB |
16
docs/images/byte01.excalidraw.svg
Normal file
16
docs/images/byte01.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 76 KiB |
16
docs/images/byte02.excalidraw.svg
Normal file
16
docs/images/byte02.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 77 KiB |
16
docs/images/byte_input_dom.excalidraw.svg
Normal file
16
docs/images/byte_input_dom.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 29 KiB |
Reference in New Issue
Block a user