From 69029a31956c05e9838a0db0ce3d3df9fd8f423f Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 14 Aug 2020 15:55:08 -0700 Subject: [PATCH] Nicer toolbar buttons --- App/Common UI/GradientView.swift | 72 +++++++ App/Common UI/ReliefButton.swift | 130 +++++++++++++ App/Common UI/SegmentedReliefButton.swift | 57 ++++++ App/SceneDelegate.swift | 6 +- .../ScriptControllerIconView.swift | 11 +- App/Titlebar and URL Bar/TitlebarView.swift | 38 +--- .../ToolbarButtonContainerView.swift | 47 +++++ App/Titlebar and URL Bar/ToolbarView.swift | 115 +++++++++++ .../ToolbarViewController.swift | 183 ++++-------------- App/Titlebar and URL Bar/URLBar.swift | 4 +- .../{CGPoint+Utils.swift => Geometry.swift} | 22 +++ SBrowser.xcodeproj/project.pbxproj | 36 +++- 12 files changed, 534 insertions(+), 187 deletions(-) create mode 100644 App/Common UI/GradientView.swift create mode 100644 App/Common UI/ReliefButton.swift create mode 100644 App/Common UI/SegmentedReliefButton.swift create mode 100644 App/Titlebar and URL Bar/ToolbarButtonContainerView.swift create mode 100644 App/Titlebar and URL Bar/ToolbarView.swift rename App/Utilities/{CGPoint+Utils.swift => Geometry.swift} (55%) diff --git a/App/Common UI/GradientView.swift b/App/Common UI/GradientView.swift new file mode 100644 index 0000000..048c36b --- /dev/null +++ b/App/Common UI/GradientView.swift @@ -0,0 +1,72 @@ +// +// GradientView.swift +// App +// +// Created by James Magahern on 8/14/20. +// + +import UIKit + +class GradientView: UIImageView +{ + enum Direction { + case horizontal + case vertical + } + + var direction: Direction = .horizontal { + didSet { image = nil; setNeedsLayout() } + } + + var colors: [UIColor] = [] { + didSet { image = nil; setNeedsLayout() } + } + + private var generatedImageSize: CGSize? + + convenience init(direction: Direction, colors: [UIColor]) { + self.init(image: nil) + self.direction = direction + self.colors = colors + } + + private func gradientImage(forSize size: CGSize) -> UIImage? { + var image: UIImage? = nil + + UIGraphicsBeginImageContext(size) + if let context = UIGraphicsGetCurrentContext() { + let gradientColorsArray = self.colors.map { $0.cgColor } + + if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: nil) { + if direction == .horizontal { + context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) + } else if direction == .vertical { + context.drawLinearGradient(gradient, start: CGPoint(x: size.width / 2, y: 0), end: CGPoint(x: size.width / 2, y: size.height), options: CGGradientDrawingOptions()) + } + } + + image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext(); + } + + return image + } + + override func layoutSubviews() { + super.layoutSubviews() + + var needsNewImage: Bool = image == nil + if let generatedImageSize = generatedImageSize { + if generatedImageSize != bounds.size { + needsNewImage = true + } + } else { + needsNewImage = true + } + + if needsNewImage { + image = gradientImage(forSize: bounds.size) + generatedImageSize = bounds.size + } + } +} diff --git a/App/Common UI/ReliefButton.swift b/App/Common UI/ReliefButton.swift new file mode 100644 index 0000000..35b4a79 --- /dev/null +++ b/App/Common UI/ReliefButton.swift @@ -0,0 +1,130 @@ +// +// ReliefButton.swift +// App +// +// Created by James Magahern on 8/14/20. +// + +import UIKit + +class ReliefButton: UIButton +{ + internal let shadowView = UIView(frame: .zero) + internal let backgroundView = GradientView(direction: .vertical, colors: ReliefButton.gradientColors(inverted: false, darkMode: false)) + + static let padding = CGFloat(24.0) + + override var isHighlighted: Bool { + didSet { + setBackgroundInverted(isHighlighted) + } + } + + + 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) + } else { + return .white + } + }) + + shadowView.alpha = 0.28 + shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0) + shadowView.isUserInteractionEnabled = false + shadowView.layer.shadowColor = UIColor.black.cgColor + 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.masksToBounds = false + shadowView.layer.shouldRasterize = true + shadowView.layer.rasterizationScale = UIScreen.main.scale + addSubview(shadowView) + + backgroundView.layer.cornerRadius = cornerRadius + backgroundView.isUserInteractionEnabled = false + backgroundView.layer.masksToBounds = true + backgroundView.layer.borderWidth = 1.0 + addSubview(backgroundView) + + traitCollectionDidChange(nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + static func gradientColors(inverted: Bool, darkMode: Bool) -> [UIColor] { + if darkMode { + if !inverted { + return [ + UIColor(white: 0.30, alpha: 1.0), + UIColor(white: 0.10, alpha: 1.0) + ] + } else { + return [ + UIColor(white: 0.10, alpha: 1.0), + UIColor(white: 0.30, alpha: 1.0) + ] + } + } else { + if !inverted { + return [ + UIColor(white: 0.98, alpha: 1.0), + UIColor(white: 0.93, alpha: 1.0) + ] + } else { + return [ + UIColor(white: 0.88, alpha: 1.0), + UIColor(white: 0.93, alpha: 1.0) + ] + } + } + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + setBackgroundInverted(isHighlighted) + backgroundView.layer.borderColor = { traitCollection -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return .init(white: 0.3, alpha: 1.0) + } else { + return .white + } + }(traitCollection).cgColor + } + + internal func setBackgroundInverted(_ inverted: Bool) { + let darkMode: Bool = (traitCollection.userInterfaceStyle == .dark) + backgroundView.colors = Self.gradientColors(inverted: inverted, darkMode: darkMode) + } + + override func imageRect(forContentRect contentRect: CGRect) -> CGRect { + let inset = CGFloat(7.0) + return contentRect.insetBy(dx: inset, dy: inset) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + let ourSize = super.sizeThatFits(size) + return CGSize(width: ourSize.width + Self.padding, height: ourSize.height) + } + + override func layoutSubviews() { + self.imageView?.contentMode = .scaleAspectFit + + super.layoutSubviews() + + sendSubviewToBack(backgroundView) + sendSubviewToBack(shadowView) + + let backgroundDimension = bounds.height - 1.0 + backgroundView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundDimension, height: backgroundDimension)) + backgroundView.frame = backgroundView.frame.centeredX(inRect: bounds) + shadowView.frame = backgroundView.frame + } +} diff --git a/App/Common UI/SegmentedReliefButton.swift b/App/Common UI/SegmentedReliefButton.swift new file mode 100644 index 0000000..7e26c75 --- /dev/null +++ b/App/Common UI/SegmentedReliefButton.swift @@ -0,0 +1,57 @@ +// +// SegmentedReliefButton.swift +// App +// +// Created by James Magahern on 8/14/20. +// + +import UIKit + +class SegmentedReliefButton: ReliefButton +{ + var children: [ReliefButton] = [] { + willSet { children.forEach { $0.removeFromSuperview() } } + didSet { children.forEach { addSubview($0) }; setNeedsLayout() } + } + + init(children: [ReliefButton]) { + super.init() + self.children = children + } + + override var isHighlighted: Bool { + didSet {} + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + let width: CGFloat = children.reduce(0.0) { (result, button) -> CGFloat in + return result + button.sizeThatFits(size).width + } + + return CGSize(width: width, height: size.height) + } + + override func layoutSubviews() { + super.layoutSubviews() + + backgroundView.frame = bounds + shadowView.frame = bounds + + var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height)) + for child in children { + child.shadowView.isHidden = true + child.backgroundView.layer.borderWidth = 0 + bringSubviewToFront(child) + + let childSize = child.sizeThatFits(bounds.size) + buttonRect.size = CGSize(width: childSize.width, height: bounds.height) + child.frame = buttonRect + + buttonRect.origin.x += buttonRect.width + } + } +} diff --git a/App/SceneDelegate.swift b/App/SceneDelegate.swift index a098c7d..d7e0ace 100644 --- a/App/SceneDelegate.swift +++ b/App/SceneDelegate.swift @@ -24,8 +24,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window.makeKeyAndVisible() self.window = window - windowScene.titlebar?.titleVisibility = .hidden - windowScene.titlebar?.separatorStyle = .none + #if targetEnvironment(macCatalyst) + windowScene.titlebar?.titleVisibility = .hidden + windowScene.titlebar?.separatorStyle = .none + #endif } } diff --git a/App/Script Policy UI/ScriptControllerIconView.swift b/App/Script Policy UI/ScriptControllerIconView.swift index 2b412ff..5b482b7 100644 --- a/App/Script Policy UI/ScriptControllerIconView.swift +++ b/App/Script Policy UI/ScriptControllerIconView.swift @@ -7,7 +7,7 @@ import UIKit -class ScriptControllerIconView: UIButton +class ScriptControllerIconView: ReliefButton { public var shieldsDown: Bool = false { didSet { setNeedsLayout() } @@ -23,8 +23,9 @@ class ScriptControllerIconView: UIButton private let shieldsUpImage = UIImage(systemName: "shield") private let shieldsPartiallyUpImage = UIImage(systemName: "shield.lefthalf.fill") - convenience init() { - self.init(frame: .zero) + + override init() { + super.init() addSubview(labelView) @@ -39,6 +40,10 @@ class ScriptControllerIconView: UIButton setBlockedScriptsNumber(0) } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + public func setBlockedScriptsNumber(_ num: Int) { if num > 0 { labelView.isHidden = false diff --git a/App/Titlebar and URL Bar/TitlebarView.swift b/App/Titlebar and URL Bar/TitlebarView.swift index 10eec43..bf15a86 100644 --- a/App/Titlebar and URL Bar/TitlebarView.swift +++ b/App/Titlebar and URL Bar/TitlebarView.swift @@ -10,11 +10,14 @@ import UIKit class TitlebarView: UIView { private let titleLabelView = UILabel(frame: .zero) - private let backgroundImageView = UIImageView(frame: .zero) + private let backgroundView = GradientView(direction: .horizontal, colors: [ + UIColor(red: 0.101, green: 0.176, blue: 0.415, alpha: 1.0), + UIColor(red: 0.153, green: 0.000, blue: 0.153, alpha: 1.0) + ]) convenience init() { self.init(frame: .zero) - addSubview(backgroundImageView) + addSubview(backgroundView) addSubview(titleLabelView) titleLabelView.textColor = .white @@ -23,7 +26,7 @@ class TitlebarView: UIView titleLabelView.layer.shadowOffset = CGSize(width: 0.0, height: 2.0) titleLabelView.font = UIFont.boldSystemFont(ofSize: 12.0) - backgroundImageView.alpha = 0.98 + backgroundView.alpha = 0.98 } func setTitle(_ title: String) { @@ -49,37 +52,10 @@ class TitlebarView: UIView } } - private func backgroundImageForSize(_ size: CGSize) -> UIImage? { - var image: UIImage? = nil - - UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0)) - if let context = UIGraphicsGetCurrentContext() { - let gradientColorsArray = [ - UIColor(red: 0.101, green: 0.176, blue: 0.415, alpha: 1.0).cgColor, - UIColor(red: 0.153, green: 0.000, blue: 0.153, alpha: 1.0).cgColor - ] - - if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: nil) { - context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) - } - - image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext(); - } - - return image - } - override func layoutSubviews() { super.layoutSubviews() - backgroundImageView.frame = bounds + backgroundView.frame = bounds titleLabelView.frame = bounds.avoiding(verticalInsets: safeAreaInsets).insetBy(dx: 8.0 + layoutMargins.left, dy: 0.0) - - if let image = backgroundImageView.image, image.size == bounds.size { - // No op - } else { - backgroundImageView.image = backgroundImageForSize(bounds.size) - } } } diff --git a/App/Titlebar and URL Bar/ToolbarButtonContainerView.swift b/App/Titlebar and URL Bar/ToolbarButtonContainerView.swift new file mode 100644 index 0000000..2af469e --- /dev/null +++ b/App/Titlebar and URL Bar/ToolbarButtonContainerView.swift @@ -0,0 +1,47 @@ +// +// ToolbarButtonContainerView.swift +// App +// +// Created by James Magahern on 8/14/20. +// + +import UIKit + +class ToolbarButtonContainerView: UIView +{ + private var buttonViews: [UIView] = [] + + public var numberOfButtonViews: Int { buttonViews.count } + + func addButtonView(_ button: UIView) { + buttonViews.append(button) + addSubview(button) + setNeedsLayout() + } + + func removeAllButtonViews() { + buttonViews.forEach { $0.removeFromSuperview() } + buttonViews.removeAll() + setNeedsLayout() + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + let width: CGFloat = buttonViews.reduce(0.0) { (result, button) -> CGFloat in + return result + button.sizeThatFits(size).width + } + + return CGSize(width: width, height: size.height) + } + + override func layoutSubviews() { + var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height)) + + for button in buttonViews { + let buttonSize = button.sizeThatFits(bounds.size) + buttonRect.size = CGSize(width: buttonSize.width, height: bounds.height) + button.frame = buttonRect + + buttonRect.origin.x += buttonRect.width + } + } +} diff --git a/App/Titlebar and URL Bar/ToolbarView.swift b/App/Titlebar and URL Bar/ToolbarView.swift new file mode 100644 index 0000000..58ecba3 --- /dev/null +++ b/App/Titlebar and URL Bar/ToolbarView.swift @@ -0,0 +1,115 @@ +// +// ToolbarView.swift +// App +// +// Created by James Magahern on 8/14/20. +// + +import UIKit + +class ToolbarView: UIView +{ + var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } } + + var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } } + + let containerView = UIView(frame: .zero) + let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) + let cancelButton = UIButton(type: .system) + + let leadingButtonsView = ToolbarButtonContainerView(frame: .zero) + let trailingButtonsView = ToolbarButtonContainerView(frame: .zero) + + convenience init() + { + self.init(frame: .zero) + addSubview(backgroundView) + addSubview(containerView) + + containerView.addSubview(leadingButtonsView) + containerView.addSubview(trailingButtonsView) + + cancelButton.setTitle("Cancel", for: .normal) + containerView.addSubview(cancelButton) + + layer.masksToBounds = false + layer.shadowColor = UIColor.black.cgColor + layer.shadowOpacity = 0.2 + layer.shadowOffset = CGSize(width: 0.0, height: 1.0) + layer.shadowRadius = 1.5 + } + + override func sizeThatFits(_ size: CGSize) -> CGSize + { + return CGSize(width: size.width, height: 44.0) + } + + override func layoutSubviews() + { + super.layoutSubviews() + + backgroundView.frame = bounds + + var containerBounds = bounds + containerBounds.size.height -= safeAreaInsets.bottom + containerView.frame = containerBounds + containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0) + + let buttonContainerInset = UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0) + + var urlBarPadding = CGFloat(8.0) + var urlBarInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) + + // Cancel button + var cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size) + cancelButtonSize.width += (urlBarPadding * 2) + cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0), + size: CGSize(width: cancelButtonSize.width + urlBarPadding, height: containerView.bounds.height)) + + // Leading toolbar buttons + if leadingButtonsView.numberOfButtonViews > 0 { + let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size) + leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize).inset(by: buttonContainerInset) + urlBarInsets.left = urlBarPadding + } else { + leadingButtonsView.frame = .zero + } + + // Trailing toolbar buttons + let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size) + trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width), y: 0), size: trailingContainerSize) + trailingButtonsView.frame = trailingButtonsView.frame.inset(by: buttonContainerInset) + urlBarInsets.right = urlBarPadding + + var avoidingSize: CGSize = .zero + if cancelButtonVisible { + cancelButton.alpha = 1.0 + trailingButtonsView.alpha = 0.0 + + avoidingSize = cancelButtonSize + } else { + cancelButton.alpha = 0.0 + trailingButtonsView.alpha = 1.0 + + avoidingSize = trailingContainerSize + } + + if let urlBar = urlBar { + let origin = CGPoint( + x: leadingButtonsView.frame.maxX, + y: 0.0 + ) + + urlBar.frame = CGRect( + origin: origin, + size: CGSize( + width: containerView.bounds.width - avoidingSize.width - origin.x, + height: trailingContainerSize.height + ) + ) + + urlBar.frame = urlBar.frame.inset(by: urlBarInsets) + } + } +} + diff --git a/App/Titlebar and URL Bar/ToolbarViewController.swift b/App/Titlebar and URL Bar/ToolbarViewController.swift index b944c24..31f10fc 100644 --- a/App/Titlebar and URL Bar/ToolbarViewController.swift +++ b/App/Titlebar and URL Bar/ToolbarViewController.swift @@ -7,154 +7,20 @@ import UIKit -class ToolbarButtonView: UIView -{ - private var buttonPadding = CGFloat(24.0) - private var buttonViews: [UIView] = [] - - public var numberOfButtonViews: Int { buttonViews.count } - - func addButtonView(_ button: UIView) { - buttonViews.append(button) - addSubview(button) - setNeedsLayout() - } - - func removeAllButtonViews() { - buttonViews.forEach { $0.removeFromSuperview() } - buttonViews.removeAll() - setNeedsLayout() - } - - override func sizeThatFits(_ size: CGSize) -> CGSize { - let width: CGFloat = buttonViews.reduce(buttonPadding / 2.0) { (result, button) -> CGFloat in - return result + button.sizeThatFits(size).width + buttonPadding - } - - return CGSize(width: width, height: size.height) - } - - override func layoutSubviews() { - var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height)) - - for button in buttonViews { - let buttonSize = button.sizeThatFits(bounds.size) - buttonRect.size = CGSize(width: buttonSize.width + buttonPadding, height: bounds.height) - button.frame = buttonRect - - buttonRect.origin.x += buttonRect.width - } - } -} - -class ToolbarView: UIView -{ - var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } } - - var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } } - - let containerView = UIView(frame: .zero) - let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) - let cancelButton = UIButton(type: .system) - - let leadingButtonsView = ToolbarButtonView(frame: .zero) - let trailingButtonsView = ToolbarButtonView(frame: .zero) - - convenience init() - { - self.init(frame: .zero) - addSubview(backgroundView) - addSubview(containerView) - - containerView.addSubview(leadingButtonsView) - containerView.addSubview(trailingButtonsView) - - cancelButton.setTitle("Cancel", for: .normal) - containerView.addSubview(cancelButton) - - layer.masksToBounds = false - layer.shadowColor = UIColor.black.cgColor - layer.shadowOpacity = 0.2 - layer.shadowOffset = CGSize(width: 0.0, height: 1.0) - layer.shadowRadius = 1.5 - } - - override func sizeThatFits(_ size: CGSize) -> CGSize - { - return CGSize(width: size.width, height: 44.0) - } - - override func layoutSubviews() - { - super.layoutSubviews() - - backgroundView.frame = bounds - - var containerBounds = bounds - containerBounds.size.height -= safeAreaInsets.bottom - containerView.frame = containerBounds - containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0) - - // Cancel button - let urlBarPadding: CGFloat = 8.0 - var cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size) - cancelButtonSize.width += (urlBarPadding * 2) - cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0), - size: CGSize(width: cancelButtonSize.width + urlBarPadding, height: containerView.bounds.height)) - - // Leading toolbar buttons - if leadingButtonsView.numberOfButtonViews > 0 { - let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size) - leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize) - } else { - leadingButtonsView.frame = .zero - } - - // Trailing toolbar buttons - let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size) - trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width) + urlBarPadding, y: 0), size: trailingContainerSize) - - var avoidingSize: CGSize = .zero - if cancelButtonVisible { - cancelButton.alpha = 1.0 - trailingButtonsView.alpha = 0.0 - - avoidingSize = cancelButtonSize - } else { - cancelButton.alpha = 0.0 - trailingButtonsView.alpha = 1.0 - - avoidingSize = trailingContainerSize - } - - if let urlBar = urlBar { - let origin = CGPoint( - x: leadingButtonsView.frame.maxX, - y: 0.0 - ) - - urlBar.frame = CGRect( - origin: origin, - size: CGSize( - width: containerView.bounds.width - avoidingSize.width - origin.x, - height: containerView.bounds.height - ) - ) - } - } -} - class ToolbarViewController: UIViewController { let urlBar = URLBar() let toolbarView = ToolbarView() let scriptControllerIconView = ScriptControllerIconView() - let shareButton = UIButton(frame: .zero) - let darkModeButton = UIButton(frame: .zero) - let windowButton = UIButton(frame: .zero) - let backButton = UIButton(frame: .zero) - let forwardButton = UIButton(frame: .zero) - let newTabButton = UIButton(frame: .zero) + + let shareButton = ReliefButton() + let darkModeButton = ReliefButton() + let windowButton = ReliefButton() + let newTabButton = ReliefButton() + + let backButton = ReliefButton() + let forwardButton = ReliefButton() + let navigationSegmentedButton = SegmentedReliefButton(children: []) var darkModeEnabled: Bool = false { didSet { @@ -189,6 +55,9 @@ class ToolbarViewController: UIViewController // Forward button forwardButton.setImage(UIImage(systemName: "chevron.right"), for: .normal) + // Navigation control + navigationSegmentedButton.children = [ backButton, forwardButton ] + // New tab button newTabButton.setImage(UIImage(systemName: "plus"), for: .normal) @@ -214,18 +83,27 @@ class ToolbarViewController: UIViewController toolbarView.leadingButtonsView.removeAllButtonViews() toolbarView.trailingButtonsView.removeAllButtonViews() + let spacerView = { () -> SpacerView in + SpacerView(space: 24.0) + } + // Setup toolbar based on trait collection if traitCollection.horizontalSizeClass == .compact { toolbarView.trailingButtonsView.addButtonView(darkModeButton) toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) toolbarView.trailingButtonsView.addButtonView(windowButton) } else { - toolbarView.leadingButtonsView.addButtonView(backButton) - toolbarView.leadingButtonsView.addButtonView(forwardButton) + toolbarView.leadingButtonsView.addButtonView(navigationSegmentedButton) + toolbarView.leadingButtonsView.addButtonView(spacerView()) + + toolbarView.trailingButtonsView.addButtonView(shareButton) + toolbarView.trailingButtonsView.addButtonView(spacerView()) toolbarView.trailingButtonsView.addButtonView(darkModeButton) - toolbarView.trailingButtonsView.addButtonView(shareButton) toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) + + toolbarView.trailingButtonsView.addButtonView(spacerView()) + toolbarView.trailingButtonsView.addButtonView(newTabButton) toolbarView.trailingButtonsView.addButtonView(windowButton) } @@ -239,3 +117,16 @@ class ToolbarViewController: UIViewController fatalError("init(coder:) has not been implemented") } } + +class SpacerView: UIView { + internal var space: CGFloat = 0 + + convenience init(space: CGFloat) { + self.init(frame: .zero) + self.space = space + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + CGSize(width: space, height: size.height) + } +} diff --git a/App/Titlebar and URL Bar/URLBar.swift b/App/Titlebar and URL Bar/URLBar.swift index 4317ea4..f4b9ef0 100644 --- a/App/Titlebar and URL Bar/URLBar.swift +++ b/App/Titlebar and URL Bar/URLBar.swift @@ -24,6 +24,7 @@ class URLBar: UIView } private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) + private let shadowView = UIView(frame: .zero) private let progressIndicatorView = ProgressIndicatorView() private let fadeMaskView = UIImageView(frame: .zero) @@ -40,7 +41,7 @@ class URLBar: UIView backgroundView.layer.masksToBounds = true backgroundView.layer.cornerRadius = backgroundCornerRadius backgroundView.layer.borderWidth = 1 - backgroundView.layer.borderColor = UIColor.systemFill.cgColor + backgroundView.layer.borderColor = UIColor.secondarySystemFill.cgColor backgroundView.isUserInteractionEnabled = false addSubview(backgroundView) @@ -166,6 +167,7 @@ class URLBar: UIView override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = bounds + shadowView.frame = bounds progressIndicatorView.frame = backgroundView.contentView.bounds textField.frame = bounds.insetBy(dx: 6.0, dy: 0) diff --git a/App/Utilities/CGPoint+Utils.swift b/App/Utilities/Geometry.swift similarity index 55% rename from App/Utilities/CGPoint+Utils.swift rename to App/Utilities/Geometry.swift index 7252388..504e1b7 100644 --- a/App/Utilities/CGPoint+Utils.swift +++ b/App/Utilities/Geometry.swift @@ -29,4 +29,26 @@ extension CGRect return rect } + + public func centeredX(inRect: CGRect) -> CGRect { + var rect = self + rect.origin.x = CGRound((inRect.width - rect.width) / 2.0) + + return rect + } + + public func centered(inRect: CGRect) -> CGRect { + self.centeredX(inRect: inRect).centeredY(inRect: inRect) + } +} + +extension CGSize +{ + public func extendingBy(dw: CGFloat, dh: CGFloat) -> CGSize { + var ourSize = self + ourSize.width += dw + ourSize.height += dh + + return ourSize + } } diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index 6dad96f..9e575f0 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A03810A24E71C5600826501 /* ToolbarButtonContainerView.swift */; }; + 1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A03810C24E71CA700826501 /* ToolbarView.swift */; }; + 1A03811024E71CF000826501 /* ReliefButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A03810F24E71CF000826501 /* ReliefButton.swift */; }; + 1A03811224E71EAA00826501 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A03811124E71EAA00826501 /* GradientView.swift */; }; + 1A03811424E73EB300826501 /* SegmentedReliefButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A03811324E73EB300826501 /* SegmentedReliefButton.swift */; }; 1A14FC2324D203D9009B3F83 /* TitlebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A14FC2224D203D9009B3F83 /* TitlebarView.swift */; }; 1A14FC2624D251BD009B3F83 /* darkmode.css in Resources */ = {isa = PBXBuildFile; fileRef = 1A14FC2524D251BD009B3F83 /* darkmode.css */; }; 1A14FC2824D26749009B3F83 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A14FC2724D26749009B3F83 /* Tab.swift */; }; @@ -25,7 +30,7 @@ 1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4A924C8D477006DC7AE /* SBRProcessPlugin.m */; }; 1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */; }; 1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */; }; - 1ADFF4C324CA6AF6006DC7AE /* CGPoint+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */; }; + 1ADFF4C324CA6AF6006DC7AE /* Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */; }; 1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */; }; 1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */; }; 1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */; }; @@ -58,6 +63,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1A03810A24E71C5600826501 /* ToolbarButtonContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonContainerView.swift; sourceTree = ""; }; + 1A03810C24E71CA700826501 /* ToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarView.swift; sourceTree = ""; }; + 1A03810F24E71CF000826501 /* ReliefButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReliefButton.swift; sourceTree = ""; }; + 1A03811124E71EAA00826501 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; + 1A03811324E73EB300826501 /* SegmentedReliefButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedReliefButton.swift; sourceTree = ""; }; 1A14FC2224D203D9009B3F83 /* TitlebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarView.swift; sourceTree = ""; }; 1A14FC2524D251BD009B3F83 /* darkmode.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = darkmode.css; sourceTree = ""; }; 1A14FC2724D26749009B3F83 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = ""; }; @@ -85,7 +95,7 @@ 1ADFF4AC24C8DFEE006DC7AE /* SBRWebProcessProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRWebProcessProxy.h; sourceTree = ""; }; 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcePolicyManager.swift; sourceTree = ""; }; 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBar.swift; sourceTree = ""; }; - 1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Utils.swift"; sourceTree = ""; }; + 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = ""; }; 1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Layout.swift"; sourceTree = ""; }; 1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarViewController.swift; sourceTree = ""; }; 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptControllerIconView.swift; sourceTree = ""; }; @@ -112,6 +122,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1A03810E24E71CCA00826501 /* Common UI */ = { + isa = PBXGroup; + children = ( + 1A03811124E71EAA00826501 /* GradientView.swift */, + 1A03810F24E71CF000826501 /* ReliefButton.swift */, + 1A03811324E73EB300826501 /* SegmentedReliefButton.swift */, + ); + path = "Common UI"; + sourceTree = ""; + }; 1A14FC2424D2517A009B3F83 /* Resources */ = { isa = PBXGroup; children = ( @@ -134,7 +154,9 @@ isa = PBXGroup; children = ( 1A14FC2224D203D9009B3F83 /* TitlebarView.swift */, + 1A03810C24E71CA700826501 /* ToolbarView.swift */, 1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */, + 1A03810A24E71C5600826501 /* ToolbarButtonContainerView.swift */, 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */, ); path = "Titlebar and URL Bar"; @@ -166,6 +188,7 @@ 1ADFF46124C7DE53006DC7AE /* SceneDelegate.swift */, 1ADFF47A24C7E176006DC7AE /* Backend */, 1ADFF47724C7DFE8006DC7AE /* Browser View */, + 1A03810E24E71CCA00826501 /* Common UI */, 1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */, 1AB88F0324D3E1EC0006F850 /* Tabs */, 1AB88F0424D3E1F90006F850 /* Titlebar and URL Bar */, @@ -238,7 +261,7 @@ 1ADFF4C124CA6AE4006DC7AE /* Utilities */ = { isa = PBXGroup; children = ( - 1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */, + 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */, 1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */, 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */, ); @@ -357,17 +380,22 @@ buildActionMask = 2147483647; files = ( 1ADFF46024C7DE53006DC7AE /* AppDelegate.swift in Sources */, + 1A03811424E73EB300826501 /* SegmentedReliefButton.swift in Sources */, + 1A03811024E71CF000826501 /* ReliefButton.swift in Sources */, + 1A03811224E71EAA00826501 /* GradientView.swift in Sources */, 1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */, 1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */, 1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */, 1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */, 1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */, + 1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */, 1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */, 1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */, 1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */, + 1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */, 1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */, 1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */, - 1ADFF4C324CA6AF6006DC7AE /* CGPoint+Utils.swift in Sources */, + 1ADFF4C324CA6AF6006DC7AE /* Geometry.swift in Sources */, 1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */, 1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */, 1A14FC2824D26749009B3F83 /* Tab.swift in Sources */,