v0.31.1: one button look — crisper emboss, darken on hover
- The emboss hugs the edges: thin lit and shaded rims, a defined outline that's a touch darker along the bottom, and a nearly flat face — shape without depth. - Hover darkens the whole button slightly instead of Qt's pale panel on top of it. - One face for every button (theme.button_face_qss): regular buttons, Library and its ▾, the purple friend-mode buttons, and the transport boxes. Shuffle and bpm are boxes that are one button, so the whole box darkens (bpm's whole box is now its tap target); the play box's three tiles each darken on their own.
This commit is contained in:
+60
-28
@@ -166,44 +166,76 @@ def apply_theme(app, prefs):
|
||||
|
||||
|
||||
def emboss_gradient(color: QColor, pressed: bool = False) -> str:
|
||||
"""A barely-there top-lit vertical gradient around ``color``.
|
||||
|
||||
Computed from the live color rather than hard-coded, so it follows the
|
||||
"""A shallow emboss around ``color`` that hugs the edges: a crisp lit rim
|
||||
along the top and a crisp shaded rim along the bottom, with the face
|
||||
itself nearly flat. Computed from the live color, so it follows the
|
||||
palette (light or dark) and the user's grayscale overrides. ``pressed``
|
||||
flips it, which reads as the button sinking in."""
|
||||
top, bottom = color.lighter(EMBOSS_LIGHTER), color.darker(EMBOSS_DARKER)
|
||||
flips it, which reads as the button sinking in.
|
||||
|
||||
The color itself sits at the midpoint, so what the user picked is still
|
||||
exactly what the middle of the button shows."""
|
||||
rim_top, face_top = color.lighter(EMBOSS_RIM), color.lighter(EMBOSS_LIGHTER)
|
||||
face_bottom, rim_bottom = color.darker(EMBOSS_DARKER), color.darker(EMBOSS_RIM)
|
||||
if pressed:
|
||||
top, bottom = bottom, top
|
||||
# The color itself sits at the midpoint, so what the user picked is still
|
||||
# exactly what the middle of the button shows.
|
||||
rim_top, face_top, face_bottom, rim_bottom = (
|
||||
rim_bottom, face_bottom, face_top, rim_top)
|
||||
return (f"qlineargradient(x1:0, y1:0, x2:0, y2:1, "
|
||||
f"stop:0 {top.name()}, stop:0.5 {color.name()}, "
|
||||
f"stop:1 {bottom.name()})")
|
||||
f"stop:0 {rim_top.name()}, stop:{EMBOSS_RIM_WIDTH} {face_top.name()}, "
|
||||
f"stop:0.5 {color.name()}, "
|
||||
f"stop:{1 - EMBOSS_RIM_WIDTH:.2f} {face_bottom.name()}, "
|
||||
f"stop:1 {rim_bottom.name()})")
|
||||
|
||||
|
||||
# How far the emboss strays from the base color (Qt's lighter/darker factor,
|
||||
# 100 = unchanged). Kept small on purpose: "very slight, soft".
|
||||
EMBOSS_LIGHTER = 106
|
||||
EMBOSS_DARKER = 104
|
||||
# 100 = unchanged). The face barely moves; the rims are where the shape is.
|
||||
EMBOSS_LIGHTER = 103
|
||||
EMBOSS_DARKER = 103
|
||||
EMBOSS_RIM = 116
|
||||
EMBOSS_RIM_WIDTH = 0.08 # fraction of the height each rim takes
|
||||
HOVER_DARKER = 107 # hover: the whole button, slightly darker
|
||||
PRESSED_DARKER = 112
|
||||
|
||||
|
||||
def emboss_edges(color: QColor) -> str:
|
||||
"""A defined outline: a touch darker at the bottom, like a lit shape."""
|
||||
edge = color.darker(135).name()
|
||||
bottom = color.darker(155).name()
|
||||
return f"border: 1px solid {edge}; border-bottom-color: {bottom};"
|
||||
|
||||
|
||||
def button_face_qss(selector: str, color: QColor, *, radius: int = 4,
|
||||
extra: str = "", checked_color: QColor | None = None,
|
||||
checked_text: str = "") -> str:
|
||||
"""The one look every button shares: the emboss, the outline, a hover that
|
||||
darkens the whole button a little, and a pressed state that sinks in.
|
||||
``extra`` carries per-button layout (padding, margins, corners);
|
||||
``checked_color`` is for toggles like the Library button."""
|
||||
hover = color.darker(HOVER_DARKER)
|
||||
down = color.darker(PRESSED_DARKER)
|
||||
qss = (f"{selector} {{ {emboss_edges(color)} border-radius: {radius}px;"
|
||||
f" background: {emboss_gradient(color)}; {extra} }}"
|
||||
f"{selector}:hover {{ background: {emboss_gradient(hover)}; }}"
|
||||
f"{selector}:pressed {{ background: {emboss_gradient(down, pressed=True)}; }}")
|
||||
if checked_color is not None:
|
||||
lit = checked_color
|
||||
text = f" color: {checked_text};" if checked_text else ""
|
||||
qss += (f"{selector}:checked {{ background: {emboss_gradient(lit, pressed=True)};"
|
||||
f" {emboss_edges(lit)}{text} }}"
|
||||
f"{selector}:checked:hover {{ background:"
|
||||
f" {emboss_gradient(lit.darker(HOVER_DARKER), pressed=True)}; }}")
|
||||
return qss
|
||||
|
||||
|
||||
def button_emboss_qss(button: QColor) -> str:
|
||||
"""Global rule for every non-flat QPushButton. Flat buttons (the version
|
||||
button, the cancel-transfer ✕, the bpm label) opt out by being flat."""
|
||||
hover = button.lighter(103)
|
||||
return f"""
|
||||
QPushButton:!flat {{
|
||||
border: 1px solid palette(mid); border-radius: 4px;
|
||||
padding: 3px 12px; min-height: 1.3em;
|
||||
background: {emboss_gradient(button)};
|
||||
}}
|
||||
QPushButton:!flat:hover {{ background: {emboss_gradient(hover)}; }}
|
||||
QPushButton:!flat:pressed, QPushButton:!flat:checked {{
|
||||
background: {emboss_gradient(button, pressed=True)};
|
||||
}}
|
||||
QPushButton:!flat:default {{ border-color: palette(highlight); }}
|
||||
QPushButton:!flat:disabled {{ color: palette(mid); }}
|
||||
"""
|
||||
button, the cancel-transfer ✕) opt out by being flat; the transport's bpm
|
||||
button is flat inside a box that carries the look instead."""
|
||||
return button_face_qss(
|
||||
"QPushButton:!flat", button,
|
||||
extra="padding: 3px 12px; min-height: 1.3em;",
|
||||
checked_color=button.darker(PRESSED_DARKER)) + \
|
||||
"QPushButton:!flat:default { border-color: palette(highlight); }" \
|
||||
"QPushButton:!flat:disabled { color: palette(mid); }"
|
||||
|
||||
|
||||
def installed_families() -> set[str]:
|
||||
|
||||
Reference in New Issue
Block a user