<commit_before>//
// IconLabelView.swift
//
// Copyright © 2015 <NAME>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import UIKit

open class IconLabelView: XCView {
    public enum Style { case topBottom, leftRight }

    fileprivate var imagePaddingConstraints: [NSLayoutConstraint] = []
    fileprivate var imageSizeConstraints: (width: NSLayoutConstraint?, height: NSLayoutConstraint?)
    fileprivate var labelsWidthConstraints: [NSLayoutConstraint] = []
    fileprivate var stackViewConstraints: (topBottom: [NSLayoutConstraint]?, leftRight: [NSLayoutConstraint]?)

    // MARK: Subviews

    fileprivate let stackView           = UIStackView()
    fileprivate let textImageSpacerView = IntrinsicContentSizeView()
    public let imageViewContainer       = UIView()
    public let imageView                = UIImageView()
    public let titleLabel               = UILabel()
    public let subtitleLabel            = UILabel()

    /// The default value is `Style.topBottom`.
    open var style = Style.topBottom {
        didSet {
            guard oldValue != style else { return }
            apply(style: style)
        }
    }

    /// The default size is `55,55`.
    open dynamic var imageSize = CGSize(width: 55, height: 55) {
        didSet {
            imageSizeConstraints.width?.constant  = imageSize.width
            imageSizeConstraints.height?.constant = imageSize.height
        }
    }

    /// The space between image and text. The default value is `0`.
    open dynamic var textImageSpacing: CGFloat = 0 {
        didSet {
            updateTextImageSpacingIfNeeded()
        }
    }

    /// The default value is `0` which means size to fit.
    open dynamic var labelsWidth: CGFloat = 0 {
        didSet {
            guard oldValue != labelsWidth else { return }

            if labelsWidth == 0 {
                guard !labelsWidthConstraints.isEmpty else { return }
                labelsWidthConstraints.deactivate()
            } else {
                if labelsWidthConstraints.isEmpty {
                    labelsWidthConstraints.append(NSLayoutConstraint(item: titleLabel,    width: labelsWidth))
                    labelsWidthConstraints.append(NSLayoutConstraint(item: subtitleLabel, width: labelsWidth))
                }
                labelsWidthConstraints.forEach {
                    $0.constant = labelsWidth
                }
                labelsWidthConstraints.activate()
            }
        }
    }

    /// The default value is `8`.
    open dynamic var imagePadding: CGFloat = 8 {
        didSet {
            imageInset = UIEdgeInsets(imagePadding)
            imageView.cornerRadius = imageCornerRadius - imagePadding
        }
    }

