From 82f5962bec1cc2b850a119a19030f00df05d451a Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 29 Jul 2026 09:06:04 +0200 Subject: [PATCH] fix(client/apple): three things the first on-glass run found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reset read as prose. Next to "Overrides Default settings" in the same tint and the same size, the one action that can undo an override looked like the third word of the notice. It is a bordered control now, pushed to the far edge of the caption line with an undo glyph. The scope switcher showed above the About tab. It sat over the whole macOS TabView, so on the acknowledgements page — which edits nothing — it read as belonging to them. The tabs are tagged and the switcher sits that one out. Pinned cards were taller than their host's. The profile chip had a line of its own, so a card with a profile and one without were different heights and a pin stuck out of its grid row. The chip rides the title line instead, which is also where it reads as "this card connects with that". Prominence is fill and weight only — a chip with a bigger type size would have brought the height difference straight back. Co-Authored-By: Claude Opus 5 (1M context) --- .../Home/GamepadHomeView.swift | 7 ++++- .../PunktfunkClient/Home/HostCards.swift | 30 +++++++++++++----- .../Settings/SettingsView+Scope.swift | 23 ++++++++++---- .../Settings/SettingsView.swift | 31 +++++++++++++++---- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/clients/apple/Sources/PunktfunkClient/Home/GamepadHomeView.swift b/clients/apple/Sources/PunktfunkClient/Home/GamepadHomeView.swift index fc30e3d4..3b1caa4d 100644 --- a/clients/apple/Sources/PunktfunkClient/Home/GamepadHomeView.swift +++ b/clients/apple/Sources/PunktfunkClient/Home/GamepadHomeView.swift @@ -42,6 +42,9 @@ private struct HomeTile: Identifiable { /// The profile this tile connects with — shown as a tinted chip. Set on a pinned card; nil on /// a plain host tile unless the host is bound to one (then it answers "what will A do?"). var profile: StreamProfile? + /// This tile IS a pinned host+profile card, not a host wearing its binding's chip — the chip + /// reads loud on the former (it is the reason the tile exists) and quiet on the latter. + var isPinnedCard = false var isOnline = false var isPaired = false var isConnecting = false @@ -273,6 +276,7 @@ struct GamepadHomeView: View { title: host.displayName, subtitle: "\(host.address):\(String(host.port))", profile: profile ?? bound, + isPinnedCard: profile != nil, isOnline: online, isPaired: host.pinnedSHA256 != nil, isConnecting: connecting, @@ -374,7 +378,8 @@ private struct GamepadHostTile: View { .lineLimit(1) .minimumScaleFactor(0.7) if let profile = tile.profile { - ProfileChip(profile: profile, size: Self.statusFont, prominent: true) + ProfileChip( + profile: profile, size: Self.statusFont, prominent: tile.isPinnedCard) .padding(.top, 4) } Text(tile.subtitle) diff --git a/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift b/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift index b35ba9a5..119a3f15 100644 --- a/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift +++ b/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift @@ -135,12 +135,23 @@ struct HostCardView: View { HStack(spacing: m.spacing) { monogramTile(monogram(host.displayName), m: m, connecting: isConnecting, filled: true) VStack(alignment: .leading, spacing: 4) { - Text(host.displayName) - .font(.geist(m.name, .bold, relativeTo: .title3)) - .foregroundStyle(.primary) - .lineLimit(1) - if let profile = shownProfile { - ProfileChip(profile: profile, size: m.status, prominent: pinnedProfile != nil) + // The chip rides the TITLE line rather than a line of its own: a card with a + // profile and one without have to be the same height, or a pinned card sticks + // out of its row in the grid. Beside the name is also where it reads as "this + // card connects with that", which is what it means. + HStack(spacing: 6) { + Text(host.displayName) + .font(.geist(m.name, .bold, relativeTo: .title3)) + .foregroundStyle(.primary) + .lineLimit(1) + if let profile = shownProfile { + ProfileChip( + profile: profile, size: m.status, + prominent: pinnedProfile != nil) + // The chip is the shorter, more compressible of the two; let the + // host name give up its width first only after the chip has. + .layoutPriority(1) + } } Text("\(host.address):\(String(host.port))") .font(.geist(m.meta, relativeTo: .caption)) @@ -301,6 +312,9 @@ struct HostCardView: View { /// The profile a card connects with, as a tinted pill. Quiet on a bound primary card (it only /// answers "what will a click do?"); prominent on a pinned card, where the profile IS the reason /// the card exists — which is where the catalog's `accent` earns its keep. +/// +/// Prominence is fill and weight only, never TYPE SIZE: the chip sits on the card's title line, +/// and a chip taller than the name would make pinned cards taller than their host's. struct ProfileChip: View { let profile: StreamProfile let size: CGFloat @@ -314,13 +328,13 @@ struct ProfileChip: View { .frame(width: size * 0.55, height: size * 0.55) .accessibilityHidden(true) // the name is right there Text(profile.name) - .font(.geist(prominent ? size + 2 : size, .semibold, relativeTo: .caption2)) + .font(.geist(size, prominent ? .bold : .semibold, relativeTo: .caption2)) .lineLimit(1) } .foregroundStyle(tint) .padding(.horizontal, 7) .padding(.vertical, 2) - .background(Capsule().fill(tint.opacity(prominent ? 0.22 : 0.13))) + .background(Capsule().fill(tint.opacity(prominent ? 0.24 : 0.12))) .accessibilityLabel("Profile \(profile.name)") } } diff --git a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Scope.swift b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Scope.swift index 687ca5e0..fdce67ff 100644 --- a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Scope.swift +++ b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Scope.swift @@ -244,9 +244,13 @@ extension SettingsView { profiles.clearOverride(id, field: name) } - /// The accent dot + "Reset" affordance a row carries while it overrides the defaults. Rendered + /// The accent dot + Reset affordance a row carries while it overrides the defaults. Rendered /// inside `described`'s caption line, so it sits with the row it belongs to instead of in some /// separate list of exceptions. + /// + /// Reset is a BORDERED control pushed to the far edge, not tinted text next to the label: as a + /// plain button it read as a third word of the notice, and the one action that can undo an + /// override must not look like prose. @ViewBuilder func overrideMarker(_ name: String) -> some View { if isOverridden(name) { @@ -259,12 +263,19 @@ extension SettingsView { Text("Overrides Default settings") .font(.geist(12, .medium, relativeTo: .caption2)) .foregroundStyle(Color.brand) - Button("Reset") { resetOverride(name) } - .buttonStyle(.plain) - .font(.geist(12, .medium, relativeTo: .caption2)) - .foregroundStyle(.tint) - .accessibilityLabel("Reset to Default settings") + Spacer(minLength: 12) + Button { + resetOverride(name) + } label: { + Label("Reset", systemImage: "arrow.uturn.backward") + .font(.geist(11, .medium, relativeTo: .caption2)) + } + .buttonStyle(.bordered) + .controlSize(.small) + .accessibilityLabel("Reset to Default settings") + .help("Stop overriding this — follow Default settings again") } + .frame(maxWidth: 360, alignment: .leading) // the described-row caption's line cap } } diff --git a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView.swift b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView.swift index 572624e8..a0375838 100644 --- a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView.swift +++ b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView.swift @@ -29,6 +29,9 @@ struct SettingsView: View { @State var nameAction: ProfileNameAction? @State var nameDraft = "" @State var profilePendingDelete: StreamProfile? + #if os(macOS) + @State private var macTab: MacTab = .general + #endif @AppStorage(DefaultsKey.streamWidth) var width = 1920 @AppStorage(DefaultsKey.streamHeight) var height = 1080 @AppStorage(DefaultsKey.streamHz) var hz = 60 @@ -133,14 +136,24 @@ struct SettingsView: View { // MARK: - macOS: tabbed preferences #if os(macOS) + /// The preferences tabs, tagged so the scope control can sit out the one page that isn't a + /// settings layer at all. + private enum MacTab: Hashable { + case general, display, input, audio, controllers, about + } + private var macBody: some View { // The scope control heads the window — above the tabs, because it is about which layer - // every tab is editing, not about any one of them. + // every tab is editing, not about any one of them. About is the exception: it edits + // nothing, and directly above the acknowledgements the switcher read as belonging to + // them. VStack(alignment: .leading, spacing: 0) { - scopeSwitcher - .padding(.horizontal, 20) - .padding(.top, 14) - .padding(.bottom, 10) + if macTab != .about { + scopeSwitcher + .padding(.horizontal, 20) + .padding(.top, 14) + .padding(.bottom, 10) + } macTabs } .frame(width: 500, height: 580) @@ -149,7 +162,7 @@ struct SettingsView: View { private var macTabs: some View { // Tab map mirrors SettingsCategory: General = session/app behavior, Display = the whole // picture (resolution lives here), Input = keyboard & mouse. - TabView { + TabView(selection: $macTab) { Form { sessionSection overlaySection @@ -157,6 +170,7 @@ struct SettingsView: View { } .formStyle(.grouped) .tabItem { Label("General", systemImage: "gearshape") } + .tag(MacTab.general) Form { resolutionSection @@ -166,12 +180,14 @@ struct SettingsView: View { } .formStyle(.grouped) .tabItem { Label("Display", systemImage: "display") } + .tag(MacTab.display) Form { inputSection } .formStyle(.grouped) .tabItem { Label("Input", systemImage: "keyboard") } + .tag(MacTab.input) Form { audioSection @@ -188,6 +204,7 @@ struct SettingsView: View { if micChannel > micChannelCount { micChannel = 0 } } .tabItem { Label("Audio", systemImage: "speaker.wave.2") } + .tag(MacTab.audio) Form { controllersSection @@ -199,9 +216,11 @@ struct SettingsView: View { } .onDisappear { gamepads.stopDiscovery() } .tabItem { Label("Controllers", systemImage: "gamecontroller") } + .tag(MacTab.controllers) AcknowledgementsView() .tabItem { Label("About", systemImage: "info.circle") } + .tag(MacTab.about) } } #endif