Community程式設計與開發github.com

mawwki/pyqt6-macos-ui

建置、重新設計或重構 PyQt6/PySide6 桌面 GUI 應用程式,使其看起來像精緻的 macOS 應用程式(無框視窗、交通燈號、側邊欄、帶有柔和陰影的卡片、動畫微互動、淺色/深色主題)。

pyqt6-macos-ui 是什麼?

這項技能旨在指導 AI 代理創建或改造 PyQt6/PySide6 桌面應用程式,使其視覺效果與行為模式貼近原生 macOS 應用程式。它不依賴 macOS 的原生 API,而是透過 frameless 視窗搭配自訂 QSS 樣式表和 QPropertyAnimation 動畫,在 Windows、Linux 和 macOS 上重現一致的 macOS 美學。技能的詳細說明文件 (SKILL.md) 包含一個完整的參考實作 (app_template.py) 規格,從設計代號 (Theme)、色彩、圓角、陰影,到自訂小元件(如 TrafficLight、ToggleSwitch、MacSlider)的幾何、時序和互動邏輯,都進行了精確定義。此外,它還特別指出了在 Windows 上開發 PyQt6 時常見的崩潰陷阱,例如直接使用 `Qt.Edge` 的型別錯誤。代理在撰寫任何 PyQt6/PySide6 UI 程式碼之前查閱此技能,可以確保結果看起來不像預設的 Qt 應用程式,而是具有精緻的 macOS 風格介面。

相容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/mawwki/pyqt6-ui-skills/tree/HEAD/pyqt6-macos-ui

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

PyQt6 在 macOS 風格 — 參考應用程式規格

以下是完整的規格,可依行為逐位元組重現 assets/app_template.py。模擬一位來自專注於打造 macOS 風格桌面應用程式工作室的介面設計師代理:內斂的調色盤、一個強調色、柔和的光線、動畫僅在解釋發生了什麼時出現 — 僅此而已。

主要規則:Qt 中的「macOS 風格」並非原生的 API,而是手動重現的美學: frameless 視窗 + 自訂 QSS + QPropertyAnimation 以實現流暢性。這樣應用程式在 Windows、Linux 和 macOS 上看起來都一樣。原生 Qt 風格無法提供圓角、半透明和動畫。

參考實作是 assets/app_template.py(單一檔案)。代理的任務:要麼複製並改編它,要麼根據此文件從頭重建。


1. 快速開始

pip install PyQt6
python app_template.py

為使用者建置:pyinstaller --windowed --icon=app.icns app_template.py。

應用程式啟動方式如下:

def main() -> None:
    app = QApplication(sys.argv)
    app.setStyle("Fusion")                       # 唯一尊重全域 QSS 的風格
    app.setFont(system_font(10, QFont.Weight.Normal))
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

2. app_template.py 檔案結構(為可讀性,順序重要)

  1. 導入:sys、time、dataclasses、pathlib.Path;QtCore(Qt、QEvent、 QPropertyAnimation、QEasingCurve、QRect( F)、QPointF、QParallelAnimationGroup、 QVariantAnimation、QTimer、pyqtProperty、pyqtSignal);QtGui(QColor、QFont、 QPainter、QFontDatabase、QPen、QPixmap、QIcon、QPalette);QtSvg(QSvgRenderer); QtWidgets(所有需要的 widget)。
  2. 設計代號:Theme (dataclass)、LIGHT/DARK、半徑/時長常數、 REPLIES、SVG_EYE/SVG_EYE_SLASH、PAGES。
  3. 輔助函式:system_font、rgba、accent_fg、shadow_color。
  4. 自訂 widget(依賴順序):HoverButton、ToggleSwitch、MacSlider、 SegmentedControl、FileDropArea、TrafficLight、SidebarToggle、TitleBar、Sidebar。
  5. build_stylesheet(theme)。
  6. MainWindow + main()。

3. 設計代號(精確值)

