mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
tweak for colors
This commit is contained in:
@@ -9,7 +9,6 @@ from .geometry import Region, Size, Spacing
|
||||
from ._layout import DockArrangeResult, WidgetPlacement
|
||||
from ._partition import partition
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .widget import Widget
|
||||
|
||||
@@ -115,7 +114,7 @@ def arrange(
|
||||
for placement in layout_placements
|
||||
]
|
||||
).size
|
||||
placement_offset += styles._align_size(placement_size, size)
|
||||
placement_offset += styles._align_size(placement_size, size).clamped
|
||||
|
||||
if placement_offset:
|
||||
layout_placements = [
|
||||
|
||||
@@ -439,13 +439,8 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""Watches the dark bool."""
|
||||
|
||||
self.screen.dark = dark
|
||||
if dark:
|
||||
self.add_class("-dark-mode")
|
||||
self.remove_class("-light-mode")
|
||||
else:
|
||||
self.remove_class("-dark-mode")
|
||||
self.add_class("-light-mode")
|
||||
|
||||
self.set_class(dark, "-dark-mode")
|
||||
self.set_class(not dark, "-light-mode")
|
||||
self.refresh_css()
|
||||
|
||||
def get_driver_class(self) -> Type[Driver]:
|
||||
@@ -1000,12 +995,13 @@ class App(Generic[ReturnType], DOMNode):
|
||||
Args:
|
||||
widget (Widget): A widget that is removed.
|
||||
"""
|
||||
for sibling in widget.siblings:
|
||||
if sibling.can_focus:
|
||||
sibling.focus()
|
||||
break
|
||||
else:
|
||||
self.focused = None
|
||||
if self.focused is widget:
|
||||
for sibling in widget.siblings:
|
||||
if sibling.can_focus:
|
||||
sibling.focus()
|
||||
break
|
||||
else:
|
||||
self.focused = None
|
||||
|
||||
async def _set_mouse_over(self, widget: Widget | None) -> None:
|
||||
"""Called when the mouse is over another widget.
|
||||
|
||||
@@ -111,3 +111,11 @@ def easing():
|
||||
from textual.cli.previews import easing
|
||||
|
||||
easing.app.run()
|
||||
|
||||
|
||||
@run.command("colors")
|
||||
def colors():
|
||||
"""Explore the design system."""
|
||||
from textual.cli.previews import colors
|
||||
|
||||
colors.app.run()
|
||||
|
||||
71
src/textual/cli/previews/colors.css
Normal file
71
src/textual/cli/previews/colors.css
Normal file
@@ -0,0 +1,71 @@
|
||||
ColorButtons {
|
||||
dock: left;
|
||||
overflow-y: auto;
|
||||
width: 30;
|
||||
}
|
||||
|
||||
ColorButtons > Button {
|
||||
width: 30;
|
||||
}
|
||||
|
||||
ColorsView {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align: center middle;
|
||||
overflow-x: auto;
|
||||
background: $background;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
ColorItem {
|
||||
layout: horizontal;
|
||||
height: 3;
|
||||
width: 1fr;
|
||||
}
|
||||
|
||||
ColorBar {
|
||||
height: auto;
|
||||
width: 1fr;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
|
||||
ColorItem {
|
||||
width: 100%;
|
||||
padding: 1 2;
|
||||
}
|
||||
|
||||
ColorGroup {
|
||||
margin: 2 0;
|
||||
width: 110;
|
||||
height: auto;
|
||||
padding: 2 4;
|
||||
background: $surface;
|
||||
border: wide $surface;
|
||||
}
|
||||
|
||||
|
||||
ColorGroup.-active {
|
||||
border: wide $secondary;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
|
||||
.disabled {
|
||||
color: $text-disabled;
|
||||
}
|
||||
|
||||
|
||||
ColorLabel {
|
||||
padding: 1 0;
|
||||
content-align: center middle;
|
||||
color: $text;
|
||||
text-style: bold;
|
||||
}
|
||||
92
src/textual/cli/previews/colors.py
Normal file
92
src/textual/cli/previews/colors.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Horizontal, Vertical
|
||||
from textual.reactive import var
|
||||
from textual.widgets import Button, Static, Footer
|
||||
|
||||
from textual.design import ColorSystem
|
||||
|
||||
|
||||
class ColorButtons(Vertical):
|
||||
def compose(self) -> ComposeResult:
|
||||
for border in ColorSystem.COLOR_NAMES:
|
||||
if border:
|
||||
yield Button(border, id=border)
|
||||
|
||||
|
||||
class ColorBar(Static):
|
||||
pass
|
||||
|
||||
|
||||
class ColorItem(Horizontal):
|
||||
pass
|
||||
|
||||
|
||||
class ColorGroup(Vertical):
|
||||
pass
|
||||
|
||||
|
||||
class Content(Vertical):
|
||||
pass
|
||||
|
||||
|
||||
class ColorLabel(Static):
|
||||
pass
|
||||
|
||||
|
||||
class ColorsView(Vertical):
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
LEVELS = [
|
||||
"darken-3",
|
||||
"darken-2",
|
||||
"darken-1",
|
||||
"",
|
||||
"lighten-1",
|
||||
"lighten-2",
|
||||
"lighten-3",
|
||||
]
|
||||
|
||||
variables = self.app.stylesheet._variables
|
||||
for color_name in ColorSystem.COLOR_NAMES:
|
||||
|
||||
items = [ColorLabel(f'"{color_name}"')]
|
||||
for level in LEVELS:
|
||||
color = f"{color_name}-{level}" if level else color_name
|
||||
item = ColorItem(
|
||||
ColorBar(f"${color}", classes="text"),
|
||||
ColorBar(f"$text", classes="text"),
|
||||
ColorBar(f"$text-muted", classes="muted"),
|
||||
ColorBar(f"$text-disabled", classes="disabled"),
|
||||
)
|
||||
item.styles.background = variables[color]
|
||||
items.append(item)
|
||||
|
||||
yield ColorGroup(*items, id=f"group-{color_name}")
|
||||
|
||||
|
||||
class ColorsApp(App):
|
||||
CSS_PATH = "colors.css"
|
||||
|
||||
BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Content(ColorButtons(), ColorsView())
|
||||
yield Footer()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
self.query(ColorGroup).remove_class("-active")
|
||||
group = self.query_one(f"#group-{event.button.id}", ColorGroup)
|
||||
group.add_class("-active")
|
||||
group.scroll_visible(speed=150)
|
||||
|
||||
def action_toggle_dark(self) -> None:
|
||||
content = self.query_one("Content", Content)
|
||||
self.dark = not self.dark
|
||||
content.mount(ColorsView())
|
||||
content.query("ColorsView").first().remove()
|
||||
|
||||
|
||||
app = ColorsApp()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -68,6 +68,16 @@ class Offset(NamedTuple):
|
||||
"""
|
||||
return self == (0, 0)
|
||||
|
||||
@property
|
||||
def clamped(self) -> Offset:
|
||||
"""Ensure x and y are above zero.
|
||||
|
||||
Returns:
|
||||
Offset: New offset.
|
||||
"""
|
||||
x, y = self
|
||||
return Offset(max(x, 0), max(y, 0))
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return self != (0, 0)
|
||||
|
||||
|
||||
@@ -80,9 +80,6 @@ class Screen(Widget):
|
||||
"""Get a list of visible widgets."""
|
||||
return list(self._compositor.visible_widgets)
|
||||
|
||||
def watch_dark(self, dark: bool) -> None:
|
||||
pass
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
background = self.styles.background
|
||||
if background.is_transparent:
|
||||
|
||||
@@ -1739,6 +1739,7 @@ class Widget(DOMNode):
|
||||
|
||||
def remove(self) -> None:
|
||||
"""Remove the Widget from the DOM (effectively deleting it)"""
|
||||
self.display = False
|
||||
self.app.post_message_no_wait(events.Remove(self, widget=self))
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
|
||||
@@ -75,6 +75,13 @@ def test_offset_is_origin():
|
||||
assert not Offset(1, 0).is_origin
|
||||
|
||||
|
||||
def test_clamped():
|
||||
assert Offset(-10, 0).clamped == Offset(0, 0)
|
||||
assert Offset(-10, -5).clamped == Offset(0, 0)
|
||||
assert Offset(5, -5).clamped == Offset(5, 0)
|
||||
assert Offset(5, 10).clamped == Offset(5, 10)
|
||||
|
||||
|
||||
def test_offset_add():
|
||||
assert Offset(1, 1) + Offset(2, 2) == Offset(3, 3)
|
||||
assert Offset(1, 2) + Offset(3, 4) == Offset(4, 6)
|
||||
|
||||
Reference in New Issue
Block a user