diff --git a/App/Common UI/ReliefButton.swift b/App/Common UI/ReliefButton.swift index 94cfab1..972d595 100644 --- a/App/Common UI/ReliefButton.swift +++ b/App/Common UI/ReliefButton.swift @@ -13,18 +13,29 @@ class ReliefButton: UIButton internal let backgroundView = GradientView(direction: .vertical, colors: ReliefButton.gradientColors(inverted: false, darkMode: false)) static let padding = CGFloat(24.0) + static let cornerRadius = CGFloat(4.0) + static let borderWidth = CGFloat(1.0) override var isHighlighted: Bool { - didSet { + get { + super.isHighlighted || remainsPressed + } + + set { + super.isHighlighted = newValue setBackgroundInverted(isHighlighted) } } + var remainsPressed: Bool = false { + didSet { + self.isHighlighted = remainsPressed + } + } + init() { super.init(frame: .zero) - let cornerRadius = CGFloat(4.0) - self.tintColor = .init(dynamicProvider: { traitCollection -> UIColor in if traitCollection.userInterfaceStyle == .light { return .init(white: 0.15, alpha: 1.0) @@ -40,16 +51,16 @@ class ReliefButton: UIButton shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0) shadowView.layer.shadowRadius = 1.0 shadowView.layer.shadowOpacity = 1.0 - shadowView.layer.cornerRadius = cornerRadius + shadowView.layer.cornerRadius = Self.cornerRadius shadowView.layer.masksToBounds = false shadowView.layer.shouldRasterize = true shadowView.layer.rasterizationScale = UIScreen.main.scale addSubview(shadowView) - backgroundView.layer.cornerRadius = cornerRadius + backgroundView.layer.cornerRadius = Self.cornerRadius backgroundView.isUserInteractionEnabled = false backgroundView.layer.masksToBounds = true - backgroundView.layer.borderWidth = 1.0 + backgroundView.layer.borderWidth = Self.borderWidth addSubview(backgroundView) traitCollectionDidChange(nil) @@ -131,5 +142,9 @@ class ReliefButton: UIButton backgroundView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundDimension, height: backgroundDimension)) backgroundView.frame = backgroundView.frame.centeredX(inRect: bounds) shadowView.frame = backgroundView.frame + + // Offset by a small amount. Visual illusion caused by the shadow + backgroundView.frame = backgroundView.frame.offsetBy(dx: 0.0, dy: 1.0) + shadowView.frame = shadowView.frame.offsetBy(dx: 0.0, dy: 1.0) } } diff --git a/App/Common UI/SegmentedReliefButton.swift b/App/Common UI/SegmentedReliefButton.swift index 7e26c75..b17e79c 100644 --- a/App/Common UI/SegmentedReliefButton.swift +++ b/App/Common UI/SegmentedReliefButton.swift @@ -14,9 +14,22 @@ class SegmentedReliefButton: ReliefButton didSet { children.forEach { addSubview($0) }; setNeedsLayout() } } + private let backgroundMaskView = UIView(frame: .zero) + private let backgroundsContainerView = UIView(frame: .zero) + private var childrenHighlighObservations: [NSKeyValueObservation] = [] + init(children: [ReliefButton]) { super.init() self.children = children + + backgroundMaskView.backgroundColor = .black + backgroundMaskView.layer.masksToBounds = true + backgroundMaskView.layer.cornerRadius = Self.cornerRadius - Self.borderWidth + + backgroundsContainerView.clipsToBounds = true + backgroundsContainerView.mask = backgroundMaskView + + addSubview(backgroundsContainerView) } override var isHighlighted: Bool { @@ -28,23 +41,36 @@ class SegmentedReliefButton: ReliefButton } override func sizeThatFits(_ size: CGSize) -> CGSize { + let ourSize = super.sizeThatFits(size) let width: CGFloat = children.reduce(0.0) { (result, button) -> CGFloat in return result + button.sizeThatFits(size).width } - return CGSize(width: width, height: size.height) + return CGSize(width: width, height: ourSize.height) + } + + override func setBackgroundInverted(_ inverted: Bool) { + // no-op } override func layoutSubviews() { super.layoutSubviews() + backgroundView.colors = [ .clear ] backgroundView.frame = bounds shadowView.frame = bounds + backgroundMaskView.frame = backgroundView.frame.insetBy(dx: 1.0, dy: 1.0) + backgroundsContainerView.frame = backgroundView.frame + + childrenHighlighObservations.removeAll() + backgroundsContainerView.subviews.forEach { $0.removeFromSuperview() } + + let darkMode = self.traitCollection.userInterfaceStyle == .dark var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height)) - for child in children { + for (i, child) in children.enumerated() { child.shadowView.isHidden = true - child.backgroundView.layer.borderWidth = 0 + child.backgroundView.isHidden = true bringSubviewToFront(child) let childSize = child.sizeThatFits(bounds.size) @@ -52,6 +78,26 @@ class SegmentedReliefButton: ReliefButton child.frame = buttonRect buttonRect.origin.x += buttonRect.width + + // Background + let backgroundView = GradientView(direction: .vertical, colors: Self.gradientColors(inverted: false, darkMode: darkMode)) + backgroundView.frame = child.frame + backgroundsContainerView.insertSubview(backgroundView, at: 0) + childrenHighlighObservations.append(child.observe(\.isHighlighted) { [backgroundView] (button, changeEvent) in + backgroundView.colors = Self.gradientColors(inverted: button.isHighlighted, darkMode: darkMode) + }) + + // Separator + if i < children.count - 1 { + let separatorView = UIView(frame: CGRect( + x: child.frame.maxX, + y: 0, + width: 1.0, + height: bounds.height + )) + separatorView.backgroundColor = .systemFill + backgroundsContainerView.addSubview(separatorView) + } } } } diff --git a/App/Script Policy UI/ScriptPolicyControl.swift b/App/Script Policy UI/ScriptPolicyControl.swift index 6455eb0..13acaa5 100644 --- a/App/Script Policy UI/ScriptPolicyControl.swift +++ b/App/Script Policy UI/ScriptPolicyControl.swift @@ -28,19 +28,19 @@ class ScriptPolicyControl: UIControl segmentContainer.children = [ allowButton, denyButton ] addSubview(segmentContainer) + allowButton.showsTouchWhenHighlighted = false allowButton.addAction(UIAction(handler: { [unowned self] _ in self.policyStatus = .allowed self.sendActions(for: .valueChanged) }), for: .touchUpInside) allowButton.imageView?.contentMode = .scaleAspectFit - addSubview(allowButton) + denyButton.showsTouchWhenHighlighted = false denyButton.addAction(UIAction(handler: { [unowned self] _ in self.policyStatus = .blocked self.sendActions(for: .valueChanged) }), for: .touchUpInside) denyButton.imageView?.contentMode = .scaleAspectFit - addSubview(denyButton) } override var intrinsicContentSize: CGSize { @@ -54,15 +54,19 @@ class ScriptPolicyControl: UIControl if policyStatus == .allowed { allowButton.tintColor = .blue + allowButton.remainsPressed = true allowButton.setImage(UIImage(systemName: "play.circle.fill"), for: .normal) - denyButton.tintColor = .darkGray + denyButton.tintColor = nil + denyButton.remainsPressed = false denyButton.setImage(UIImage(systemName: "stop.circle"), for: .normal) } else { - allowButton.tintColor = .darkGray + allowButton.tintColor = nil + allowButton.remainsPressed = false allowButton.setImage(UIImage(systemName: "play.circle"), for: .normal) denyButton.tintColor = .red + denyButton.remainsPressed = true denyButton.setImage(UIImage(systemName: "stop.circle.fill"), for: .normal) } }