    /// The distance that the view is inset from the enclosing content view.
    /// The default value is `UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)`.
    open dynamic var imageInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) {
        didSet {
            imagePaddingConstraints.at(0)?.constant = imageInset.top
            imagePaddingConstraints.at(1)?.constant = imageInset.bottom
            imagePaddingConstraints.at(2)?.constant = imageInset.left
            imagePaddingConstraints.at(3)?.constant = imageInset.right
        }
    }

    /// The default value is `13`.
    open dynamic var imageCornerRadius: CGFloat = 13 {
        didSet {
            imageViewContainer.cornerRadius = imageCornerRadius
        }
    }

    /// The default value is `false`.
    open dynamic var isRoundImageView = false {
        didSet {
            imageCornerRadius = isRoundImageView ? imageSize.height / 2 : 0
        }
    }

    /// The default value is `nil`, which results in a transparent background color.
    open dynamic var imageBackgroundColor: UIColor? = nil {
        didSet {
            imageViewContainer.backgroundColor = imageBackgroundColor
        }
    }

    /// The default value is `false`.
    open dynamic var isImageViewHidden: Bool = false {
        didSet {
            guard oldValue != isImageViewHidden else { return }

            if isImageViewHidden {
                stackView.removeArrangedSubview(imageViewContainer)
                imageViewContainer.removeFromSuperview()
            } else {
                stackView.insertArrangedSubview(imageViewContainer, at: 0)<commit_msg>Added isImageViewPrepended method to toggle image view placement.<commit_after>//
// IconLabelView.swift
//
// Copyright © 2015 <NAME>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import UIKit

open class IconLabelView: XCView {
    public enum Style { case topBottom, leftRight }

    fileprivate var imagePaddingConstraints: [NSLayoutConstraint] = []
    fileprivate var imageSizeConstraints: (width: NSLayoutConstraint?, height: NSLayoutConstraint?)
    fileprivate var labelsWidthConstraints: [NSLayoutConstraint] = []
    fileprivate var stackViewConstraints: (topBottom: [NSLayoutConstraint]?, leftRight: [NSLayoutConstraint]?)

    // MARK: Subviews

    fileprivate let stackView           = UIStackView()
    fileprivate let textImageSpacerView = IntrinsicContentSizeView()
    public let imageViewContainer       = UIView()
    public let imageView                = UIImageView()
    public let titleLabel               = UILabel()
    public let subtitleLabel            = UILabel()

    /// The default value is `Style.topBottom`.
    open var style = Style.topBottom {
        didSet {
            guard oldValue != style else { return }
            apply(style: style)
        }
    }

    /// The default size is `55,55`.
    open dynamic var imageSize = CGSize(width: 55, height: 55) {
        didSet {
            imageSizeConstraints.width?.constant  = imageSize.width
            imageSizeConstraints.height?.constant = imageSize.height
        }
    }

    /// The space between image and text. The default value is `0`.
    open dynamic var textImageSpacing: CGFloat = 0 {
        didSet {
            updateTextImageSpacingIfNeeded()
        }
    }

    /// The default value is `0` which means size to fit.
    open dynamic var labelsWidth: CGFloat = 0 {
        didSet {
            guard oldValue != labelsWidth else { return }

            if labelsWidth == 0 {
                guard !labelsWidthConstraints.isEmpty else { return }
                labelsWidthConstraints.deactivate()
            } else {
                if labelsWidthConstraints.isEmpty {
                    labelsWidthConstraints.append(NSLayoutConstraint(item: titleLabel,    width: labelsWidth))
                    labelsWidthConstraints.append(NSLayoutConstraint(item: subtitleLabel, width: labelsWidth))
                }
                labelsWidthConstraints.forEach {
                    $0.constant = labelsWidth
                }
                labelsWidthConstraints.activate()
            }
        }
    }

    /// The default value is `8`.
    open dynamic var imagePadding: CGFloat = 8 {
        didSet {
            imageInset = UIEdgeInsets(imagePadding)
            imageView.cornerRadius = imageCornerRadius - imagePadding
        }
    }

    /// The distance that the view is inset from the enclosing content view.
    /// The default value is `UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)`.
    open dynamic var imageInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) {
        didSet {
            imagePaddingConstraints.at(0)?.constant = imageInset.top
            imagePaddingConstraints.at(1)?.constant = imageInset.bottom
            imagePaddingConstraints.at(2)?.constant = imageInset.left
            imagePaddingConstraints.at(3)?.constant = imageInset.right
        }
    }

    /// The default value is `13`.
    open dynamic var imageCornerRadius: CGFloat = 13 {
        didSet {
            imageViewContainer.cornerRadius = imageCornerRadius
        }
    }

    /// The default value is `false`.
    open dynamic var isRoundImageView = false {
        didSet {
            imageCornerRadius = isRoundImageView ? imageSize.height / 2 : 0
        }
    }

    /// The default value is `nil`, which results in a transparent background color.
    open dynamic var imageBackgroundColor: UIColor? = nil {
        didSet {
            imageViewContainer.backgroundColor = imageBackgroundColor
        }
    }

    /// The default value is `true`.
    open dynamic var isImageViewPrepended: Bool = true {
        didSet {
            guard oldValue != isImageViewPrepended, !isImageViewHidden else { return }

            if isImageViewPrepended {
                stackView.moveArrangedSubviews(imageViewContainer, at: 0)
                stackView.moveArrangedSubviews(textImageSpacerView, at: 1)
            } else {
                var lastIndex: Int { return stackView.arrangedSubviews.count - 1 }
                stackView.moveArrangedSubviews(textImageSpacerView, at: lastIndex)
                stackView.moveArrangedSubviews(imageViewContainer, at: lastIndex)
            }

            updateTextImageSpacingIfNeeded()
        }
    }

    /// The default value is `false`.
    open dynamic var isImageViewHidden: Bool = false {
        didSet {
            guard oldValue != isImageViewHidden else { return }

            if isImageViewHidden {
                stackView.removeArrangedSubview(imageViewContainer)
                imageViewContainer.removeFromSuperview()
            } else {
                if isImageViewPrepended {
                    stackView.insertArrangedSubview(imageViewContainer, at: 0)
                    stackView.moveArrangedSubviews(textImageSpacerView, at: 1)
                } else {
                    var lastIndex: Int { return stackView.arrangedSubviews.count - 1 }
                    stackView.moveArrangedSubviews(textImageSpacerView, at: lastIndex)
                    stackView.insertArrangedSubview(imageViewContainer, at: lastIndex)
                }
{'fraction_non_alphanumeric': 0.07602779871054174, 'fraction_numerical': 0.006949677635434983, 'mean_word_length': 2.304925290536801, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '410523', 'n_tokens_mistral': 3103, 'n_tokens_neox': 2897, 'n_words': 994}