mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
flat buttons
This commit is contained in:
@@ -24,6 +24,22 @@ class ButtonsApp(App[str]):
|
||||
Button.warning("Warning!", disabled=True),
|
||||
Button.error("Error!", disabled=True),
|
||||
),
|
||||
VerticalScroll(
|
||||
Static("Flat Buttons", classes="header"),
|
||||
Button("Default", flat=True),
|
||||
Button("Primary!", variant="primary", flat=True),
|
||||
Button.success("Success!", flat=True),
|
||||
Button.warning("Warning!", flat=True),
|
||||
Button.error("Error!", flat=True),
|
||||
),
|
||||
VerticalScroll(
|
||||
Static("Disabled Flat Buttons", classes="header"),
|
||||
Button("Default", disabled=True, flat=True),
|
||||
Button("Primary!", variant="primary", disabled=True, flat=True),
|
||||
Button.success("Success!", disabled=True, flat=True),
|
||||
Button.warning("Warning!", disabled=True, flat=True),
|
||||
Button.error("Error!", disabled=True, flat=True),
|
||||
),
|
||||
)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
|
||||
@@ -87,6 +87,11 @@ BORDER_CHARS: dict[
|
||||
("█", " ", "█"),
|
||||
("█", "▄", "█"),
|
||||
),
|
||||
"block": (
|
||||
("▄", "▄", "▄"),
|
||||
("█", " ", "█"),
|
||||
("▀", "▀", "▀"),
|
||||
),
|
||||
"hkey": (
|
||||
("▔", "▔", "▔"),
|
||||
(" ", " ", " "),
|
||||
@@ -190,6 +195,11 @@ BORDER_LOCATIONS: dict[
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
),
|
||||
"block": (
|
||||
(1, 1, 1),
|
||||
(0, 0, 0),
|
||||
(1, 1, 1),
|
||||
),
|
||||
"hkey": (
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
|
||||
@@ -41,6 +41,7 @@ from typing import (
|
||||
Generic,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Sequence,
|
||||
TextIO,
|
||||
@@ -3969,12 +3970,17 @@ class App(Generic[ReturnType], DOMNode):
|
||||
)
|
||||
|
||||
def _parse_action(
|
||||
self, action: str | ActionParseResult, default_namespace: DOMNode
|
||||
self,
|
||||
action: str | ActionParseResult,
|
||||
default_namespace: DOMNode,
|
||||
namespaces: Mapping[str, DOMNode] | None = None,
|
||||
) -> tuple[DOMNode, str, tuple[object, ...]]:
|
||||
"""Parse an action.
|
||||
|
||||
Args:
|
||||
action: An action string.
|
||||
default_namespace: Namespace to user when none is supplied in the action.
|
||||
namespaces: Mapping of namespaces.
|
||||
|
||||
Raises:
|
||||
ActionError: If there are any errors parsing the action string.
|
||||
@@ -3987,8 +3993,10 @@ class App(Generic[ReturnType], DOMNode):
|
||||
else:
|
||||
destination, action_name, params = actions.parse(action)
|
||||
|
||||
action_target: DOMNode | None = None
|
||||
if destination:
|
||||
action_target: DOMNode | None = (
|
||||
None if namespaces is None else namespaces.get(destination)
|
||||
)
|
||||
if destination and action_target is None:
|
||||
if destination not in self._action_targets:
|
||||
raise ActionError(f"Action namespace {destination} is not known")
|
||||
action_target = getattr(self, destination, None)
|
||||
@@ -4021,6 +4029,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self,
|
||||
action: str | ActionParseResult,
|
||||
default_namespace: DOMNode | None = None,
|
||||
namespaces: Mapping[str, DOMNode] | None = None,
|
||||
) -> bool:
|
||||
"""Perform an [action](/guide/actions).
|
||||
|
||||
@@ -4030,12 +4039,13 @@ class App(Generic[ReturnType], DOMNode):
|
||||
action: Action encoded in a string.
|
||||
default_namespace: Namespace to use if not provided in the action,
|
||||
or None to use app.
|
||||
namespaces: Mapping of namespaces.
|
||||
|
||||
Returns:
|
||||
True if the event has been handled.
|
||||
"""
|
||||
action_target, action_name, params = self._parse_action(
|
||||
action, self if default_namespace is None else default_namespace
|
||||
action, self if default_namespace is None else default_namespace, namespaces
|
||||
)
|
||||
if action_target.check_action(action_name, params):
|
||||
return await self._dispatch_action(action_target, action_name, params)
|
||||
|
||||
@@ -24,6 +24,7 @@ VALID_BORDER: Final = {
|
||||
"tall",
|
||||
"tab",
|
||||
"thick",
|
||||
"block",
|
||||
"vkey",
|
||||
"wide",
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ EdgeType = Literal[
|
||||
"round",
|
||||
"solid",
|
||||
"thick",
|
||||
"block",
|
||||
"double",
|
||||
"dashed",
|
||||
"heavy",
|
||||
|
||||
@@ -19,6 +19,7 @@ from typing import (
|
||||
Collection,
|
||||
Generator,
|
||||
Iterable,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
@@ -4304,13 +4305,16 @@ class Widget(DOMNode):
|
||||
self._layout_cache[cache_key] = visual
|
||||
return visual
|
||||
|
||||
async def run_action(self, action: str) -> None:
|
||||
async def run_action(
|
||||
self, action: str, namespaces: Mapping[str, DOMNode] | None = None
|
||||
) -> None:
|
||||
"""Perform a given action, with this widget as the default namespace.
|
||||
|
||||
Args:
|
||||
action: Action encoded as a string.
|
||||
namespaces: Mapping of namespaces.
|
||||
"""
|
||||
await self.app.run_action(action, self)
|
||||
await self.app.run_action(action, self, namespaces)
|
||||
|
||||
def post_message(self, message: Message) -> bool:
|
||||
"""Post a message to this widget.
|
||||
|
||||
@@ -50,109 +50,149 @@ class Button(Widget, can_focus=True):
|
||||
Button {
|
||||
width: auto;
|
||||
min-width: 16;
|
||||
height: auto;
|
||||
color: $button-foreground;
|
||||
background: $surface;
|
||||
border: none;
|
||||
border-top: tall $surface-lighten-1;
|
||||
border-bottom: tall $surface-darken-1;
|
||||
height:auto;
|
||||
line-pad: 1;
|
||||
text-align: center;
|
||||
content-align: center middle;
|
||||
text-style: bold;
|
||||
line-pad: 1;
|
||||
|
||||
|
||||
&.-textual-compact {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
text-opacity: 0.6;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
text-style: $button-focus-text-style;
|
||||
background-tint: $foreground 5%;
|
||||
}
|
||||
&:hover {
|
||||
border-top: tall $surface;
|
||||
background: $surface-darken-1;
|
||||
}
|
||||
&.-active {
|
||||
&.-style-flat {
|
||||
|
||||
color: auto 90%;
|
||||
background: $surface;
|
||||
border-bottom: tall $surface-lighten-1;
|
||||
border-top: tall $surface-darken-1;
|
||||
tint: $background 30%;
|
||||
}
|
||||
|
||||
&.-primary {
|
||||
color: $button-color-foreground;
|
||||
background: $primary;
|
||||
border-top: tall $primary-lighten-3;
|
||||
border-bottom: tall $primary-darken-3;
|
||||
|
||||
border: block $surface;
|
||||
&:hover {
|
||||
background: $primary-darken-2;
|
||||
border-top: tall $primary;
|
||||
opacity: 90%;
|
||||
}
|
||||
&:focus {
|
||||
text-style: $button-focus-text-style;
|
||||
}
|
||||
&.-active {
|
||||
background: $surface;
|
||||
border: block $surface;
|
||||
tint: $background 30%;
|
||||
}
|
||||
|
||||
&.-primary {
|
||||
background: $primary-muted;
|
||||
border: block $primary-muted;
|
||||
}
|
||||
&.-success {
|
||||
background: $success-muted;
|
||||
border: block $success-muted;
|
||||
}
|
||||
&.-warning {
|
||||
background: $warning-muted;
|
||||
border: block $warning-muted;
|
||||
}
|
||||
&.-error {
|
||||
background: $error-muted;
|
||||
border: block $error-muted;
|
||||
}
|
||||
}
|
||||
&.-style-default {
|
||||
text-style: bold;
|
||||
color: $button-foreground;
|
||||
background: $surface;
|
||||
border: none;
|
||||
border-top: tall $surface-lighten-1;
|
||||
border-bottom: tall $surface-darken-1;
|
||||
|
||||
|
||||
&.-textual-compact {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
text-opacity: 0.4;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
text-style: $button-focus-text-style;
|
||||
background-tint: $foreground 5%;
|
||||
}
|
||||
&:hover {
|
||||
border-top: tall $surface;
|
||||
background: $surface-darken-1;
|
||||
}
|
||||
|
||||
&.-active {
|
||||
background: $surface;
|
||||
border-bottom: tall $surface-lighten-1;
|
||||
border-top: tall $surface-darken-1;
|
||||
tint: $background 30%;
|
||||
}
|
||||
|
||||
&.-primary {
|
||||
color: $button-color-foreground;
|
||||
background: $primary;
|
||||
border-bottom: tall $primary-lighten-3;
|
||||
border-top: tall $primary-darken-3;
|
||||
}
|
||||
}
|
||||
border-top: tall $primary-lighten-3;
|
||||
border-bottom: tall $primary-darken-3;
|
||||
|
||||
&.-success {
|
||||
color: $button-color-foreground;
|
||||
background: $success;
|
||||
border-top: tall $success-lighten-2;
|
||||
border-bottom: tall $success-darken-3;
|
||||
&:hover {
|
||||
background: $primary-darken-2;
|
||||
border-top: tall $primary;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $success-darken-2;
|
||||
border-top: tall $success;
|
||||
&.-active {
|
||||
background: $primary;
|
||||
border-bottom: tall $primary-lighten-3;
|
||||
border-top: tall $primary-darken-3;
|
||||
}
|
||||
}
|
||||
|
||||
&.-active {
|
||||
&.-success {
|
||||
color: $button-color-foreground;
|
||||
background: $success;
|
||||
border-bottom: tall $success-lighten-2;
|
||||
border-top: tall $success-darken-2;
|
||||
}
|
||||
}
|
||||
border-top: tall $success-lighten-2;
|
||||
border-bottom: tall $success-darken-3;
|
||||
|
||||
&.-warning{
|
||||
color: $button-color-foreground;
|
||||
background: $warning;
|
||||
border-top: tall $warning-lighten-2;
|
||||
border-bottom: tall $warning-darken-3;
|
||||
&:hover {
|
||||
background: $success-darken-2;
|
||||
border-top: tall $success;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $warning-darken-2;
|
||||
border-top: tall $warning;
|
||||
&.-active {
|
||||
background: $success;
|
||||
border-bottom: tall $success-lighten-2;
|
||||
border-top: tall $success-darken-2;
|
||||
}
|
||||
}
|
||||
|
||||
&.-active {
|
||||
&.-warning{
|
||||
color: $button-color-foreground;
|
||||
background: $warning;
|
||||
border-bottom: tall $warning-lighten-2;
|
||||
border-top: tall $warning-darken-2;
|
||||
}
|
||||
}
|
||||
border-top: tall $warning-lighten-2;
|
||||
border-bottom: tall $warning-darken-3;
|
||||
|
||||
&.-error {
|
||||
color: $button-color-foreground;
|
||||
background: $error;
|
||||
border-top: tall $error-lighten-2;
|
||||
border-bottom: tall $error-darken-3;
|
||||
&:hover {
|
||||
background: $warning-darken-2;
|
||||
border-top: tall $warning;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $error-darken-1;
|
||||
border-top: tall $error;
|
||||
&.-active {
|
||||
background: $warning;
|
||||
border-bottom: tall $warning-lighten-2;
|
||||
border-top: tall $warning-darken-2;
|
||||
}
|
||||
}
|
||||
|
||||
&.-active {
|
||||
&.-error {
|
||||
color: $button-color-foreground;
|
||||
background: $error;
|
||||
border-bottom: tall $error-lighten-2;
|
||||
border-top: tall $error-darken-2;
|
||||
border-top: tall $error-lighten-2;
|
||||
border-bottom: tall $error-darken-3;
|
||||
|
||||
&:hover {
|
||||
background: $error-darken-1;
|
||||
border-top: tall $error;
|
||||
}
|
||||
|
||||
&.-active {
|
||||
background: $error;
|
||||
border-bottom: tall $error-lighten-2;
|
||||
border-top: tall $error-darken-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +209,9 @@ class Button(Widget, can_focus=True):
|
||||
compact = reactive(False, toggle_class="-textual-compact")
|
||||
"""Make the button compact (without borders)."""
|
||||
|
||||
flat = reactive(False)
|
||||
"""Enable alternative flat button style."""
|
||||
|
||||
class Pressed(Message):
|
||||
"""Event sent when a `Button` is pressed and there is no Button action.
|
||||
|
||||
@@ -201,6 +244,7 @@ class Button(Widget, can_focus=True):
|
||||
tooltip: RenderableType | None = None,
|
||||
action: str | None = None,
|
||||
compact: bool = False,
|
||||
flat: bool = False,
|
||||
):
|
||||
"""Create a Button widget.
|
||||
|
||||
@@ -214,6 +258,7 @@ class Button(Widget, can_focus=True):
|
||||
tooltip: Optional tooltip.
|
||||
action: Optional action to run when clicked.
|
||||
compact: Enable compact button style.
|
||||
flat: Enable alternative flat look buttons.
|
||||
"""
|
||||
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||
|
||||
@@ -224,6 +269,7 @@ class Button(Widget, can_focus=True):
|
||||
self.variant = variant
|
||||
self.action = action
|
||||
self.compact = compact
|
||||
self.flat = flat
|
||||
self.active_effect_duration = 0.2
|
||||
"""Amount of time in seconds the button 'press' animation lasts."""
|
||||
|
||||
@@ -253,6 +299,10 @@ class Button(Widget, can_focus=True):
|
||||
self.remove_class(f"-{old_variant}")
|
||||
self.add_class(f"-{variant}")
|
||||
|
||||
def watch_flat(self, flat: bool) -> None:
|
||||
self.set_class(flat, "-style-flat")
|
||||
self.set_class(not flat, "-style-default")
|
||||
|
||||
def validate_label(self, label: ContentText) -> Content:
|
||||
"""Parse markup for self.label"""
|
||||
return Content.from_text(label)
|
||||
@@ -314,6 +364,7 @@ class Button(Widget, can_focus=True):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
flat: bool = False,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating a success Button variant.
|
||||
|
||||
@@ -324,6 +375,7 @@ class Button(Widget, can_focus=True):
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
disabled: Whether the button is disabled or not.
|
||||
flat: Enable alternative flat look buttons.
|
||||
|
||||
Returns:
|
||||
A [`Button`][textual.widgets.Button] widget of the 'success'
|
||||
@@ -336,6 +388,7 @@ class Button(Widget, can_focus=True):
|
||||
id=id,
|
||||
classes=classes,
|
||||
disabled=disabled,
|
||||
flat=flat,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -347,6 +400,7 @@ class Button(Widget, can_focus=True):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
flat: bool = False,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating a warning Button variant.
|
||||
|
||||
@@ -357,6 +411,7 @@ class Button(Widget, can_focus=True):
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
disabled: Whether the button is disabled or not.
|
||||
flat: Enable alternative flat look buttons.
|
||||
|
||||
Returns:
|
||||
A [`Button`][textual.widgets.Button] widget of the 'warning'
|
||||
@@ -369,6 +424,7 @@ class Button(Widget, can_focus=True):
|
||||
id=id,
|
||||
classes=classes,
|
||||
disabled=disabled,
|
||||
flat=flat,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -380,6 +436,7 @@ class Button(Widget, can_focus=True):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
flat: bool = False,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating an error Button variant.
|
||||
|
||||
@@ -390,6 +447,7 @@ class Button(Widget, can_focus=True):
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
disabled: Whether the button is disabled or not.
|
||||
flat: Enable alternative flat look buttons.
|
||||
|
||||
Returns:
|
||||
A [`Button`][textual.widgets.Button] widget of the 'error'
|
||||
@@ -402,4 +460,5 @@ class Button(Widget, can_focus=True):
|
||||
id=id,
|
||||
classes=classes,
|
||||
disabled=disabled,
|
||||
flat=flat,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user