feat(macos): self-drawn window chrome + app-wide antialiased rounded UI#5
Open
Miint-Sunny wants to merge 20 commits into
Open
feat(macos): self-drawn window chrome + app-wide antialiased rounded UI#5Miint-Sunny wants to merge 20 commits into
Miint-Sunny wants to merge 20 commits into
Conversation
macOS previously kept the stock native title bar (Plan B), which sat above the app's own toolbar and produced a visible "double bar". Qt 6.9's ExpandedClientAreaHint + NoTitleBarBackgroundHint extend the content under the native title bar while keeping the real traffic lights, collapsing the two bars into one. WindowSurface fills the window edge-to-edge (margin 0) because the native window mask already rounds the corners; the 8px inset band is only needed for the Windows frameless shadow/resize zone. The earlier blocker (QTBUG-133215, dark bar + traffic lights floating over content) is fixed in the bundled Qt 6.11. HAINTAG_MAC_CLASSIC=1 restores the stock native title bar as a fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_on_card_finished decided docking with per-edge half-plane tests in GLOBAL coordinates for the left/right/top/bottom docks (e.g. "card.left() < dock.right() + 30"). For a card floated out into its own window that meant releasing it anywhere in that half of the screen — or on an adjacent display — vacuumed it into the dock, so it appeared to vanish. Replace the four half-plane branches with the same "intersects the expanded dock rect" test the floating dock already used, so a card only docks when actually dropped onto the dock band. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… corners
Corner radii were ad hoc: ~178 border-radius sites across 25 files using 9
distinct values (0/2/3/4/6/7/8/10/12 px), some DPI-scaled and some not, with
the workbench card at 4px vs generic cards at 10px.
Introduce one scale — rad_none/xs/sm/md (0/2/6/10) — exposed as QSS palette
tokens ({rad_*}, mirroring the existing fs_* pipeline) plus RAD_* constants
and a _rad() helper in ui_tokens for inline stylesheets. Every card, panel,
dialog, button, input and menu now resolves to one of those four values, so
retuning the whole app is a one-line change. Pills, circular slider knobs,
the toggle track and deliberate square segmented joins are preserved.
Also fix the blurry card corners: #WidgetDragStrip painted its own rounded
top corners at the same radius directly over #WidgetCard's, so two
independently-antialiased semi-transparent edges blended into a doubled/soft
corner. The strip is now a plain rectangle inside the card (single rounded
edge), and the card border goes from {line} (0.08 alpha) to {line_strong}
(0.18) so that edge reads crisp against the dark workspace.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pin / always-on-top toggles used the 📌 / 📍 emoji, which renders as an OS-specific colour bitmap that clashes with the flat monochrome chrome and cannot follow the palette. Add native_app/icons.py — a QPainter push-pin drawn in an explicit colour (upright when pinned, tilted off-vertical when not) — and switch the title-bar, widget-card and image-manager pin buttons from setText() to setIcon(), recoloured on toggle and on theme change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QSS border-radius corners are not antialiased in Qt's stylesheet engine, so every rounded control/surface shows stair-stepped corners. Add the shared machinery to paint them crisply instead: - icons.py: to_qcolor() parses CSS rgba() palette strings (QColor() can't and silently yields black); rounded_rect_path()/paint_rounded_surface() are the QPainter replacement for QSS border-radius (fill/border/dashed); pin glyph refined (round head, outline/fill states, 4x oversample). - qss_surfaces.py (new): pre-renders antialiased rounded fills to cached 1x/@2x PNGs applied as a 9-patch border-image, so QSS-styled controls (buttons/inputs/menus) get crisp corners while keeping state selectors. - common.py: HoverPillButton, DashedCircleButton, DashedRectButton — self-painted button surfaces for titlebar pills and dashed add/ghost pads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace QSS border-radius (aliased) across containers and controls: - Cards/panels/popups (DockPanel, SettingsPanel, floating tray, tag completer, image manager surfaces) paint their fill+border in paintEvent with WA_TranslucentBackground; theme.py drops their QSS border/radius. - theme.control_surfaces_qss() appended after scale_qss() re-skins buttons, the input family and menus with the 9-patch AA surfaces; main.py wires it. - image_manager QPainter paths route palette values through to_qcolor() (they were passing rgba() strings straight to QColor(), painting black). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the main window's controls onto the antialiased primitives and strip their now-redundant QSS border-radius: - Widget cards self-paint body/header/border; grip, pin, close and resize handles become HoverPillButtons (titlebar pills, main + normal chrome). - Send/summary buttons (input), OC chip (workbench) use pill surfaces. - Dashed controls self-paint via DashedRectButton / _DropZone.set_dashed(): ghost add/preview (prompt manager), example image-select pad, local & LLM drop zones (interrogator), library ref-image add, LLM thumb add. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QSS slider handles use border-radius==half-width to stay circular, which Qt rasterises with visible stair-stepping — most obvious of all the aliased controls since the eye reads a circle's jagged edge instantly. Add RoundHandleSlider: the groove/sub-page stay QSS (2px, no visible aliasing) while the knob is drawn as an antialiased circle in paintEvent (handle kept transparent in QSS but sized so subControlRect still lays it out). All five sliders (settings temp/top-p/penalties, interrogator thresholds, image manager thumb size, opacity popup) unify on one accent_handle knob. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hover and active fills on titlebar buttons (language/settings/gallery/ help/pin/min/max/close), the dock collapse toggle, and per-entry delete (x) buttons were QSS border-radius rects — aliased corners on every hover. Route them all through HoverPillButton, which paints the pill as an antialiased rounded rect; QSS keeps them transparent and only sets glyph colour. Adds an "active" pill state (property active == true) for the settings/pin toggles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tus pill) The edit-toggle/copy buttons, the output container frame and the generation status capsule set widget-level QSS border-radius (which both aliases and overrides the app-level 9-patch rules). Route the buttons through HoverPillButton, self-paint the frame (QSS keeps an identically-sized transparent box so the layout doesn't move), and add PillLabel for the capsule. Scrollbar handles drop from 4px to the app-wide rad_xs=2 radius. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, dock preview Remaining always-visible QSS-rounded surfaces in the workbench: - Prompt entry frames self-paint fill + hover border + the expanded-header tint (top-only rounded path); example entry frames self-paint fill + hover border. QSS keeps identically-sized transparent 1px-border boxes. - Titlebar OC chips and the splitter swap pill route through HoverPillButton. - The dock drop-preview band becomes a RoundedPanel (new shared class). - Drop the dead #SwapButton rule (nothing sets that objectName). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared floating popups still drew their rounded chrome via QSS: the panel container, close/OK/Cancel buttons, the changelog popup and the list/spinbox inside popup panels. Migrate them: - _create_popup's inner panel and the changelog surface become RoundedPanel; the changelog body QTextEdit gets the 9-patch AA surface. - PopupClose/PopupBtn route through HoverPillButton; QSS keeps glyph colour only (padding compensates the dropped 1px border). - #PopupPanel QListWidget/::item/QSpinBox re-skinned via control_surfaces_qss 9-patch overrides (items keep hover/selected states). - The settings export/import QDialog drops its aliased QSS radius — it is a system-chromed window (macOS rounds it); its three group cards become RoundedPanels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…adata/timeline) - Prompt-preview and shortcuts popup surfaces become RoundedPanel; the shortcuts tutorial button becomes a HoverPillButton pill. - Prompt-preview message boxes and history-sidebar text blocks re-skin via the 9-patch AA surface (padding compensates border-width==slice; the history rule is scoped to QTextEdit so scrollbars don't inherit it). - Metadata-viewer thumbnail frame self-paints its rounded border under the pixmap (border-image would be covered by the full-size pixmap). - Timeline history cards become a self-painted _TimelineCard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e manager chrome - Library artist/OC banner headers/bodies self-paint their asymmetric corners (open-edged paths so expanded header + body join seamlessly); ref-image thumbnails self-paint under the pixmap; the Artist/OC segmented pair and add-entry buttons become HoverPillButtons with an active state. - OC bubble: circular avatar and stepper containers self-paint; outfit pills and action buttons become HoverPillButtons (active outfit keeps its accent state via the property). - Tag-completer category dot becomes a self-painted AA circle. - Image manager: the titlebar band merges into the self-painted window surface (top-only rounded band, continuous border); detail-panel action buttons become HoverPillButtons. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The upstream CI (ci.yml, runs on every PR targeting master) gates on ruff check — these seven leftovers from swapping QSS controls to the self-painted classes were failing it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The window shell corners were the last QSS-rounded containers: the shell fill+border (#WindowSurface), the title bar's top corners and the content area's bottom corners — visible as aliased window corners on the frameless Windows/Linux path. WindowSurface, a new _TitleBarSurface and _ContentHostSurface now paint them antialiased (rounded_rect_path gains bottom_only). On the macOS expanded-client-area path the native mask rounds the window, so WindowSurface.set_plain(True) replaces the old QSS override with a square fill. The title bar's 1px bottom hairline stays QSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The interrogator panel read as square: its footer/action buttons and LLM thumbnails used border-radius 0, the mode switch and inputs 2px. Bring it into the subtle-round scale with antialiased surfaces: - _local_button_style and the LLM text buttons (default/primary/danger/ secondary) render via the AA 9-patch at rad_sm; padding compensates so button heights are unchanged. Compact/icon/toggle buttons stay in the small-control rad_xs tier (slice would eat their content box). - The mode switch self-paints its frame and checked/hover segment fills (a 9-patch border-width would inflate the fixed 24px height). - Inputs/combos/text edits move to the rad_sm 9-patch surface. - LLM thumbnails self-paint rad_sm frames (selected keeps the 2px ring); the dashed add pad and the restart button join the same tier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish the sweep on the rarely-shown chrome: updater dialogs (changelog box, outline + accent action buttons), crash-report QMessageBox buttons, the destroy-template editor (add/del/save/cancel buttons, list with selected-item surface, name/text inputs), the onboarding tour panel and its Next button, and the hint bubble surface. Buttons ride HoverPillButton or the AA 9-patch with padding compensation; panels become RoundedPanels. Progress bars and 2px-radius chips stay QSS (no visible aliasing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first pass skinned interrogator buttons with the 9-patch: outline-style fills and stretched corners that read rounder than the QPainter-drawn pills used everywhere else. Route every interrogator button (local setup/footer, LLM toolbar/presets/footer) through HoverPillButton instead — the same class as the workbench copy/edit buttons, so the shape matches exactly: solid bg_surface fill + hairline border, accent for primaries, danger ring while running. HoverPillButton now recognises checkable buttons' checked state as 'active' (API toggle). The mode-switch segment fill drops to rad_xs+1 — the near-capsule block read too round against the app's rounded squares. The 9-patch stays only on the input family. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the 9-patch border-image on combos, macOS' overlay popup geometry (menu-style, content-width, aligned over the control) miscalculates its height and the list opens cut off — and the QSS combobox-popup hint is not honoured on the real interaction path. Give every QComboBox a Fusion BASE style via an application-level Polish hook: its popup is the standard below-the-control drop-down, sized correctly; the app stylesheet still wraps the widget style, so the themed rendering is unchanged. Covers combos created later (prompt entries, dialogs) too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
macOS 集成第三轮:自绘窗口镶边 + 全应用抗锯齿圆角语言。
Qt 的样式表引擎光栅化
border-radius时不做抗锯齿,整个 UI 的圆角都有阶梯锯齿。本 PR 把所有可见圆角表面迁移到自绘/预渲染抗锯齿路径,并统一了设计语言。主要内容
ExpandedClientAreaHint扩展客户区,内容延伸到原生标题栏下,消除"双标题栏";HAINTAG_MAC_CLASSIC=1回退经典模式。窗口壳(surface/标题条/内容区)自绘 AA 圆角,Windows/Linux 无边框路径同样受益paint_rounded_surface/rounded_rect_path(QPainter 自绘)、qss_surfaces.py(9-patch 预渲染,供 QSS 控件保留 :hover/:focus 状态机)、to_qcolor(解析调色板的 CSSrgba()字符串——直接传给QColor()会静默画成纯黑)widgets/common.py):HoverPillButton(填充式圆角方按钮,全应用按钮统一形状)、DashedRectButton/DashedCircleButton(虚线框)、RoundHandleSlider(AA 圆头滑杆)、RoundedPanel、PillLabel验证
ruff check+pytest(137 例)全绿🤖 Generated with Claude Code