mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
screenshot
This commit is contained in:
8
sandbox/darren/color_animate.css
Normal file
8
sandbox/darren/color_animate.css
Normal file
@@ -0,0 +1,8 @@
|
||||
* {
|
||||
transition: color 300ms linear, background 300ms linear;
|
||||
}
|
||||
|
||||
#another-box {
|
||||
background: $boost;
|
||||
padding: 1 2;
|
||||
}
|
||||
28
sandbox/darren/color_animate.py
Normal file
28
sandbox/darren/color_animate.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.color import Color
|
||||
from textual.widgets import Static
|
||||
|
||||
START_COLOR = Color.parse("#FF0000EE")
|
||||
END_COLOR = Color.parse("#0000FF0F")
|
||||
|
||||
|
||||
class ColorAnimate(App):
|
||||
BINDINGS = [Binding("d", action="toggle_dark", description="Dark mode")]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
self.box = Static("Hello, world", id="box")
|
||||
self.box.styles.background = START_COLOR
|
||||
|
||||
self.another_box = Static("Another box with $boost", id="another-box")
|
||||
|
||||
yield self.box
|
||||
yield self.another_box
|
||||
|
||||
def key_a(self):
|
||||
self.animator.animate(self.box.styles, "background", END_COLOR, duration=2.0)
|
||||
|
||||
|
||||
app = ColorAnimate(css_path="color_animate.css")
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -2,19 +2,21 @@ from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Static, Footer, Header
|
||||
|
||||
|
||||
class JustABox(App):
|
||||
class MainScreen(Screen):
|
||||
|
||||
BINDINGS = [
|
||||
Binding(
|
||||
key="ctrl+t", action="text_fade_out", description="text-opacity fade out"
|
||||
),
|
||||
Binding(
|
||||
key="o,f,w",
|
||||
action="widget_fade_out",
|
||||
description="opacity fade out",
|
||||
key_display="o or f or w",
|
||||
(
|
||||
"o,f,w",
|
||||
"widget_fade_out",
|
||||
"opacity fade out",
|
||||
# key_display="o or f or w",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -25,11 +27,16 @@ class JustABox(App):
|
||||
|
||||
def action_text_fade_out(self) -> None:
|
||||
box = self.query_one("#box1")
|
||||
self.animator.animate(box.styles, "text_opacity", value=0.0, duration=1)
|
||||
self.app.animator.animate(box.styles, "text_opacity", value=0.0, duration=1)
|
||||
|
||||
def action_widget_fade_out(self) -> None:
|
||||
box = self.query_one("#box1")
|
||||
self.animator.animate(box.styles, "opacity", value=0.0, duration=1)
|
||||
self.app.animator.animate(box.styles, "opacity", value=0.0, duration=1)
|
||||
|
||||
|
||||
class JustABox(App):
|
||||
def on_mount(self):
|
||||
self.push_screen(MainScreen())
|
||||
|
||||
def key_d(self):
|
||||
print(self.screen.styles.get_rules())
|
||||
|
||||
@@ -480,7 +480,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""Action to toggle dark mode."""
|
||||
self.dark = not self.dark
|
||||
|
||||
def action_screenshot(self, filename: str | None, path: str = "~/") -> None:
|
||||
def action_screenshot(self, filename: str | None = None, path: str = "./") -> None:
|
||||
"""Save an SVG "screenshot". This action will save an SVG file containing the current contents of the screen.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
* {
|
||||
transition: color 300ms linear, background 300ms linear;
|
||||
}
|
||||
|
||||
ColorButtons {
|
||||
dock: left;
|
||||
overflow-y: auto;
|
||||
width: 30;
|
||||
}
|
||||
|
||||
ColorButtons > Button {
|
||||
ColorButtons > Button {
|
||||
width: 30;
|
||||
}
|
||||
|
||||
@@ -18,7 +22,7 @@ ColorsView {
|
||||
}
|
||||
|
||||
ColorItem {
|
||||
layout: horizontal;
|
||||
layout: horizontal;
|
||||
height: 3;
|
||||
width: 1fr;
|
||||
}
|
||||
@@ -36,15 +40,15 @@ ColorBar.label {
|
||||
|
||||
ColorItem {
|
||||
width: 100%;
|
||||
padding: 1 2;
|
||||
padding: 1 2;
|
||||
}
|
||||
|
||||
ColorGroup {
|
||||
margin: 2 0;
|
||||
width: 80;
|
||||
width: 80;
|
||||
height: auto;
|
||||
padding: 1 4 2 4;
|
||||
background: $surface;
|
||||
background: $surface;
|
||||
border: wide $surface;
|
||||
}
|
||||
|
||||
@@ -73,3 +77,256 @@ ColorLabel {
|
||||
color: $text;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
.primary-darken-3 {
|
||||
background: $primary-darken-3;
|
||||
}
|
||||
.primary-darken-2 {
|
||||
background: $primary-darken-2;
|
||||
}
|
||||
.primary-darken-1 {
|
||||
background: $primary-darken-1;
|
||||
}
|
||||
.primary {
|
||||
background: $primary;
|
||||
}
|
||||
.primary-lighten-1 {
|
||||
background: $primary-lighten-1;
|
||||
}
|
||||
.primary-lighten-2 {
|
||||
background: $primary-lighten-2;
|
||||
}
|
||||
.primary-lighten-3 {
|
||||
background: $primary-lighten-3;
|
||||
}
|
||||
.secondary-darken-3 {
|
||||
background: $secondary-darken-3;
|
||||
}
|
||||
.secondary-darken-2 {
|
||||
background: $secondary-darken-2;
|
||||
}
|
||||
.secondary-darken-1 {
|
||||
background: $secondary-darken-1;
|
||||
}
|
||||
.secondary {
|
||||
background: $secondary;
|
||||
}
|
||||
.secondary-lighten-1 {
|
||||
background: $secondary-lighten-1;
|
||||
}
|
||||
.secondary-lighten-2 {
|
||||
background: $secondary-lighten-2;
|
||||
}
|
||||
.secondary-lighten-3 {
|
||||
background: $secondary-lighten-3;
|
||||
}
|
||||
.background-darken-3 {
|
||||
background: $background-darken-3;
|
||||
}
|
||||
.background-darken-2 {
|
||||
background: $background-darken-2;
|
||||
}
|
||||
.background-darken-1 {
|
||||
background: $background-darken-1;
|
||||
}
|
||||
.background {
|
||||
background: $background;
|
||||
}
|
||||
.background-lighten-1 {
|
||||
background: $background-lighten-1;
|
||||
}
|
||||
.background-lighten-2 {
|
||||
background: $background-lighten-2;
|
||||
}
|
||||
.background-lighten-3 {
|
||||
background: $background-lighten-3;
|
||||
}
|
||||
.primary-background-darken-3 {
|
||||
background: $primary-background-darken-3;
|
||||
}
|
||||
.primary-background-darken-2 {
|
||||
background: $primary-background-darken-2;
|
||||
}
|
||||
.primary-background-darken-1 {
|
||||
background: $primary-background-darken-1;
|
||||
}
|
||||
.primary-background {
|
||||
background: $primary-background;
|
||||
}
|
||||
.primary-background-lighten-1 {
|
||||
background: $primary-background-lighten-1;
|
||||
}
|
||||
.primary-background-lighten-2 {
|
||||
background: $primary-background-lighten-2;
|
||||
}
|
||||
.primary-background-lighten-3 {
|
||||
background: $primary-background-lighten-3;
|
||||
}
|
||||
.secondary-background-darken-3 {
|
||||
background: $secondary-background-darken-3;
|
||||
}
|
||||
.secondary-background-darken-2 {
|
||||
background: $secondary-background-darken-2;
|
||||
}
|
||||
.secondary-background-darken-1 {
|
||||
background: $secondary-background-darken-1;
|
||||
}
|
||||
.secondary-background {
|
||||
background: $secondary-background;
|
||||
}
|
||||
.secondary-background-lighten-1 {
|
||||
background: $secondary-background-lighten-1;
|
||||
}
|
||||
.secondary-background-lighten-2 {
|
||||
background: $secondary-background-lighten-2;
|
||||
}
|
||||
.secondary-background-lighten-3 {
|
||||
background: $secondary-background-lighten-3;
|
||||
}
|
||||
.surface-darken-3 {
|
||||
background: $surface-darken-3;
|
||||
}
|
||||
.surface-darken-2 {
|
||||
background: $surface-darken-2;
|
||||
}
|
||||
.surface-darken-1 {
|
||||
background: $surface-darken-1;
|
||||
}
|
||||
.surface {
|
||||
background: $surface;
|
||||
}
|
||||
.surface-lighten-1 {
|
||||
background: $surface-lighten-1;
|
||||
}
|
||||
.surface-lighten-2 {
|
||||
background: $surface-lighten-2;
|
||||
}
|
||||
.surface-lighten-3 {
|
||||
background: $surface-lighten-3;
|
||||
}
|
||||
.panel-darken-3 {
|
||||
background: $panel-darken-3;
|
||||
}
|
||||
.panel-darken-2 {
|
||||
background: $panel-darken-2;
|
||||
}
|
||||
.panel-darken-1 {
|
||||
background: $panel-darken-1;
|
||||
}
|
||||
.panel {
|
||||
background: $panel;
|
||||
}
|
||||
.panel-lighten-1 {
|
||||
background: $panel-lighten-1;
|
||||
}
|
||||
.panel-lighten-2 {
|
||||
background: $panel-lighten-2;
|
||||
}
|
||||
.panel-lighten-3 {
|
||||
background: $panel-lighten-3;
|
||||
}
|
||||
.boost-darken-3 {
|
||||
background: $boost-darken-3;
|
||||
}
|
||||
.boost-darken-2 {
|
||||
background: $boost-darken-2;
|
||||
}
|
||||
.boost-darken-1 {
|
||||
background: $boost-darken-1;
|
||||
}
|
||||
.boost {
|
||||
background: $boost;
|
||||
}
|
||||
.boost-lighten-1 {
|
||||
background: $boost-lighten-1;
|
||||
}
|
||||
.boost-lighten-2 {
|
||||
background: $boost-lighten-2;
|
||||
}
|
||||
.boost-lighten-3 {
|
||||
background: $boost-lighten-3;
|
||||
}
|
||||
.warning-darken-3 {
|
||||
background: $warning-darken-3;
|
||||
}
|
||||
.warning-darken-2 {
|
||||
background: $warning-darken-2;
|
||||
}
|
||||
.warning-darken-1 {
|
||||
background: $warning-darken-1;
|
||||
}
|
||||
.warning {
|
||||
background: $warning;
|
||||
}
|
||||
.warning-lighten-1 {
|
||||
background: $warning-lighten-1;
|
||||
}
|
||||
.warning-lighten-2 {
|
||||
background: $warning-lighten-2;
|
||||
}
|
||||
.warning-lighten-3 {
|
||||
background: $warning-lighten-3;
|
||||
}
|
||||
.error-darken-3 {
|
||||
background: $error-darken-3;
|
||||
}
|
||||
.error-darken-2 {
|
||||
background: $error-darken-2;
|
||||
}
|
||||
.error-darken-1 {
|
||||
background: $error-darken-1;
|
||||
}
|
||||
.error {
|
||||
background: $error;
|
||||
}
|
||||
.error-lighten-1 {
|
||||
background: $error-lighten-1;
|
||||
}
|
||||
.error-lighten-2 {
|
||||
background: $error-lighten-2;
|
||||
}
|
||||
.error-lighten-3 {
|
||||
background: $error-lighten-3;
|
||||
}
|
||||
.success-darken-3 {
|
||||
background: $success-darken-3;
|
||||
}
|
||||
.success-darken-2 {
|
||||
background: $success-darken-2;
|
||||
}
|
||||
.success-darken-1 {
|
||||
background: $success-darken-1;
|
||||
}
|
||||
.success {
|
||||
background: $success;
|
||||
}
|
||||
.success-lighten-1 {
|
||||
background: $success-lighten-1;
|
||||
}
|
||||
.success-lighten-2 {
|
||||
background: $success-lighten-2;
|
||||
}
|
||||
.success-lighten-3 {
|
||||
background: $success-lighten-3;
|
||||
}
|
||||
.accent-darken-3 {
|
||||
background: $accent-darken-3;
|
||||
}
|
||||
.accent-darken-2 {
|
||||
background: $accent-darken-2;
|
||||
}
|
||||
.accent-darken-1 {
|
||||
background: $accent-darken-1;
|
||||
}
|
||||
.accent {
|
||||
background: $accent;
|
||||
}
|
||||
.accent-lighten-1 {
|
||||
background: $accent-lighten-1;
|
||||
}
|
||||
.accent-lighten-2 {
|
||||
background: $accent-lighten-2;
|
||||
}
|
||||
.accent-lighten-3 {
|
||||
background: $accent-lighten-3;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ class ColorsView(Vertical):
|
||||
"lighten-3",
|
||||
]
|
||||
|
||||
variables = self.app.stylesheet._variables
|
||||
for color_name in ColorSystem.COLOR_NAMES:
|
||||
|
||||
items: list[Widget] = [ColorLabel(f'"{color_name}"')]
|
||||
@@ -55,8 +54,8 @@ class ColorsView(Vertical):
|
||||
ColorBar(f"${color}", classes="text label"),
|
||||
ColorBar(f"$text-muted", classes="muted"),
|
||||
ColorBar(f"$text-disabled", classes="disabled"),
|
||||
classes=color,
|
||||
)
|
||||
item.styles.background = variables[color]
|
||||
items.append(item)
|
||||
|
||||
yield ColorGroup(*items, id=f"group-{color_name}")
|
||||
@@ -84,12 +83,6 @@ class ColorsApp(App):
|
||||
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()
|
||||
|
||||
|
||||
@@ -349,18 +349,28 @@ class Color(NamedTuple):
|
||||
Args:
|
||||
destination (Color): Another color.
|
||||
factor (float): A blend factor, 0 -> 1.
|
||||
alpha (float | None): New alpha for result. Defaults to 1.
|
||||
alpha (float | None): New alpha for result. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Color: A new color.
|
||||
"""
|
||||
if factor == 0:
|
||||
return self
|
||||
elif factor == 1:
|
||||
return destination
|
||||
r1, g1, b1, a1 = self
|
||||
r2, g2, b2, a2 = destination
|
||||
|
||||
if alpha is None:
|
||||
new_alpha = a1 + (a2 - a1) * factor
|
||||
else:
|
||||
new_alpha = alpha
|
||||
|
||||
return Color(
|
||||
int(r1 + (r2 - r1) * factor),
|
||||
int(g1 + (g2 - g1) * factor),
|
||||
int(b1 + (b2 - b1) * factor),
|
||||
a1 + (a2 - a1) * factor if alpha is None else alpha,
|
||||
new_alpha,
|
||||
)
|
||||
|
||||
def __add__(self, other: object) -> Color:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
* {
|
||||
transition: background 250ms linear, color 250ms linear;
|
||||
}
|
||||
|
||||
Screen {
|
||||
layers: base overlay notes;
|
||||
@@ -30,6 +33,7 @@ Sidebar Title {
|
||||
|
||||
OptionGroup {
|
||||
background: $boost;
|
||||
color: $text;
|
||||
height: 1fr;
|
||||
border-right: vkey $background;
|
||||
}
|
||||
@@ -179,7 +183,7 @@ LocationLink:hover {
|
||||
}
|
||||
|
||||
DataTable {
|
||||
height: 10;
|
||||
height: 16;
|
||||
}
|
||||
|
||||
LoginForm {
|
||||
@@ -216,6 +220,7 @@ TreeControl {
|
||||
|
||||
|
||||
Window {
|
||||
background: $boost;
|
||||
overflow: auto;
|
||||
height: auto;
|
||||
max-height: 16;
|
||||
|
||||
@@ -93,6 +93,7 @@ Here's an example of some CSS used in this app:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
EXAMPLE_CSS = """\
|
||||
Screen {
|
||||
layers: base overlay notes;
|
||||
@@ -286,8 +287,9 @@ class DemoApp(App):
|
||||
CSS_PATH = "demo.css"
|
||||
TITLE = "Textual Demo"
|
||||
BINDINGS = [
|
||||
("ctrl+s", "app.toggle_class('Sidebar', '-hidden')", "Sidebar"),
|
||||
("ctrl+b", "app.toggle_class('Sidebar', '-hidden')", "Sidebar"),
|
||||
("ctrl+t", "app.toggle_dark", "Toggle Dark mode"),
|
||||
("ctrl+s", "app.screenshot()", "Screenshot"),
|
||||
("f1", "app.toggle_class('TextLog', '-hidden')", "Notes"),
|
||||
Binding("ctrl+c,ctrl+q", "app.quit", "Quit", show=True),
|
||||
]
|
||||
@@ -300,12 +302,12 @@ class DemoApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Container(
|
||||
Sidebar(classes="-hidden"),
|
||||
Header(),
|
||||
Header(show_clock=True),
|
||||
TextLog(classes="-hidden", wrap=False, highlight=True, markup=True),
|
||||
Body(
|
||||
QuickAccess(
|
||||
LocationLink("TOP", ".location-top"),
|
||||
LocationLink("Rich", ".location-rich"),
|
||||
LocationLink("Rich content", ".location-rich"),
|
||||
LocationLink("CSS", ".location-css"),
|
||||
LocationLink("Widgets", ".location-widgets"),
|
||||
),
|
||||
@@ -319,7 +321,7 @@ class DemoApp(App):
|
||||
SubTitle("Tables"),
|
||||
Static(example_table, classes="table pad"),
|
||||
SubTitle("JSON"),
|
||||
Window(Static(JSON(JSON_EXAMPLE), expand=True, classes="pad")),
|
||||
Window(Static(JSON(JSON_EXAMPLE), expand=True), classes="pad"),
|
||||
),
|
||||
classes="location-rich location-first",
|
||||
),
|
||||
@@ -366,6 +368,18 @@ class DemoApp(App):
|
||||
table.zebra_stripes = True
|
||||
for n in range(20):
|
||||
table.add_row(*[f"Cell ([b]{n}[/b], {col})" for col in range(6)])
|
||||
self.query_one("Welcome Button", Button).focus()
|
||||
|
||||
def action_screenshot(self, filename: str | None = None, path: str = "./") -> None:
|
||||
"""Save an SVG "screenshot". This action will save an SVG file containing the current contents of the screen.
|
||||
|
||||
Args:
|
||||
filename (str | None, optional): Filename of screenshot, or None to auto-generate. Defaults to None.
|
||||
path (str, optional): Path to directory. Defaults to "~/".
|
||||
"""
|
||||
self.bell()
|
||||
path = self.save_screenshot(filename, path)
|
||||
self.add_note(f"Screenshot saved to {path!r}")
|
||||
|
||||
|
||||
app = DemoApp()
|
||||
|
||||
Reference in New Issue
Block a user