Skip to content

Commit d9bb755

Browse files
Centre the scrub pill, unblur it, and stop the readout acting as a pick target
- Centre the pill over the value row rather than left-aligning it. This is safe now that it belongs to the row and is bounded by the row's width; the earlier left-anchoring was working around it being centred over a single field, which could push it past the swatch edge. - Cancel the inherited text shadow on the pill. The swatch's content carries one for legibility against any colour, but on the pill — which already sits on an opaque capsule — it just read as blur. - Shield the readout block from starting a screen pick. Clicking the labels, the colour name, or the gaps between them fell through to PickTarget and began a pick, which is easy to trigger by accident when the same block is also where you click to edit and drag to scrub. The shield is an NSView rather than a SwiftUI contentShape because PickTarget is itself an NSView in the same z-order, so it has to win AppKit's hit-testing, not just SwiftUI's. The value fields sit in front and keep receiving their own clicks. - Show a hairline in the swatch's UI colour on hover, so the boundary of that non-picking region is discoverable rather than invisible.
1 parent e3baaf4 commit d9bb755

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

Pika/Views/EditableColorValue.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ struct EditableColorValue: View {
183183
// Decorative overlay: it doesn't feed into the row's reported size, so it can appear and
184184
// change width without perturbing `FlowLayout`. Anchored to the row rather than to the
185185
// dragged field, so it's always in bounds and doesn't jump between components.
186-
.overlay(alignment: .topLeading) {
186+
.overlay(alignment: .top) {
187187
if let rowScrubPreview {
188188
Text(rowScrubPreview)
189189
.font(.system(size: 11, weight: .semibold, design: .rounded))
@@ -196,7 +196,10 @@ struct EditableColorValue: View {
196196
.background(Capsule().fill(Color(uiColor).opacity(0.92)))
197197
// Bounded by the row, and allowed to shrink rather than run past its edge:
198198
// a long format (rgba with five decimals a channel) is wider than the swatch.
199-
.frame(maxWidth: effectiveWidth, alignment: .leading)
199+
.frame(maxWidth: effectiveWidth)
200+
// The swatch's content carries a text shadow for legibility on any colour;
201+
// inherited by the pill it just reads as blur, so cancel it here.
202+
.shadow(color: .clear, radius: 0, x: 0, y: 0)
200203
.offset(y: -24)
201204
.transition(.opacity.combined(with: .scale(scale: 0.9)))
202205
.allowsHitTesting(false)

Pika/Views/EyedropperButton.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ private struct PickTarget: NSViewRepresentable {
6666
}
6767
}
6868

69+
/// Swallows clicks over the readout block so they don't fall through to `PickTarget` and start
70+
/// a screen pick. An AppKit view rather than a SwiftUI `contentShape`: `PickTarget` is itself an
71+
/// `NSView` sitting in the same z-order, so the thing shadowing it has to win AppKit's own
72+
/// hit-testing, not just SwiftUI's.
73+
private struct ClickShield: NSViewRepresentable {
74+
func makeNSView(context _: Context) -> NSView { ShieldView() }
75+
func updateNSView(_: NSView, context _: Context) {}
76+
77+
final class ShieldView: NSView {
78+
// Absorb rather than forward: the readout's own fields sit in front of this and keep
79+
// receiving their clicks, but the labels, colour name, and the gaps between them no
80+
// longer act as a pick target.
81+
override func mouseDown(with _: NSEvent) {}
82+
}
83+
}
84+
6985
struct EyedropperButton: View {
7086
@ObservedObject var eyedropper: Eyedropper
7187
/// Shared with the other swatch (owned by `ColorPickers`) rather than local: a click on
@@ -87,6 +103,8 @@ struct EyedropperButton: View {
87103
@State private var valueInvalid: Bool = false
88104
@State private var isPressed: Bool = false
89105
@State private var flashOpacity: Double = 0
106+
/// Drives the hairline that shows where the non-picking readout block begins.
107+
@State private var readoutHovered = false
90108

91109
var body: some View {
92110
ZStack {
@@ -170,6 +188,18 @@ struct EyedropperButton: View {
170188
.shadow(color: shadowColor.opacity(0.30), radius: 0, x: 0, y: 1)
171189
.shadow(color: shadowColor.opacity(0.10), radius: 3, x: 0, y: 0)
172190
}
191+
// Both of these sit outside the shadow above, so the hairline stays crisp.
192+
.background(ClickShield())
193+
.overlay(
194+
RoundedRectangle(cornerRadius: 6.0, style: .continuous)
195+
.strokeBorder(
196+
eyedropper.color.getUIColor().opacity(readoutHovered ? 0.35 : 0),
197+
lineWidth: 1
198+
)
199+
.allowsHitTesting(false)
200+
)
201+
.onHover { readoutHovered = $0 }
202+
.animation(.easeInOut(duration: 0.15), value: readoutHovered)
173203
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
174204

175205
VStack(spacing: 4.0) {

0 commit comments

Comments
 (0)