修正 Studio 力曲线原点、运行不可用提示与禁用按钮样式
FN 轴视图留 8% 余量使 0 刻度与静止曲线离开边框;实时视图在轨道上直接写出运行不可用的原因;禁用按钮改为虚线无填充,复选框加勾,单选框选中不再变方,并放大勾选框、微调框箭头、滚动条与菜单行以便触屏操作。另修 ui_check 桩 worker 签名与英文回放提示。在 Linux 离屏环境对比未改动的 HEAD 验证:studio 失败集合为 HEAD 的子集,ui_check 结果一致。
This commit is contained in:
@@ -69,6 +69,51 @@ def _paint_arrow(kind: str, fill: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def _paint_check(fill: str) -> str:
|
||||
"""Draw a tick PNG for a checked checkbox; return its path ('' on failure).
|
||||
|
||||
A checked box used to be a solid square, which at 15 px reads as "disabled"
|
||||
or "indeterminate" rather than "on".
|
||||
"""
|
||||
try:
|
||||
from PySide6.QtCore import QPointF, Qt
|
||||
from PySide6.QtGui import QColor, QImage, QPainter, QPainterPath, QPen
|
||||
except Exception: # pragma: no cover - headless import failure
|
||||
return ""
|
||||
try:
|
||||
size = 36 # drawn at 2x the indicator so the tick stays crisp when scaled
|
||||
safe = fill.strip().lstrip("#").replace("/", "")
|
||||
path = _arrow_icon_cache_dir() / f"check_{safe}.png"
|
||||
if path.is_file():
|
||||
return str(path)
|
||||
image = QImage(size, size, QImage.Format.Format_ARGB32)
|
||||
image.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
||||
pen = QPen(QColor(fill), size * 0.13)
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
||||
painter.setPen(pen)
|
||||
tick = QPainterPath(QPointF(size * 0.22, size * 0.52))
|
||||
tick.lineTo(QPointF(size * 0.42, size * 0.72))
|
||||
tick.lineTo(QPointF(size * 0.78, size * 0.30))
|
||||
painter.drawPath(tick)
|
||||
painter.end()
|
||||
if not image.save(str(path), "PNG"):
|
||||
return ""
|
||||
return str(path)
|
||||
except Exception: # pragma: no cover - never break theming
|
||||
return ""
|
||||
|
||||
|
||||
def _check_image(fill: str) -> str:
|
||||
path = _paint_check(fill)
|
||||
if not path:
|
||||
return "none"
|
||||
return 'url("' + path.replace("\\", "/") + '")'
|
||||
|
||||
|
||||
def _arrow_image(kind: str, fill: str, size: int = 14) -> str:
|
||||
path = _paint_arrow(kind, fill)
|
||||
if not path:
|
||||
@@ -86,6 +131,8 @@ def build_qss(theme: str = "dark") -> str:
|
||||
arrow_up = _arrow_image("up", dim)
|
||||
arrow_down_disabled = _arrow_image("down", muted)
|
||||
arrow_up_disabled = _arrow_image("up", muted)
|
||||
check_mark = _check_image(accent_text)
|
||||
check_mark_disabled = _check_image(muted)
|
||||
return f"""
|
||||
QWidget {{
|
||||
background-color: {c['window_bg']};
|
||||
@@ -117,7 +164,7 @@ QMenu {{
|
||||
padding: 4px;
|
||||
}}
|
||||
QMenu::item {{
|
||||
padding: 6px 18px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
QMenu::item:selected {{
|
||||
@@ -219,10 +266,13 @@ QPushButton:pressed {{
|
||||
QPushButton:focus {{
|
||||
border-color: {c['focus_ring']};
|
||||
}}
|
||||
/* Disabled = no fill and a dashed outline. A filled disabled button was the
|
||||
same colour as an enabled neutral one, so a rail of unavailable actions
|
||||
read as a rail of available ones. */
|
||||
QPushButton:disabled {{
|
||||
color: {c['text_muted']};
|
||||
background-color: {c['window_bg']};
|
||||
border: 1px solid {c['separator']};
|
||||
background-color: transparent;
|
||||
border: 1px dashed {c['input_border']};
|
||||
}}
|
||||
QPushButton#primaryButton {{
|
||||
background-color: {c['accent']};
|
||||
@@ -241,9 +291,9 @@ QPushButton#primaryButton:checked {{
|
||||
border-color: {c['accent']};
|
||||
}}
|
||||
QPushButton#primaryButton:disabled {{
|
||||
background-color: {c['panel_border']};
|
||||
background-color: transparent;
|
||||
color: {c['text_muted']};
|
||||
border-color: {c['panel_border']};
|
||||
border: 1px dashed {c['input_border']};
|
||||
}}
|
||||
QPushButton#dangerButton {{
|
||||
background-color: {c['danger_fill']};
|
||||
@@ -262,9 +312,9 @@ QPushButton#dangerButton:checked {{
|
||||
border-color: {c['danger_fill']};
|
||||
}}
|
||||
QPushButton#dangerButton:disabled {{
|
||||
background-color: {c['panel_border']};
|
||||
background-color: transparent;
|
||||
color: {c['text_muted']};
|
||||
border-color: {c['panel_border']};
|
||||
border: 1px dashed {c['input_border']};
|
||||
}}
|
||||
QPushButton#ghostButton {{
|
||||
background-color: transparent;
|
||||
@@ -310,8 +360,8 @@ QPushButton#dangerOutline:checked {{
|
||||
}}
|
||||
QPushButton#dangerOutline:disabled {{
|
||||
color: {c['text_muted']};
|
||||
background-color: {c['window_bg']};
|
||||
border: 1px solid {c['separator']};
|
||||
background-color: transparent;
|
||||
border: 1px dashed {c['input_border']};
|
||||
}}
|
||||
QPushButton[compact="true"] {{
|
||||
padding: 6px 9px;
|
||||
@@ -428,9 +478,12 @@ QComboBox QAbstractItemView {{
|
||||
selection-background-color: {c['menu_hover']};
|
||||
selection-color: {c['text_primary']};
|
||||
}}
|
||||
QComboBox QAbstractItemView::item {{
|
||||
min-height: 30px;
|
||||
}}
|
||||
QAbstractSpinBox::up-button, QAbstractSpinBox::down-button {{
|
||||
subcontrol-origin: border;
|
||||
width: 18px;
|
||||
width: 22px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}}
|
||||
@@ -457,15 +510,17 @@ QAbstractSpinBox::down-arrow:disabled {{
|
||||
image: {arrow_down_disabled};
|
||||
}}
|
||||
/* ---------- Check / radio ---------- */
|
||||
/* 18 px boxes and a taller row: the label is part of the hit area, and the
|
||||
whole row has to be easy to hit with a finger. */
|
||||
QCheckBox, QRadioButton {{
|
||||
background: transparent;
|
||||
color: {c['text_primary']};
|
||||
spacing: 7px;
|
||||
padding: 2px 0;
|
||||
spacing: 8px;
|
||||
padding: 4px 0;
|
||||
}}
|
||||
QCheckBox::indicator, QRadioButton::indicator {{
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: {c['input_bg']};
|
||||
border: 1px solid {c['input_border']};
|
||||
}}
|
||||
@@ -473,23 +528,34 @@ QCheckBox::indicator {{
|
||||
border-radius: 4px;
|
||||
}}
|
||||
QRadioButton::indicator {{
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
QCheckBox::indicator:hover, QRadioButton::indicator:hover {{
|
||||
border-color: {c['accent_hover']};
|
||||
}}
|
||||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
|
||||
QCheckBox::indicator:checked {{
|
||||
background-color: {c['accent']};
|
||||
border: 4px solid {c['accent']};
|
||||
border: 1px solid {c['accent']};
|
||||
image: {check_mark};
|
||||
}}
|
||||
/* QSS borders add to ``width``: a 5 px ring on an 18 px box grew the checked
|
||||
radio to 28 px and turned the circle into a rounded square. Shrinking the
|
||||
content keeps the outer size (20 px) equal to the unchecked one. */
|
||||
QRadioButton::indicator:checked {{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: {c['input_bg']};
|
||||
border: 4px solid {c['accent']};
|
||||
border: 5px solid {c['accent']};
|
||||
}}
|
||||
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled {{
|
||||
background-color: {c['window_bg']};
|
||||
border-color: {c['separator']};
|
||||
}}
|
||||
QCheckBox::indicator:checked:disabled {{
|
||||
background-color: {c['separator']};
|
||||
border-color: {c['separator']};
|
||||
image: {check_mark_disabled};
|
||||
}}
|
||||
/* ---------- Progress ---------- */
|
||||
QProgressBar {{
|
||||
background-color: {c['input_bg']};
|
||||
@@ -549,26 +615,26 @@ QTableCornerButton::section {{
|
||||
/* ---------- Scrollbars ---------- */
|
||||
QScrollBar:vertical {{
|
||||
background: {c['scrollbar_bg']};
|
||||
width: 10px;
|
||||
width: 12px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}}
|
||||
QScrollBar:horizontal {{
|
||||
background: {c['scrollbar_bg']};
|
||||
height: 10px;
|
||||
height: 12px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}}
|
||||
QScrollBar::handle:vertical {{
|
||||
background: {c['scrollbar_handle']};
|
||||
min-height: 28px;
|
||||
border-radius: 5px;
|
||||
min-height: 32px;
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}}
|
||||
QScrollBar::handle:horizontal {{
|
||||
background: {c['scrollbar_handle']};
|
||||
min-width: 28px;
|
||||
border-radius: 5px;
|
||||
min-width: 32px;
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}}
|
||||
QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover {{
|
||||
@@ -707,3 +773,4 @@ def color(theme: str, key: str, default: str = "#888888") -> str:
|
||||
def clear_theme_cache() -> None:
|
||||
load_colors.cache_clear()
|
||||
_paint_arrow.cache_clear()
|
||||
_paint_check.cache_clear()
|
||||
|
||||
Reference in New Issue
Block a user