diff --git a/examples/calculator.css b/examples/calculator.css index 6e8e5da0f..0d777edfa 100644 --- a/examples/calculator.css +++ b/examples/calculator.css @@ -27,10 +27,6 @@ Button { color: $text-primary-lighten-2; } -#ac,#c,#plus-minus,#percent { - tint: $primary 25%; -} - #number-0 { column-span: 2; } diff --git a/examples/calculator.py b/examples/calculator.py index 90eba01af..7c06c4d54 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -1,6 +1,7 @@ from decimal import Decimal from textual.app import App, ComposeResult +from textual import events from textual.layout import Container from textual.reactive import Reactive from textual.widgets import Button, Static @@ -16,6 +17,17 @@ class CalculatorApp(App): value = Reactive.var("") operator = Reactive.var("plus") + KEY_MAP = { + "+": "plus", + "-": "minus", + ".": "point", + "*": "multiply", + "/": "divide", + "_": "plus-minus", + "%": "percent", + "=": "equals", + } + def watch_numbers(self, value: str) -> None: """Called when numbers is updated.""" # Update the Numbers widget @@ -34,10 +46,10 @@ class CalculatorApp(App): """Add our buttons.""" yield Container( Static(id="numbers"), - Button("AC", id="ac"), - Button("C", id="c"), - Button("+/-", id="plus-minus"), - Button("%", id="percent"), + Button("AC", id="ac", variant="primary"), + Button("C", id="c", variant="primary"), + Button("+/-", id="plus-minus", variant="primary"), + Button("%", id="percent", variant="primary"), Button("รท", id="divide", variant="warning"), Button("7", id="number-7"), Button("8", id="number-8"), @@ -57,6 +69,22 @@ class CalculatorApp(App): id="calculator", ) + def on_key(self, event: events.Key) -> None: + """Called when the user presses a key.""" + + def press(button_id: str) -> None: + self.query_one(f"#{button_id}", Button).press() + self.set_focus(None) + + key = event.key + if key.isdecimal(): + press(f"number-{key}") + elif key == "c": + press("c") + press("ac") + elif key in self.KEY_MAP: + press(self.KEY_MAP[key]) + def on_button_pressed(self, event: Button.Pressed) -> None: """Called when a button is pressed.""" diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 4a2b806b2..bb653bfd9 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -18,8 +18,8 @@ from ..message import Message from ..reactive import Reactive from ..widget import Widget -ButtonVariant = Literal["default", "success", "warning", "error"] -_VALID_BUTTON_VARIANTS = {"default", "success", "warning", "error"} +ButtonVariant = Literal["default", "primary", "success", "warning", "error"] +_VALID_BUTTON_VARIANTS = {"default", "primary", "success", "warning", "error"} class InvalidButtonVariant(Exception): @@ -59,6 +59,32 @@ class Button(Widget, can_focus=True): border-top: tall $panel-darken-2; } + /* Primary variant */ + Button.-primary { + background: $primary; + color: $text-primary; + border-top: tall $primary-lighten-2; + border-bottom: tall $primary-darken-3; + + } + + Button.-primary:hover { + background: $primary-darken-2; + color: $text-primary-darken-2; + + } + + Button.-active { + tint: $background 30%; + } + + Button.-primary.-active { + background: $primary; + border-bottom: tall $primary-lighten-3; + border-top: tall $primary-darken-3; + + } + /* Success variant */ Button.-success { @@ -188,14 +214,18 @@ class Button(Widget, can_focus=True): label.stylize(self.text_style) return label - async def on_click(self, event: events.Click) -> None: + async def _on_click(self, event: events.Click) -> None: event.stop() - if self.disabled: + self.press() + + def press(self) -> None: + """Respond to a button press.""" + if self.disabled or not self.display: return # Manage the "active" effect: self._start_active_affect() # ...and let other components know that we've just been clicked: - await self.emit(Button.Pressed(self)) + self.emit_no_wait(Button.Pressed(self)) def _start_active_affect(self) -> None: """Start a small animation to show the button was clicked.""" @@ -204,7 +234,7 @@ class Button(Widget, can_focus=True): self.ACTIVE_EFFECT_DURATION, partial(self.remove_class, "-active") ) - async def on_key(self, event: events.Key) -> None: + async def _on_key(self, event: events.Key) -> None: if event.key == "enter" and not self.disabled: self._start_active_affect() await self.emit(Button.Pressed(self))