Theme 是 frozen dataclass,包含欄位:bg、surface、sidebar、text、text_secondary、 accent、separator_rgb(字串 #000000/#FFFFFF — 供 rgba() 使用的基本色)、separator_alpha (0..255)、shadow_alpha (0..255)。

LIGHT = Theme(bg="#F5F5F7", surface="#FFFFFF", sidebar="#ECECEF", text="#1D1D1F",
              text_secondary="#6E6E73", accent="#1D1D1F",
              separator_rgb="#000000", separator_alpha=20, shadow_alpha=55)
DARK  = Theme(bg="#1E1E1E", surface="#2A2A2C", sidebar="#242426", text="#F5F5F7",
              text_secondary="#98989D", accent="#CFCFD4",
              separator_rgb="#FFFFFF", separator_alpha=20, shadow_alpha=150)

重要:此應用程式的強調色並非藍色。它是「反向」的 — 淺色主題中為黑色 #1D1D1F,深色主題中為淺色 #CFCFD4(這提供了與系統藍色相同的對比度,但採用 黑白色調)。任何將文字放置在強調色背景上的程式碼,都必須使用 accent_fg(theme),而不是硬編碼。

def accent_fg(theme: Theme) -> str:
    color = QColor(theme.accent)
    luminance = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255
    return "#1D1D1F" if luminance > 0.6 else "#FFFFFF"

def rgba(hex_color: str, alpha_0_255: int) -> str:
    c = QColor(hex_color)
    return f"rgba({c.red()}, {c.green()}, {c.blue()}, {alpha_0_255})"

常數:

  • RADIUS_WINDOW = 14、RADIUS_CARD = 12、RADIUS_CONTROL = 8、SIDEBAR_WIDTH = 196。
  • EDGE_MARGIN = 6、邊緣位元遮罩 EDGE_TOP, EDGE_BOTTOM, EDGE_LEFT, EDGE_RIGHT = 1, 2, 4, 8。
  • DUR_FAST = 140(hover/press)、DUR_MED = 250(頁面、側邊欄、開關、區段)、 EASE = QEasingCurve.Type.OutCubic。
  • PAGES = ["總覽", "任務", "輸入", "聊天", "檔案", "設定"]。
  • REPLIES — 5 個簡短的機器人回覆。
  • SVG_EYE (Heroicons, viewBox 0 0 24 24, fill="currentColor") 和 SVG_EYE_SLASH (Bootstrap, viewBox 0 0 16 16) — 多行字串。

字型:system_font() 中的備用鏈 — Inter → Segoe UI Variable Text → .AppleSystemUIFont → SF Pro Text → Helvetica Neue → Manrope → Segoe UI; 加上 setStyleStrategy(QFont.StyleStrategy.PreferAntialias)。

間距來自 4/8/12/16/20/24/32 刻度。柔和陰影:卡片 — blurRadius 24、 offset (0,5)、顏色 shadow_color(theme)(帶有 shadow_alpha 的黑色)。


4. 視窗架構

self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setMinimumSize(760, 480)
self.resize(960, 600)
self.setMouseTracking(True)
QApplication.instance().installEventFilter(self)   # 用於邊緣調整大小

樹狀結構 (_build_ui):QMainWindow → root (QWidget) → panel (QFrame, objectName "Panel") → TitleBar (40px) + body (QHBoxLayout) → Sidebar (196px) + separator (1px) + QStackedWidget。 面板套用了 build_stylesheet(theme) — QSS 向下繼承。

陰影和圓角位於子元件 QFrame#Panel 上,而不是視窗本身:視窗是透明的 並為陰影留出空間。這很關鍵 — 不能將陰影效果直接應用在 QMainWindow 上。

拖曳與調整大小

移動視窗 — Qt6 的原生機制,無需手動計算偏移量:

def mousePressEvent(self, event):   # 在 TitleBar 中
    if event.button() == Qt.MouseButton.LeftButton:
        handle = self._window.windowHandle()
        if handle is not None:
            handle.startSystemMove()

在邊緣更改大小 — 同樣是原生的 QWindow.startSystemResize。在 MainWindow 中 設定了針對 QApplication 的事件過濾器,它會:

  • 在距離視窗邊緣 ≤ EDGE_MARGIN 的區域內發生 MouseButtonPress (Left) 時,呼叫 _start_system_resize(edges) 並返回 True(事件被消耗);
  • 在沒有按鍵的 MouseMove 時更新視窗遊標;
  • 在 Leave 時重置遊標。

_resize_edges_for(pos) 將全域位置轉換為邊緣位元遮罩;僅在視窗可見且未最大化時才起作用。_cursor_for_edges(edges):中心 → Arrow;左/右 → SizeHor;上/下 → SizeVer;上+左 或 下+右 → SizeFDiag;否則 → SizeBDiag。

PyQt6 陷阱: Qt.Edge 不能轉換為 int,也不能使用 | 運算子與 int 混合使用 (TypeError)。因此,所有邊緣數學都使用自訂的 EDGE_* 位元常數,而提供給 startSystemResize 的標誌按以下方式組合:

flag = Qt.Edge(0)
if edges & EDGE_TOP:    flag |= Qt.Edge.TopEdge
if edges & EDGE_BOTTOM: flag |= Qt.Edge.BottomEdge
if edges & EDGE_LEFT:   flag |= Qt.Edge.LeftEdge
if edges & EDGE_RIGHT:  flag |= Qt.Edge.RightEdge
handle.startSystemResize(flag)

5. 自訂 Widget(精確參數)

HoverButton(具有平滑 hover/press 效果的按鈕)

  • QPushButton、setFlat(True)、高度 34、字型 13/Medium、padding 0 18px、 圓角 RADIUS_CONTROL。
  • QSS 中的文字:color: {accent_fg(theme)}(不是硬編碼的 "white"!)。
  • 背景 — 不是 QSS :hover(即時的),而是 Q_PROPERTY alpha (float, 0..1):在 paintEvent 中 使用 setAlphaF(alpha) 繪製強調色 QColor + 圓角矩形,然後 super().paintEvent() 繪製文字。事件:enter → 0.85、leave → 1.0、 press → 0.7、release → 0.85/1.0。動畫 DUR_FAST、EASE。

ToggleSwitch(macOS 開關)

  • QWidget 42×24、信號 toggled(bool)、僅左鍵按下的處理器。
  • Q_PROPERTY knobPos (float 0..1) 以 DUR_MED/EASE 進行動畫。
  • 繪製:圓角軌道 (radius = h/2)、顏色在 #B9B9BE(關)和 accent(開)之間插值; 手柄 — 圓形 QColor(self._theme.bg)、直徑 h-4、 位置 x_min = 2、x_max = width - knob_d - 2。
  • 陷阱: 手柄使用 theme.bg 繪製,而不是白色 — 在深色主題中,白色手柄會與白色的關閉狀態軌道融合在一起。
  • 程式化 setChecked 僅動畫化,不發射信號(信號在 mousePressEvent 中發射)。

MacSlider(macOS 滑桿)

  • QWidget 高度 28、Q_PROPERTY knobScale (1.0 → 1.12 hover、0.94 press)、DUR_FAST。
  • 根據 x 計算值:ratio = (x - pad) / (width - 2*pad)、pad = 9.5。
  • 繪製:軌道高度 6(背景色為 separator 加上 alpha 26、填充部分為 accent)、圓形 9.5px:發光效果 (accent、hover 時 alpha 22 / press 時 alpha 34、半徑 +4)、陰影(黑色 alpha 26)、 圓形本身為 theme.bg 並帶有 separator 邊框(alpha 90,pressed 時為 255)、penWidth 1.2。

SegmentedControl(「緊湊/中等/大型」區段)

  • QWidget 高度 30;按鈕高度 26、字型 12、padding 0 16px、盒子 spacing 4、margins 4。
  • 「藥丸」指示器 — QFrame#SegIndicator (bg accent、圓角 7) 位於按鈕後方,佈局相同, 透過 QPropertyAnimation(geometry) 以 DUR_MED/EASE 移動。
  • 文字顏色:所選按鈕為 accent_fg(theme)、其餘為 text_secondary。
  • 在 showEvent 中,將藥丸指示器置底 (lower()) 並設定為所選按鈕的幾何形狀。

TrafficLight(交通燈號)

  • QPushButton 22×22、Q_PROPERTY hover (0..1) 以 DUR_MED 動畫化。
  • 沒有黃色/綠色的邊框顏色:關閉 — 紅色 #FF5F57 (alpha 200 + 35·hover)、 最小化/縮放 — 中性色 rgba(text_secondary, 60 + 40·hover)。
  • 圖示使用 QPen 搭配 RoundCap 繪製,寬度 2、僅使用整數座標(請參閱陷阱):
    • 關閉 — 兩條線構成的叉叉;
    • 最小化 — 向下 V 形:(cx-5,cy-3)-(cx,cy+3)-(cx+5,cy-3);
    • 最大化(未展開) — 向上 V 形:(cx-5,cy+3)-(cx,cy-3)-(cx+5,cy+3);
    • 最大化在展開狀態下 (_maximized) — 構成四個角括號的 8 條線。
  • 圖示顏色:關閉按鈕 — 白色 alpha 205+30·hover;其餘 — 根據 hover 在 text_secondary → text 之間插值。

SidebarToggle(側邊欄摺疊按鈕)

  • QPushButton 22×22、hover 0..1、hover 背景 rgba(separator,26)、圖示在 text_secondary → text 之間插值。
  • 展開面板的圖示 (Lucide panel-left-close):邊框 drawRoundedRect(QRectF(cx-7, cy-7, 14, 14), 2, 2)、 位於 cx-2 的垂直分隔線、來自 (cx+4, cy-2)→(cx, cy)→(cx+4, cy+2) 的 V 形 <。
  • 摺疊狀態的圖示 (Lucide panel-right):邊框 + 位於 cx+2 的分隔線、沒有 V 形。
  • set_collapsed(bool) 切換圖示;狀態由 MainWindow.toggle_sidebar 同步。

Sidebar(導航欄)

  • QWidget setFixedWidth(SIDEBAR_WIDTH)、margins 10,16,10,16、spacing 2。
  • 品牌「我的專案」(13/Bold、padding: 0 8px 14px 8px)。
  • 頁面名稱 — QPushButton#NavButton 高度 30、字型 13、checkable、 非啟用狀態 — text_secondary、啟用狀態 — text。指示器 — QFrame 圓角 8、 bg rgba(accent,40)、根據 geometry 向所選按鈕動畫化 (DUR_MED)。
  • 點擊目前頁面的非啟用按鈕 — 無操作。在 showEvent 中,指示器在沒有動畫的情況下設定。

6. 通用 QSS (build_stylesheet)

  • #Panel — 主題背景色、視窗圓角。#TitleBar — 透明。#Sidebar — 側邊欄背景色。
  • #Indicator — rgba(accent, 40)、圓角 8。
  • QPushButton#NavButton — 左對齊、圓角 8、padding-left 10;:checked — text; :hover — rgba(separator,26)。
  • QPushButton#ToolButton — 次要顏色、圓角 6、hover 時高亮。
  • 捲動條(全域添加,防止聊天和列表看起來「厚重」): 垂直/水平 8px、把手圓角 3 rgba(separator,60)(hover 120)、最小值 30、 箭頭和頁面 — 透明。
  • 在任何具有 QSS 背景的 widget 上設定 setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)。

