Signal-iOS/SignalUI/Appearance/ColorOrGradientSwatchView.swift
Evan Hahn 370ff654e7
Change license to AGPL
Change license to AGPL

This commit:

- Updates the `LICENSE` file

- Start every file with something like:

      // Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
      // SPDX-License-Identifier: AGPL-3.0-only

---

First, I removed existing license headers with this Ruby 3.1.2 script:

    require 'set'

    EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']

    same = 0
    different = 0

    all_files = `git ls-files`.lines.map { |line| line.strip }
    all_files.each do |relative_path|
      if relative_path == 'Pods'
        next
      end

      unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
        next
      end

      path = File.expand_path(relative_path)

      contents = File.read(path)
      new_contents = contents.sub(/\/\/\n\/\/  Copyright .*\n\/\/\n\n/, '')

      if contents == new_contents
        same += 1
      else
        different += 1
      end

      File.write(path, new_contents)
    end

    puts "updated #{different} file(s), left #{same} untouched"

I'm sure this script could be improved, but it worked well enough.

Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.

Then I fixed some stragglers and updated the precommit script.

See [a similar change in the Desktop app][0].

[0]: 8bfaf598af
2022-10-13 08:25:37 -05:00

169 lines
5.9 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalMessaging
// Compare with CVColorOrGradientView:
//
// * CVColorOrGradientView is intended to be used in CVC cells.
// It does not assume the gradient bounds corresponds to the
// view bounds.
// * ColorOrGradientSwatchView is for use elsewhere.
// It can pin gradient bounds to the edges of a circle for previews.
// It can be used to render wallpapers.
//
// Although we could combine these two views, these two scenarios are
// just different enough that its convenient to have two separate views.
public class ColorOrGradientSwatchView: ManualLayoutViewWithLayer {
public var setting: ColorOrGradientSetting {
didSet {
if setting != oldValue {
configure()
}
}
}
public enum ShapeMode {
case circle
case rectangle
}
private let shapeMode: ShapeMode
private let themeMode: ColorOrGradientThemeMode
private let gradientLayer = CAGradientLayer()
public init(setting: ColorOrGradientSetting,
shapeMode: ShapeMode,
themeMode: ColorOrGradientThemeMode = .auto) {
self.setting = setting
self.shapeMode = shapeMode
self.themeMode = themeMode
super.init(name: "ColorOrGradientSwatchView")
self.shouldDeactivateConstraints = false
configure()
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .ThemeDidChange, object: nil)
addLayoutBlock { view in
guard let view = view as? ColorOrGradientSwatchView else { return }
view.gradientLayer.frame = view.bounds
view.configure()
}
}
@available(swift, obsoleted: 1.0)
required init(name: String) {
owsFail("Do not use this initializer.")
}
@objc
private func themeDidChange() {
configure()
}
fileprivate struct State: Equatable {
let size: CGSize
let setting: ColorOrGradientSetting
}
private var state: State?
private func configure() {
let size = bounds.size
let newState = State(size: size, setting: setting)
// Exit early if the appearance and bounds haven't changed.
guard state != newState else {
return
}
self.state = newState
switch shapeMode {
case .circle:
self.layer.cornerRadius = size.smallerAxis * 0.5
self.clipsToBounds = true
case .rectangle:
self.layer.cornerRadius = 0
self.clipsToBounds = false
}
switch setting.asValue(themeMode: themeMode) {
case .transparent:
backgroundColor = nil
gradientLayer.removeFromSuperlayer()
case .solidColor(let color):
backgroundColor = color
gradientLayer.removeFromSuperlayer()
case .gradient(let color1, let color2, let angleRadians):
backgroundColor = nil
if gradientLayer.superlayer != self.layer {
gradientLayer.removeFromSuperlayer()
layer.addSublayer(gradientLayer)
}
/* The start and end points of the gradient when drawn into the layer's
* coordinate space. The start point corresponds to the first gradient
* stop, the end point to the last gradient stop. Both points are
* defined in a unit coordinate space that is then mapped to the
* layer's bounds rectangle when drawn. (I.e. [0,0] is the bottom-left
* corner of the layer, [1,1] is the top-right corner.) The default values
* are [.5,0] and [.5,1] respectively. Both are animatable. */
let unitCenter = CGPoint(x: 0.5, y: 0.5)
// Note the signs.
let startVector = CGPoint(x: +sin(angleRadians), y: -cos(angleRadians))
let startScale: CGFloat
switch shapeMode {
case .circle:
// In circle mode, we want the startPoint and endPoint to reside
// on the circumference of the circle.
startScale = 0.5
case .rectangle:
// In rectangle mode, we want the startPoint and endPoint to reside
// on the edge of the unit square, and thus edge of the rectangle.
// We therefore scale such that longer axis is a half unit.
let startSquareScale: CGFloat = max(abs(startVector.x), abs(startVector.y))
startScale = 0.5 / startSquareScale
}
let startPointUL = unitCenter + startVector * +startScale
// The endpoint should be "opposite" the start point, on the opposite edge of the view.
let endPointUL = unitCenter + startVector * -startScale
// UIKit/UIView uses an upper-left origin.
// Core Graphics/CALayer uses a lower-left origin.
func convertToLayerUnit(_ point: CGPoint) -> CGPoint {
// TODO: The documentation clearly indicates that
// CAGradientLayer.startPoint and endPoint use the layer's
// coordinate space with lower-left origin. But the
// observed behavior is that they use an upper-left origin.
// I can't figure out why.
//
// return CGPoint(x: point.x, y: (1 - point.y))
return point
}
let startPointLL = convertToLayerUnit(startPointUL)
let endPointLL = convertToLayerUnit(endPointUL)
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.frame = self.bounds
gradientLayer.startPoint = startPointLL
gradientLayer.endPoint = endPointLL
gradientLayer.colors = [
color1.cgColor,
color2.cgColor
]
CATransaction.commit()
}
}
}