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 ._layout import DockArrangeResult, WidgetPlacement
|
||||||
from ._partition import partition
|
from ._partition import partition
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ def arrange(
|
|||||||
for placement in layout_placements
|
for placement in layout_placements
|
||||||
]
|
]
|
||||||
).size
|
).size
|
||||||
placement_offset += styles._align_size(placement_size, size)
|
placement_offset += styles._align_size(placement_size, size).clamped
|
||||||
|
|
||||||
if placement_offset:
|
if placement_offset:
|
||||||
layout_placements = [
|
layout_placements = [
|
||||||
|
|||||||
@@ -439,13 +439,8 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
"""Watches the dark bool."""
|
"""Watches the dark bool."""
|
||||||
|
|
||||||
self.screen.dark = dark
|
self.screen.dark = dark
|
||||||
if dark:
|
self.set_class(dark, "-dark-mode")
|
||||||
self.add_class("-dark-mode")
|
self.set_class(not dark, "-light-mode")
|
||||||
self.remove_class("-light-mode")
|
|
||||||
else:
|
|
||||||
self.remove_class("-dark-mode")
|
|
||||||
self.add_class("-light-mode")
|
|
||||||
|
|
||||||
self.refresh_css()
|
self.refresh_css()
|
||||||
|
|
||||||
def get_driver_class(self) -> Type[Driver]:
|
def get_driver_class(self) -> Type[Driver]:
|
||||||
@@ -1000,12 +995,13 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
Args:
|
Args:
|
||||||
widget (Widget): A widget that is removed.
|
widget (Widget): A widget that is removed.
|
||||||
"""
|
"""
|
||||||
for sibling in widget.siblings:
|
if self.focused is widget:
|
||||||
if sibling.can_focus:
|
for sibling in widget.siblings:
|
||||||
sibling.focus()
|
if sibling.can_focus:
|
||||||
break
|
sibling.focus()
|
||||||
else:
|
break
|
||||||
self.focused = None
|
else:
|
||||||
|
self.focused = None
|
||||||
|
|
||||||
async def _set_mouse_over(self, widget: Widget | None) -> None:
|
async def _set_mouse_over(self, widget: Widget | None) -> None:
|
||||||
"""Called when the mouse is over another widget.
|
"""Called when the mouse is over another widget.
|
||||||
|
|||||||
@@ -111,3 +111,11 @@ def easing():
|
|||||||
from textual.cli.previews import easing
|
from textual.cli.previews import easing
|
||||||
|
|
||||||
easing.app.run()
|
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)
|
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:
|
def __bool__(self) -> bool:
|
||||||
return self != (0, 0)
|
return self != (0, 0)
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ class Screen(Widget):
|
|||||||
"""Get a list of visible widgets."""
|
"""Get a list of visible widgets."""
|
||||||
return list(self._compositor.visible_widgets)
|
return list(self._compositor.visible_widgets)
|
||||||
|
|
||||||
def watch_dark(self, dark: bool) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
def render(self) -> RenderableType:
|
||||||
background = self.styles.background
|
background = self.styles.background
|
||||||
if background.is_transparent:
|
if background.is_transparent:
|
||||||
|
|||||||
@@ -1739,6 +1739,7 @@ class Widget(DOMNode):
|
|||||||
|
|
||||||
def remove(self) -> None:
|
def remove(self) -> None:
|
||||||
"""Remove the Widget from the DOM (effectively deleting it)"""
|
"""Remove the Widget from the DOM (effectively deleting it)"""
|
||||||
|
self.display = False
|
||||||
self.app.post_message_no_wait(events.Remove(self, widget=self))
|
self.app.post_message_no_wait(events.Remove(self, widget=self))
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
def render(self) -> RenderableType:
|
||||||
|
|||||||
@@ -75,6 +75,13 @@ def test_offset_is_origin():
|
|||||||
assert not Offset(1, 0).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():
|
def test_offset_add():
|
||||||
assert Offset(1, 1) + Offset(2, 2) == Offset(3, 3)
|
assert Offset(1, 1) + Offset(2, 2) == Offset(3, 3)
|
||||||
assert Offset(1, 2) + Offset(3, 4) == Offset(4, 6)
|
assert Offset(1, 2) + Offset(3, 4) == Offset(4, 6)
|
||||||
|
|||||||
Reference in New Issue
Block a user