7. 頁面(共 6 個)

透過 _page_shell(theme, title)(margins 28/24/28/24、spacing 16、標題 22/Bold)和 _add_card(theme, layout, title)(卡片 + 標題 14/DemiBold + 陰影)組合。

  1. 總覽 — 「系統」卡片(切換開關:自動啟動 ✓、通知 ✓、省電模式 ✗) 和「顯示」卡片(滑桿:亮度=70、音量=45)。滑桿行 — 標籤在左側、 控制項在右側,control_stretch=1(請參閱關於 stretch 的陷阱)。

  2. 任務 — 4 個 QCheckBox,指示器 18×18 (border rgba(separator,120)、hover → accent、checked → bg accent);進度條 (12px、區塊 accent)、計數器 「已完成 N / M」、透過 QVariantAnimation (280ms) 實現 QProgressBar 動畫; HoverButton("重設") 按鈕。

  3. 輸入 — 欄位:

    • 姓名、電子郵件 — 標準 QLineEdit (34px、圓角 8、hover 邊框 rgba(sep,140)、 focus → 邊框 accent + 色調 rgba(accent,8)、placeholder text_secondary、 selection accent+accent_fg)。
    • 密碼 — _build_password_field:尾隨操作「眼睛」— 圖示在 eye ↔ eye_slash 之間切換、點擊切換 EchoMode.Password ↔ Normal。
    • 搜尋 — _build_search_field:藥丸形圓角 17、前導放大鏡圖示、尾隨 叉叉圖示、僅在文字非空時可見(點擊 — 清除)。
    • 多行文字 — QTextEdit (圓角 10、透過 palette.setColor(QPalette.ColorRole.PlaceholderText, ...) 設定 placeholder — QSS 選擇器 QTextEdit[placeholderText] 不可靠) + 右側的字元計數器。
    • 欄位圖示在 _field_icon 中繪製 (18px:放大鏡 = 圓圈 + f 手柄;叉叉 = rgba(text_secondary,110) 的圓圈 + 顏色為 theme.bg 的「✕」) 或從 SVG 渲染 (_svg_icon): 渲染前,SVG 中的 currentColor 被替換為 theme.text_secondary(請參閱陷阱)。
  4. 聊天 — 請參閱第 8 節。

  5. 檔案 — 請參閱第 9 節。

  6. 設定 — 「外觀」卡片(切換開關「深色主題」→ _on_theme_toggled、 「介面大小」區段)、「行為」卡片(切換開關 自動更新 ✓、動畫 ✓)、 下拉選單「介面語言」(QSS:欄位 surface+邊框、QAbstractItemView — selection-background-color: accent; selection-color: accent_fg)、滑桿 「動畫速度」=50。


8. 聊天(「聊天」頁面)

  • 容器 — 透明 QScrollArea(無框;為避免白色背景:QSS background: transparent + viewport().setStyleSheet(...transparent) + 內部 QWidget 透明)。
  • 訊息儲存在 `self._chat_messages: list[(text, m

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相關技能