<commit_before>//
//  BottomButtonScrollView.swift
//  Hakumai
//
//  Created by <NAME> on 2021/05/30.
//  Copyright © 2021 <NAME>. All rights reserved.
//

import Foundation
import Cocoa
import SnapKit

private let defaultButtonWidth = 32
private let buttonTopBottomOffset: CGFloat = 8
private let buttonRightOffset: CGFloat = 20

final class ButtonScrollView: NSScrollView {
    // MARK: - Properties
    private let topButton = NSButton()
    private let bottomButton = NSButton()
    private var isBoundsChangeObserving = false

    deinit {
        log.debug()
        NotificationCenter.default.removeObserver(
            self,
            name: NSView.boundsDidChangeNotification,
            object: nil)
    }
}

// MARK: - Public Functions
extension ButtonScrollView {
    func enableScrollButtons() {
        enableTopScrollButton()
        enableBottomScrollButton()
    }

    func enableTopScrollButton(width: Int? = defaultButtonWidth) {
        guard topButton.target == nil else { return }

        configureButtonAppearance(topButton, image: Asset.arrowUpwardBlack.image)
        topButton.target = self
        topButton.action = #selector(topButtonPressed)
        updateTopButtonVisibility()

        // Make sure we need to call `addSubview()` of `superview`, not of `self`.
        // Seems `self` and its descendants are not controlled by auto layout,
        // and it doesn't work for this case.
        superview?.addSubview(topButton)
        topButton.snp.makeConstraints { make in
            if let width = width { make.width.equalTo(width) }
            make.right.equalTo(self).offset(-buttonRightOffset)
            make.top.equalTo(self).offset(buttonTopBottomOffset + contentView.contentInsets.top)
        }
        addBoundsDidChangeNotificationObserver()
    }

    func enableBottomScrollButton(width: Int? = defaultButtonWidth) {
        guard bottomButton.target == nil else { return }

        configureButtonAppearance(bottomButton, image: Asset.arrowDownwardBlack.image)
        bottomButton.target = self
        bottomButton.action = #selector(bottomButtonPressed)
        updateBottomButtonVisibility()

        superview?.addSubview(bottomButton)
        bottomButton.snp.makeConstraints { make in
            if let width = width { make.width.equalTo(width) }
            make.right.equalTo(self).offset(-buttonRightOffset)
            make.bottom.equalTo(self).offset(-buttonTopBottomOffset)
        }
        addBoundsDidChangeNotificationObserver()
    }

    func updateTopButtonVisibility() {
        guard topButton.target != nil else { return }
        topButton.isHidden = isReachedToTop
    }

    func updateBottomButtonVisibility() {
        guard bottomButton.target != nil else { return }
        bottomButton.isHidden = isReachedToBottom
    }<commit_msg>refactor<commit_after>//
//  BottomButtonScrollView.swift
//  Hakumai
//
//  Created by <NAME> on 2021/05/30.
//  Copyright © 2021 <NAME>. All rights reserved.
//

import Foundation
import Cocoa
import SnapKit

private let defaultButtonWidth = 32
private let buttonTopBottomMargin: CGFloat = 8
private let buttonRightMargin: CGFloat = 20

final class ButtonScrollView: NSScrollView {
    // MARK: - Properties
    private let topButton = NSButton()
    private let bottomButton = NSButton()
    private var isBoundsChangeObserving = false

    deinit {
        log.debug()
        NotificationCenter.default.removeObserver(
            self,
            name: NSView.boundsDidChangeNotification,
            object: nil)
    }
}

// MARK: - Public Functions
extension ButtonScrollView {
    func enableScrollButtons() {
        enableTopScrollButton()
        enableBottomScrollButton()
    }

    func enableTopScrollButton(width: Int? = defaultButtonWidth) {
        guard topButton.target == nil else { return }

        configureButtonAppearance(topButton, image: Asset.arrowUpwardBlack.image)
        topButton.target = self
        topButton.action = #selector(topButtonPressed)
        updateTopButtonVisibility()

        // Make sure we need to call `addSubview()` of `superview`, not of `self`.
        // Seems `self` and its descendants are not controlled by auto layout,
        // and it doesn't work for this case.
        superview?.addSubview(topButton)
        topButton.snp.makeConstraints { make in
            if let width = width { make.width.equalTo(width) }
            make.right.equalTo(self).offset(-buttonRightMargin)
            make.top.equalTo(self).offset(buttonTopBottomMargin + contentView.contentInsets.top)
        }
        addBoundsDidChangeNotificationObserver()
    }

    func enableBottomScrollButton(width: Int? = defaultButtonWidth) {
        guard bottomButton.target == nil else { return }

        configureButtonAppearance(bottomButton, image: Asset.arrowDownwardBlack.image)
        bottomButton.target = self
        bottomButton.action = #selector(bottomButtonPressed)
        updateBottomButtonVisibility()

        superview?.addSubview(bottomButton)
        bottomButton.snp.makeConstraints { make in
            if let width = width { make.width.equalTo(width) }
            make.right.equalTo(self).offset(-buttonRightMargin)
            make.bottom.equalTo(self).offset(-buttonTopBottomMargin)
        }
        addBoundsDidChangeNotificationObserver()
    }

    func updateTopButtonVisibility() {
        guard topButton.target != nil else { return }
        topButton.isHidden = isReachedToTop
    }

    func updateBottomButtonVisibility() {
        guard bottomButton.target != nil else { return }
        bottomButton.isHidden = isReachedToBottom
    }
{'fraction_non_alphanumeric': 0.08482697426796805, 'fraction_numerical': 0.006033717834960071, 'mean_word_length': 2.8004045853000674, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, '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': '4436407', 'n_tokens_mistral': 1534, 'n_tokens_neox': 1468, 'n_words': 354}