Merge branch 'css' of github.com:Textualize/textual into user-css-over-widget-css

This commit is contained in:
Darren Burns
2022-06-28 13:53:08 +01:00
2 changed files with 86 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import sys
from functools import partial
from typing import cast
if sys.version_info >= (3, 8):
@@ -41,6 +42,8 @@ class Button(Widget, can_focus=True):
margin: 1 0;
align: center middle;
text-style: bold;
transition: background 0.1;/* for "active" effect */
}
Button:hover {
@@ -49,6 +52,10 @@ class Button(Widget, can_focus=True):
border: tall $primary-lighten-1;
}
Button.-active {
background: $primary-lighten-1;
}
.-dark-mode Button {
background: $background;
color: $primary-lighten-2;
@@ -59,6 +66,10 @@ class Button(Widget, can_focus=True):
background: $surface;
}
.-dark-mode Button.-active {
background: $background-lighten-3;
}
/* Success variant */
Button.-success {
background: $success;
@@ -72,6 +83,10 @@ class Button(Widget, can_focus=True):
border: tall $success-lighten-2;
}
Button.-success.-active {
background: $success-lighten-1;
}
.-dark-mode Button.-success {
background: $success;
color: $text-success;
@@ -84,6 +99,10 @@ class Button(Widget, can_focus=True):
border: tall $success-lighten-3;
}
.-dark-mode Button.-success.-active {
background: $success-lighten-1;
}
/* Warning variant */
Button.-warning {
background: $warning;
@@ -97,6 +116,10 @@ class Button(Widget, can_focus=True):
border: tall $warning-lighten-3;
}
Button.-warning.-active {
background: $warning;
}
.-dark-mode Button.-warning {
background: $warning;
color: $text-warning;
@@ -109,6 +132,10 @@ class Button(Widget, can_focus=True):
border: tall $warning-lighten-3;
}
.-dark-mode Button.-warning.-active {
background: $warning-lighten-1;
}
/* Error variant */
Button.-error {
background: $error;
@@ -122,6 +149,10 @@ class Button(Widget, can_focus=True):
border: tall $error-lighten-3;
}
Button.-error.-active {
background: $error;
}
.-dark-mode Button.-error {
background: $error;
color: $text-error;
@@ -134,11 +165,18 @@ class Button(Widget, can_focus=True):
border: tall $error-lighten-3;
}
.-dark-mode Button.-error.-active {
background: $error;
}
App.-show-focus Button:focus {
tint: $accent 20%;
}
"""
ACTIVE_EFFECT_DURATION = 0.3
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
class Pressed(Message, bubble=True):
@property
def button(self) -> Button:
@@ -199,8 +237,15 @@ class Button(Widget, can_focus=True):
async def on_click(self, event: events.Click) -> None:
event.stop()
if not self.disabled:
await self.emit(Button.Pressed(self))
if self.disabled:
return
# Manage the "active" effect:
self.add_class("-active")
self.set_timer(
self.ACTIVE_EFFECT_DURATION, partial(self.remove_class, "-active")
)
# ...and let other components know that we've just been clicked:
await self.emit(Button.Pressed(self))
async def on_key(self, event: events.Key) -> None:
if event.key == "enter" and not self.disabled:

View File

@@ -19,13 +19,12 @@ SCREEN_H = 8 # height of our Screens
SCREEN_SIZE = Size(SCREEN_W, SCREEN_H)
PLACEHOLDERS_DEFAULT_H = 3 # the default height for our Placeholder widgets
# TODO: Brittle test
# These tests are currently way to brittle due to the CSS layout not being final
# They are also very hard to follow, if they break its not clear *what* went wrong
# Going to leave them marked as "skip" for now.
# As per Widget's CSS property, by default Widgets have a horizontal scrollbar of size 1
# and a vertical scrollbar of size 2:
SCROLL_H_SIZE = 1
SCROLL_V_SIZE = 2
@pytest.mark.skip("brittle")
@pytest.mark.asyncio
@pytest.mark.integration_test # this is a slow test, we may want to skip them in some contexts
@pytest.mark.parametrize(
@@ -70,7 +69,7 @@ PLACEHOLDERS_DEFAULT_H = 3 # the default height for our Placeholder widgets
# #root's virtual height should be as high as its stacked content
(SCREEN_W - 2 - 1, PLACEHOLDERS_DEFAULT_H * 4),
# placeholders width=same than screen, minus 2 borders, minus scrollbar :: height=default height minus 2 borders
(SCREEN_W - 2 - 1, PLACEHOLDERS_DEFAULT_H),
(SCREEN_W - 2 - SCROLL_V_SIZE, PLACEHOLDERS_DEFAULT_H),
# placeholders should be at offset 1 because of #root's border
1,
],
@@ -90,9 +89,12 @@ PLACEHOLDERS_DEFAULT_H = 3 # the default height for our Placeholder widgets
"border: solid white;", # #root has a visible border
"align: center top;", # placeholders are centered horizontally
# #root's virtual height should be as high as its stacked content
(SCREEN_W - 2 - 1, PLACEHOLDERS_DEFAULT_H * 4),
(
SCREEN_W - 2 - SCROLL_V_SIZE,
PLACEHOLDERS_DEFAULT_H * 4,
),
# placeholders width=same than screen, minus 2 borders, minus scrollbar :: height=default height
(SCREEN_W - 2 - 1, PLACEHOLDERS_DEFAULT_H),
(SCREEN_W - 2 - SCROLL_V_SIZE, PLACEHOLDERS_DEFAULT_H),
# placeholders should be at offset 1 because of #root's border
1,
],
@@ -114,7 +116,7 @@ async def test_composition_of_vertical_container_with_children(
overflow: hidden auto;
${root_container_style}
}
VerticalContainer Placeholder {
height: ${placeholders_height};
${placeholders_style}
@@ -233,7 +235,6 @@ async def test_border_edge_types_impact_on_widget_size(
assert top_left_edge_char_is_a_visible_one == expects_visible_char_at_top_left_edge
@pytest.mark.skip("brittle")
@pytest.mark.asyncio
@pytest.mark.parametrize(
"large_widget_size,container_style,expected_large_widget_visible_region_size",
@@ -246,32 +247,43 @@ async def test_border_edge_types_impact_on_widget_size(
# explicit hiding of the overflow: no scrollbars either
[Size(30, 30), "overflow: hidden", Size(20, 20)],
# scrollbar for both directions
[Size(30, 30), "overflow: auto", Size(19, 19)],
[
Size(30, 30),
"overflow: auto",
Size(
20 - SCROLL_V_SIZE,
20 - SCROLL_H_SIZE,
),
],
# horizontal scrollbar
[Size(30, 30), "overflow-x: auto", Size(20, 19)],
[Size(30, 30), "overflow-x: auto", Size(20, 20 - SCROLL_H_SIZE)],
# vertical scrollbar
[Size(30, 30), "overflow-y: auto", Size(19, 20)],
[Size(30, 30), "overflow-y: auto", Size(20 - SCROLL_V_SIZE, 20)],
# scrollbar for both directions, custom scrollbar size
[Size(30, 30), ("overflow: auto", "scrollbar-size: 3 5"), Size(15, 17)],
[Size(30, 30), ("overflow: auto", "scrollbar-size: 3 5"), Size(20 - 5, 20 - 3)],
# scrollbar for both directions, custom vertical scrollbar size
[Size(30, 30), ("overflow: auto", "scrollbar-size-vertical: 3"), Size(17, 19)],
[
Size(30, 30),
("overflow: auto", "scrollbar-size-vertical: 3"),
Size(20 - 3, 20 - SCROLL_H_SIZE),
],
# scrollbar for both directions, custom horizontal scrollbar size
[
Size(30, 30),
("overflow: auto", "scrollbar-size-horizontal: 3"),
Size(19, 17),
Size(20 - SCROLL_V_SIZE, 20 - 3),
],
# scrollbar needed only vertically, custom scrollbar size
[
Size(20, 30),
("overflow: auto", "scrollbar-size: 3 3"),
Size(17, 20),
Size(20 - 3, 20),
],
# scrollbar needed only horizontally, custom scrollbar size
[
Size(30, 20),
("overflow: auto", "scrollbar-size: 3 3"),
Size(20, 17),
Size(20, 20 - 3),
],
),
)
@@ -285,22 +297,26 @@ async def test_scrollbar_size_impact_on_the_layout(
self.styles.width = large_widget_size[0]
self.styles.height = large_widget_size[1]
container_style_rules = (
[container_style] if isinstance(container_style, str) else container_style
)
class LargeWidgetContainer(Widget):
# TODO: Once textual#581 ("Default versus User CSS") is solved the following CSS should just use the
# "LargeWidgetContainer" selector, without having to use a more specific one to be able to override Widget's CSS:
CSS = """
LargeWidgetContainer {
#large-widget-container {
width: 20;
height: 20;
${container_style};
}
""".replace(
"${container_style}",
container_style
if isinstance(container_style, str)
else ";".join(container_style),
";\n".join(container_style_rules),
)
large_widget = LargeWidget()
container = LargeWidgetContainer(large_widget)
container = LargeWidgetContainer(large_widget, id="large-widget-container")
class MyTestApp(AppTest):
def compose(self) -> ComposeResult: