From 3d6f9f143c75bcd9c52160f4c739ea69508aa94b Mon Sep 17 00:00:00 2001 From: N-HIROYASU Date: Sat, 16 Jul 2022 13:40:18 +0900 Subject: [PATCH 1/5] hide window --- scripts/mock-generate.sh | 2 +- slideover-for-macos.xcodeproj/project.pbxproj | 4 + slideover-for-macos/AppDelegate.swift | 4 + .../SlideOverWindowPresenter.swift | 53 +++++++++---- .../SlideOverWindowState.swift | 5 ++ .../SlideOverWindowUseCase.swift | 9 ++- .../Service/NotificationManager.swift | 1 + .../Service/SlideOverService.swift | 23 +++++- .../Service/UserSettingService.swift | 10 +++ .../UI/Base.lproj/Main.storyboard | 77 ++++++++++++++----- .../UI/Setting/SettingViewController.swift | 17 ++++ slideover-for-macos/UI/ja.lproj/Main.strings | 2 + .../Mocks/mock.generated.swift | 54 ++++++++----- .../SlideOverWindowPresenterTests.swift | 32 ++++---- .../SlideOverWindowUsecaseTests.swift | 2 +- 15 files changed, 220 insertions(+), 75 deletions(-) create mode 100644 slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift diff --git a/scripts/mock-generate.sh b/scripts/mock-generate.sh index 718cc96..e69cdeb 100644 --- a/scripts/mock-generate.sh +++ b/scripts/mock-generate.sh @@ -2,4 +2,4 @@ set -ue -binary/mockolo -s ./slideover-for-macos -d ./slideover-for-macosTests/Mocks/mock.generated.swift --testable-imports Fixture_in_Picture --enable-args-history +mockolo -s ./slideover-for-macos -d ./slideover-for-macosTests/Mocks/mock.generated.swift --testable-imports Fixture_in_Picture --enable-args-history diff --git a/slideover-for-macos.xcodeproj/project.pbxproj b/slideover-for-macos.xcodeproj/project.pbxproj index 7d1ae0a..a78ea52 100644 --- a/slideover-for-macos.xcodeproj/project.pbxproj +++ b/slideover-for-macos.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4F88368C2841DE32003C6333 /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F88368B2841DE32003C6333 /* WindowManager.swift */; }; 4F88368E2841E22D003C6333 /* ApplicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F88368D2841E22D003C6333 /* ApplicationService.swift */; }; 4F9248F1283A3B5D0059C7AC /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9248F0283A3B5D0059C7AC /* String+.swift */; }; + 4FB85F96288272E300D03E22 /* SlideOverWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */; }; 4FBCEAFA27C1FA3300BCF88C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */; }; 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */; }; 4FBCEAFE27C1FA3400BCF88C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFD27C1FA3400BCF88C /* Assets.xcassets */; }; @@ -79,6 +80,7 @@ 4F88368B2841DE32003C6333 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; 4F88368D2841E22D003C6333 /* ApplicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationService.swift; sourceTree = ""; }; 4F9248F0283A3B5D0059C7AC /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; + 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverWindowState.swift; sourceTree = ""; }; 4FBCEAF627C1FA3300BCF88C /* Fixture in Picture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Fixture in Picture.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverViewController.swift; sourceTree = ""; }; @@ -295,6 +297,7 @@ 4FBCEB3F27C63B6C00BCF88C /* SlideOverWindowAction.swift */, 4FBCEB4127C63B8D00BCF88C /* SlideOverWindowUseCase.swift */, 4FBCEB3C27C63A3000BCF88C /* SlideOverWindowPresenter.swift */, + 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */, ); path = SlideOverWindow; sourceTree = ""; @@ -492,6 +495,7 @@ 4FBE65862821601D0001FB4E /* SlideOverView.swift in Sources */, 4FBCEB3527C6285000BCF88C /* AppContainer.swift in Sources */, 4FBCEB2D27C626FD00BCF88C /* Injectable.swift in Sources */, + 4FB85F96288272E300D03E22 /* SlideOverWindowState.swift in Sources */, 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */, 4F88368A2841B8CB003C6333 /* FeaturePresentViewController.swift in Sources */, 4FBE658F282218E90001FB4E /* AntialiasedImageView.swift in Sources */, diff --git a/slideover-for-macos/AppDelegate.swift b/slideover-for-macos/AppDelegate.swift index 5f45d18..7716fc0 100644 --- a/slideover-for-macos/AppDelegate.swift +++ b/slideover-for-macos/AppDelegate.swift @@ -23,6 +23,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { mainWindowController.showWindow(self) } } + + func applicationDidUnhide(_ notification: Notification) { + notificationManager?.push(name: .displaySlideOver, param: nil) + } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift index 00ca8a1..7717d68 100644 --- a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift @@ -163,6 +163,7 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { output?.focusSearchBar() } + private var windowSizeBeforeHideCompletely: NSSize? func disappearWindow(completion: @escaping (Bool) -> Void) { guard let output = output, !output.isMiniaturized else { completion(false) @@ -174,23 +175,32 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { completion(false) return } - let isSuccess = self.slideOverService.hideWindow(for: window, type: position) - if isSuccess { - switch position { - case .left, .topLeft, .bottomLeft: - self.output?.contentView?.hideReappearLeftButton(completion: {}) - self.output?.contentView?.showReappearRightButton(completion: { [weak self] in - self?.output?.setWindowAlpha(0.4) - }) - case .right, .topRight, .bottomRight: - self.output?.contentView?.hideReappearRightButton(completion: {}) - self.output?.contentView?.showReappearLeftButton(completion: { [weak self] in - self?.output?.setWindowAlpha(0.4) - }) + + if self.userSetting.isCompletelyHideWindow { + self.windowSizeBeforeHideCompletely = window.frame.size + let isSuccess = self.slideOverService.hideWindowCompletely(for: window, type: position) + NSApplication.shared.hide(nil) + completion(isSuccess) + } else { + let isSuccess = self.slideOverService.hideWindowOnlyHalf(for: window, type: position) + if isSuccess { + switch position { + case .left, .topLeft, .bottomLeft: + self.output?.contentView?.hideReappearLeftButton(completion: {}) + self.output?.contentView?.showReappearRightButton(completion: { [weak self] in + self?.output?.setWindowAlpha(0.4) + }) + case .right, .topRight, .bottomRight: + self.output?.contentView?.hideReappearRightButton(completion: {}) + self.output?.contentView?.showReappearLeftButton(completion: { [weak self] in + self?.output?.setWindowAlpha(0.4) + }) + } } + completion(isSuccess) } - completion(isSuccess) } + } func appearWindow(completion: @escaping (Bool) -> Void) { @@ -202,12 +212,21 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { } output.fixWindow { [weak self] window in - guard let window = window else { + guard let self = self, let window = window else { completion(false) return } - self?.slideOverService.arrangeWindow(for: window, type: position) - self?.restoreHiddenWindow() + + if self.userSetting.isCompletelyHideWindow { + NSApplication.shared.unhide(nil) + if let windowSize = self.windowSizeBeforeHideCompletely { + self.slideOverService.arrangeWindow(for: window, windowSize: windowSize, type: position) + self.windowSizeBeforeHideCompletely = nil + } + } else { + self.slideOverService.arrangeWindow(for: window, type: position) + } + self.restoreHiddenWindow() completion(true) } } diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift new file mode 100644 index 0000000..c93bc40 --- /dev/null +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift @@ -0,0 +1,5 @@ +import Foundation + +struct SlideOverWindowState { + var isHidden: Bool +} diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift index df088af..fce9f7f 100644 --- a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift @@ -69,12 +69,13 @@ class SlideOverWindowInteractor: SlideOverWindowUseCase { observeHideWindowNotification() observeMouseEvent() observeZoomNotifications() + observeDisplaySlideOverNotification() setWillMoveNotification() setRightMouseUpSubject() resizeWindow() registerSwitchWindowVisibilityShortcutKey() - if let latestPosition = userSettingService.latestPosition, let latestWindowSize = userSettingService.latestWindowSize { + if let latestPosition = userSettingService.latestPosition, let latestWindowSize = userSettingService.latestWindowSize, latestWindowSize.width != 0 { presenter.initialArrangeWindow(type: latestPosition, size: latestWindowSize) } else if let latestPosition = userSettingService.latestPosition { presenter.fixWindow(type: latestPosition) @@ -309,4 +310,10 @@ extension SlideOverWindowInteractor { self?.presenter.resetZoom() } } + + private func observeDisplaySlideOverNotification() { + notificationManager.observe(name: .displaySlideOver) { [weak self] _ in + self?.requestAppearWindow() + } + } } diff --git a/slideover-for-macos/Service/NotificationManager.swift b/slideover-for-macos/Service/NotificationManager.swift index e01c1b9..5ed93fd 100644 --- a/slideover-for-macos/Service/NotificationManager.swift +++ b/slideover-for-macos/Service/NotificationManager.swift @@ -12,6 +12,7 @@ extension Notification.Name { static let zoomInWebView = Notification.Name("zoomInWebView") static let zoomOutWebView = Notification.Name("zoomOutWebView") static let zoomResetWebView = Notification.Name("zoomResetWebView") + static let displaySlideOver = Notification.Name("displaySlideOver") } /// @mockable diff --git a/slideover-for-macos/Service/SlideOverService.swift b/slideover-for-macos/Service/SlideOverService.swift index 8a11efa..6346662 100644 --- a/slideover-for-macos/Service/SlideOverService.swift +++ b/slideover-for-macos/Service/SlideOverService.swift @@ -35,7 +35,8 @@ protocol SlideOverService { func arrangeWindow(for window: NSWindow, type: SlideOverKind) func arrangeWindow(for window: NSWindow, windowSize: NSSize, type: SlideOverKind) func arrangeWindowPosition(for window: NSWindow, windowSize: NSSize, type: SlideOverKind) - func hideWindow(for window: NSWindow, type: SlideOverKind) -> Bool + func hideWindowOnlyHalf(for window: NSWindow, type: SlideOverKind) -> Bool + func hideWindowCompletely(for window: NSWindow, type: SlideOverKind) -> Bool } class SlideOverServiceImpl: SlideOverService { @@ -131,7 +132,7 @@ class SlideOverServiceImpl: SlideOverService { } } - func hideWindow(for window: NSWindow, type: SlideOverKind) -> Bool { + func hideWindowOnlyHalf(for window: NSWindow, type: SlideOverKind) -> Bool { switch type { case .left, .topLeft, .bottomLeft: let size = window.frame.size @@ -164,6 +165,24 @@ class SlideOverServiceImpl: SlideOverService { } } + func hideWindowCompletely(for window: NSWindow, type: SlideOverKind) -> Bool { + let prevSize = window.frame.size + let prevPoint = window.frame.origin + + switch type { + case .right, .topRight, .bottomRight: + let nextWindowPoint = NSPoint(x: prevPoint.x + prevSize.width, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: nextWindowPoint, size: NSSize(width: 0, height: prevSize.height)) + window.setFrame(nextWindowFrame, display: false, animate: true) + return true + case .left, .topLeft, .bottomLeft: + let nextWindowPoint = NSPoint(x: prevPoint.x, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: nextWindowPoint, size: NSSize(width: 0, height: prevSize.height)) + window.setFrame(nextWindowFrame, display: false, animate: true) + return true + } + } + private func canSetFrame(nextFrame: NSRect) -> Bool { // NOTE: 移動先のFrameが二つ以上のスクリーンに重なっており、Originがどのスクリーンにも含まれない場合、setFrameができない可能性あり let isIntersectsTwoScreen = NSScreen.screens.filter { screen in screen.frame.intersects(nextFrame) }.count >= 2 diff --git a/slideover-for-macos/Service/UserSettingService.swift b/slideover-for-macos/Service/UserSettingService.swift index 3ed9243..d2c7765 100644 --- a/slideover-for-macos/Service/UserSettingService.swift +++ b/slideover-for-macos/Service/UserSettingService.swift @@ -8,6 +8,7 @@ protocol UserSettingService { var latestWindowSize: NSSize? { get set } var latestUserAgent: UserAgent? { get set } var isNotAllowedGlobalShortcut: Bool { get set } + var isCompletelyHideWindow: Bool { get set } var latestShownFeatureVersion: String? { get set } } @@ -65,6 +66,15 @@ class UserSettingServiceImpl: UserSettingService { } } + var isCompletelyHideWindow: Bool { + get { + userDefaults.bool(forKey: "isCompletelyHideWindow") + } + set { + userDefaults.set(newValue, forKey: "isCompletelyHideWindow") + } + } + var latestShownFeatureVersion: String? { get { userDefaults.string(forKey: "latestShownFeatureVersion") diff --git a/slideover-for-macos/UI/Base.lproj/Main.storyboard b/slideover-for-macos/UI/Base.lproj/Main.storyboard index e119014..c828762 100644 --- a/slideover-for-macos/UI/Base.lproj/Main.storyboard +++ b/slideover-for-macos/UI/Base.lproj/Main.storyboard @@ -458,23 +458,63 @@ DQ - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -487,12 +527,13 @@ DQ - - + + + diff --git a/slideover-for-macos/UI/Setting/SettingViewController.swift b/slideover-for-macos/UI/Setting/SettingViewController.swift index 08cefc6..0ebfc65 100644 --- a/slideover-for-macos/UI/Setting/SettingViewController.swift +++ b/slideover-for-macos/UI/Setting/SettingViewController.swift @@ -3,6 +3,7 @@ import Cocoa class SettingViewController: NSViewController { @IBOutlet weak var hideWindowShortcutSwitchButton: NSSwitch! + @IBOutlet weak var showALittleWhenHideWindowSwitch: NSSwitch! var userSetting: UserSettingService? var globalShortcutService: GlobalShortcutService? var alertService: AlertService? @@ -13,9 +14,14 @@ class SettingViewController: NSViewController { userSetting = Injector.shared.buildSafe(UserSettingService.self) globalShortcutService = Injector.shared.buildSafe(GlobalShortcutService.self) alertService = Injector.shared.buildSafe(AlertService.self) + if let isNotAllow = userSetting?.isNotAllowedGlobalShortcut { hideWindowShortcutSwitchButton.state = isNotAllow ? .off : .on } + + if let isCompletelyHide = userSetting?.isCompletelyHideWindow { + showALittleWhenHideWindowSwitch.state = isCompletelyHide ? .off : .on + } } @IBAction func didTapHideWindowShortcutSwitch(_ sender: Any) { @@ -31,4 +37,15 @@ class SettingViewController: NSViewController { } } + + @IBAction func didTapShowALittleWhenHideWindowSwitch(_ sender: Any) { + switch showALittleWhenHideWindowSwitch.state { + case .on: + userSetting?.isCompletelyHideWindow = false + case .off: + userSetting?.isCompletelyHideWindow = true + default: + break + } + } } diff --git a/slideover-for-macos/UI/ja.lproj/Main.strings b/slideover-for-macos/UI/ja.lproj/Main.strings index ff5c28a..63de587 100644 --- a/slideover-for-macos/UI/ja.lproj/Main.strings +++ b/slideover-for-macos/UI/ja.lproj/Main.strings @@ -238,3 +238,5 @@ "SPt-53-dJI.title" = "ズームアウト"; "VZZ-xk-zcZ.title" = "ズームリセット"; + +"Df5-dj-PCz.title" = "「ウィンドウを隠す」をした時、一部分だけ表示しておく"; diff --git a/slideover-for-macosTests/Mocks/mock.generated.swift b/slideover-for-macosTests/Mocks/mock.generated.swift index 3ec7bf5..a8522f9 100644 --- a/slideover-for-macosTests/Mocks/mock.generated.swift +++ b/slideover-for-macosTests/Mocks/mock.generated.swift @@ -123,12 +123,12 @@ class SlideOverWindowActionMock: SlideOverWindowAction { } - private(set) var didTapHideWindowCallCount = 0 - var didTapHideWindowHandler: (() -> ())? + private(set) var didTaphideWindowOnlyHalfCallCount = 0 + var didTaphideWindowOnlyHalfHandler: (() -> ())? func didTapHideWindow() { - didTapHideWindowCallCount += 1 - if let didTapHideWindowHandler = didTapHideWindowHandler { - didTapHideWindowHandler() + didTaphideWindowOnlyHalfCallCount += 1 + if let didTaphideWindowOnlyHalfHandler = didTaphideWindowOnlyHalfHandler { + didTaphideWindowOnlyHalfHandler() } } @@ -229,13 +229,14 @@ class URLValidationServiceMock: URLValidationService { class UserSettingServiceMock: UserSettingService { init() { } - init(initialPage: URL? = nil, latestPage: URL? = nil, latestPosition: SlideOverKind? = nil, latestWindowSize: NSSize? = nil, latestUserAgent: UserAgent? = nil, isNotAllowedGlobalShortcut: Bool = false, latestShownFeatureVersion: String? = nil) { + init(initialPage: URL? = nil, latestPage: URL? = nil, latestPosition: SlideOverKind? = nil, latestWindowSize: NSSize? = nil, latestUserAgent: UserAgent? = nil, isNotAllowedGlobalShortcut: Bool = false, isCompletelyHideWindow: Bool = false, latestShownFeatureVersion: String? = nil) { self.initialPage = initialPage self.latestPage = latestPage self.latestPosition = latestPosition self.latestWindowSize = latestWindowSize self.latestUserAgent = latestUserAgent self.isNotAllowedGlobalShortcut = isNotAllowedGlobalShortcut + self.isCompletelyHideWindow = isCompletelyHideWindow self.latestShownFeatureVersion = latestShownFeatureVersion } @@ -258,6 +259,9 @@ class UserSettingServiceMock: UserSettingService { private(set) var isNotAllowedGlobalShortcutSetCallCount = 0 var isNotAllowedGlobalShortcut: Bool = false { didSet { isNotAllowedGlobalShortcutSetCallCount += 1 } } + private(set) var isCompletelyHideWindowSetCallCount = 0 + var isCompletelyHideWindow: Bool = false { didSet { isCompletelyHideWindowSetCallCount += 1 } } + private(set) var latestShownFeatureVersionSetCallCount = 0 var latestShownFeatureVersion: String? = nil { didSet { latestShownFeatureVersionSetCallCount += 1 } } } @@ -592,12 +596,12 @@ class SlideOverWebViewMenuDelegateMock: SlideOverWebViewMenuDelegate { } - private(set) var didTapHideWindowCallCount = 0 - var didTapHideWindowHandler: (() -> ())? + private(set) var didTaphideWindowOnlyHalfCallCount = 0 + var didTaphideWindowOnlyHalfHandler: (() -> ())? func didTapHideWindow() { - didTapHideWindowCallCount += 1 - if let didTapHideWindowHandler = didTapHideWindowHandler { - didTapHideWindowHandler() + didTaphideWindowOnlyHalfCallCount += 1 + if let didTaphideWindowOnlyHalfHandler = didTaphideWindowOnlyHalfHandler { + didTaphideWindowOnlyHalfHandler() } } @@ -1219,14 +1223,26 @@ class SlideOverServiceMock: SlideOverService { } - private(set) var hideWindowCallCount = 0 - var hideWindowArgValues = [(NSWindow, SlideOverKind)]() - var hideWindowHandler: ((NSWindow, SlideOverKind) -> (Bool))? - func hideWindow(for window: NSWindow, type: SlideOverKind) -> Bool { - hideWindowCallCount += 1 - hideWindowArgValues.append((window, type)) - if let hideWindowHandler = hideWindowHandler { - return hideWindowHandler(window, type) + private(set) var hideWindowOnlyHalfCallCount = 0 + var hideWindowOnlyHalfArgValues = [(NSWindow, SlideOverKind)]() + var hideWindowOnlyHalfHandler: ((NSWindow, SlideOverKind) -> (Bool))? + func hideWindowOnlyHalf(for window: NSWindow, type: SlideOverKind) -> Bool { + hideWindowOnlyHalfCallCount += 1 + hideWindowOnlyHalfArgValues.append((window, type)) + if let hideWindowOnlyHalfHandler = hideWindowOnlyHalfHandler { + return hideWindowOnlyHalfHandler(window, type) + } + return false + } + + private(set) var hideWindowCompletelyCallCount = 0 + var hideWindowCompletelyArgValues = [(NSWindow, SlideOverKind)]() + var hideWindowCompletelyHandler: ((NSWindow, SlideOverKind) -> (Bool))? + func hideWindowCompletely(for window: NSWindow, type: SlideOverKind) -> Bool { + hideWindowCompletelyCallCount += 1 + hideWindowCompletelyArgValues.append((window, type)) + if let hideWindowCompletelyHandler = hideWindowCompletelyHandler { + return hideWindowCompletelyHandler(window, type) } return false } diff --git a/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowPresenterTests.swift b/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowPresenterTests.swift index 21f1b7f..2d8e0e8 100644 --- a/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowPresenterTests.swift +++ b/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowPresenterTests.swift @@ -166,7 +166,7 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "right") { _ in setUp() userSetting.latestPosition = .right - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearLeftButtonHandler = { $0() } @@ -174,7 +174,7 @@ class SlideOverWindowPresenterTests: XCTestCase { subject.disappearWindow { isSuccess = $0 } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 1) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 1) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 0) @@ -186,14 +186,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "topRight") { _ in setUp() userSetting.latestPosition = .topRight - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearLeftButtonHandler = { $0() } subject.disappearWindow { _ in } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 1) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 1) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 0) @@ -204,14 +204,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "bottomRight") { _ in setUp() userSetting.latestPosition = .bottomRight - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearLeftButtonHandler = { $0() } subject.disappearWindow { _ in } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 1) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 1) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 0) @@ -226,14 +226,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "left") { _ in setUp() userSetting.latestPosition = .left - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearRightButtonHandler = { $0() } subject.disappearWindow { _ in } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 0) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 0) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 1) @@ -244,14 +244,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "topLeft") { _ in setUp() userSetting.latestPosition = .topLeft - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearRightButtonHandler = { $0() } subject.disappearWindow { _ in } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 0) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 0) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 1) @@ -262,14 +262,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "bottomLeft") { _ in setUp() userSetting.latestPosition = .bottomLeft - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = false contentView.showReappearRightButtonHandler = { $0() } subject.disappearWindow { _ in } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 0) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 0) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 1) @@ -282,14 +282,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "hideWindowに失敗したら何もしないこと") { _ in setUp() userSetting.latestPosition = .bottomLeft - slideOverService.hideWindowHandler = { _, _ in false } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in false } output.isMiniaturized = false var isSuccess: Bool? subject.disappearWindow { isSuccess = $0 } XCTAssertEqual(output.fixWindowCallCount, 1) - XCTAssertEqual(slideOverService.hideWindowCallCount, 1) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 1) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 0) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 0) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 0) @@ -300,14 +300,14 @@ class SlideOverWindowPresenterTests: XCTestCase { XCTContext.runActivity(named: "ウィンドウが最小されているとき") { _ in setUp() userSetting.latestPosition = .bottomLeft - slideOverService.hideWindowHandler = { _, _ in true } + slideOverService.hideWindowOnlyHalfHandler = { _, _ in true } output.isMiniaturized = true var isSuccess: Bool? subject.disappearWindow { isSuccess = $0 } XCTAssertEqual(output.fixWindowCallCount, 0) - XCTAssertEqual(slideOverService.hideWindowCallCount, 0) + XCTAssertEqual(slideOverService.hideWindowOnlyHalfCallCount, 0) XCTAssertEqual(contentView.hideReappearRightButtonCallCount, 0) XCTAssertEqual(contentView.showReappearLeftButtonCallCount, 0) XCTAssertEqual(contentView.showReappearRightButtonCallCount, 0) diff --git a/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowUsecaseTests.swift b/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowUsecaseTests.swift index fea0f9a..cd11366 100644 --- a/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowUsecaseTests.swift +++ b/slideover-for-macosTests/Module/SlideOverWindow/SlideOverWindowUsecaseTests.swift @@ -49,7 +49,7 @@ class SlideOverWindowUseCaseTests: XCTestCase { setUp() subject.setUp() - XCTAssertEqual(notificationManager.observeCallCount, 9) + XCTAssertEqual(notificationManager.observeCallCount, 10) XCTAssertEqual(notificationManager.observeArgValues.first, .reload) XCTAssertEqual(notificationManager.observeArgValues[1], .clearCache) XCTAssertEqual(notificationManager.observeArgValues[2], .openUrl) From 4e0de2a8c20d0cc4d526b8636f3f08c6ede9d7d1 Mon Sep 17 00:00:00 2001 From: N-HIROYASU Date: Sun, 17 Jul 2022 12:22:48 +0900 Subject: [PATCH 2/5] refactor v1 --- slideover-for-macos.xcodeproj/project.pbxproj | 103 ++++++--- .../xcshareddata/swiftpm/Package.resolved | 9 + slideover-for-macos/AppContainer.swift | 32 +++ slideover-for-macos/AppDelegate.swift | 19 +- .../Coordinator/Coordinator.swift | 10 + .../Coordinator/SlideOverCoordinator.swift | 48 ++++ .../Coordinator/Transitionable.swift | 20 ++ slideover-for-macos/DI/AppContainer.swift | 35 --- slideover-for-macos/DI/Container+.swift | 19 -- slideover-for-macos/DI/Dependencies.swift | 8 - slideover-for-macos/DI/Injectable.swift | 7 - slideover-for-macos/DI/Injector.swift | 28 --- slideover-for-macos/DI/SwinjectInjector.swift | 19 -- .../Module/SlideOver/SlideOverAction.swift | 144 ++++++++++++ .../SlideOverNotificationObserver.swift | 145 ++++++++++++ .../Module/SlideOver/SlideOverPresenter.swift | 83 +++++++ .../Module/SlideOver/SlideOverState.swift | 15 ++ .../Module/SlideOver/SlideOverUseCase.swift | 210 ++++++++++++++++++ .../SlideOverWindowAction.swift | 1 + .../SlideOverWindowPresenter.swift | 13 +- .../SlideOverWindowUseCase.swift | 9 +- .../Service/ScreenManager.swift | 21 ++ .../Service/SlideOverComputable.swift | 8 +- .../Service/SlideOverComputation.swift | 176 +++++++++++++++ .../Service/SlideOverService.swift | 1 + .../FeaturePresentViewController.swift | 1 + .../UI/Setting/SettingViewController.swift | 1 + .../UI/SlideOverViewController.swift | 41 ++-- .../UI/SlideOverWindowController.swift | 122 +++++++--- .../UI/Util/SlideOverWebView.swift | 16 +- 30 files changed, 1143 insertions(+), 221 deletions(-) create mode 100644 slideover-for-macos/AppContainer.swift create mode 100644 slideover-for-macos/Coordinator/Coordinator.swift create mode 100644 slideover-for-macos/Coordinator/SlideOverCoordinator.swift create mode 100644 slideover-for-macos/Coordinator/Transitionable.swift delete mode 100644 slideover-for-macos/DI/AppContainer.swift delete mode 100644 slideover-for-macos/DI/Container+.swift delete mode 100644 slideover-for-macos/DI/Dependencies.swift delete mode 100644 slideover-for-macos/DI/Injectable.swift delete mode 100644 slideover-for-macos/DI/Injector.swift delete mode 100644 slideover-for-macos/DI/SwinjectInjector.swift create mode 100644 slideover-for-macos/Module/SlideOver/SlideOverAction.swift create mode 100644 slideover-for-macos/Module/SlideOver/SlideOverNotificationObserver.swift create mode 100644 slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift create mode 100644 slideover-for-macos/Module/SlideOver/SlideOverState.swift create mode 100644 slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift create mode 100644 slideover-for-macos/Service/ScreenManager.swift create mode 100644 slideover-for-macos/Service/SlideOverComputation.swift diff --git a/slideover-for-macos.xcodeproj/project.pbxproj b/slideover-for-macos.xcodeproj/project.pbxproj index a78ea52..9706fc5 100644 --- a/slideover-for-macos.xcodeproj/project.pbxproj +++ b/slideover-for-macos.xcodeproj/project.pbxproj @@ -13,6 +13,17 @@ 4F88368E2841E22D003C6333 /* ApplicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F88368D2841E22D003C6333 /* ApplicationService.swift */; }; 4F9248F1283A3B5D0059C7AC /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9248F0283A3B5D0059C7AC /* String+.swift */; }; 4FB85F96288272E300D03E22 /* SlideOverWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */; }; + 4FB85F9928827BDE00D03E22 /* SlideOverAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F9828827BDE00D03E22 /* SlideOverAction.swift */; }; + 4FB85F9B28827BE700D03E22 /* SlideOverUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F9A28827BE700D03E22 /* SlideOverUseCase.swift */; }; + 4FB85F9D28827BEC00D03E22 /* SlideOverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F9C28827BEC00D03E22 /* SlideOverPresenter.swift */; }; + 4FB85F9F28827BF100D03E22 /* SlideOverState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85F9E28827BF100D03E22 /* SlideOverState.swift */; }; + 4FB85FA12882851000D03E22 /* SlideOverNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FA02882851000D03E22 /* SlideOverNotificationObserver.swift */; }; + 4FB85FA32882908E00D03E22 /* SlideOverComputation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FA22882908E00D03E22 /* SlideOverComputation.swift */; }; + 4FB85FA5288297AE00D03E22 /* ScreenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FA4288297AE00D03E22 /* ScreenManager.swift */; }; + 4FB85FA82882A06200D03E22 /* SlideOverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FA72882A06200D03E22 /* SlideOverCoordinator.swift */; }; + 4FB85FAB2882A11400D03E22 /* Injectable in Frameworks */ = {isa = PBXBuildFile; productRef = 4FB85FAA2882A11400D03E22 /* Injectable */; }; + 4FB85FAD2882A1EE00D03E22 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */; }; + 4FB85FAF2882A20600D03E22 /* Transitionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FAE2882A20600D03E22 /* Transitionable.swift */; }; 4FBCEAFA27C1FA3300BCF88C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */; }; 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */; }; 4FBCEAFE27C1FA3400BCF88C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFD27C1FA3400BCF88C /* Assets.xcassets */; }; @@ -25,12 +36,7 @@ 4FBCEB1A27C27EC800BCF88C /* slideover_for_macosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB1927C27EC800BCF88C /* slideover_for_macosTests.swift */; }; 4FBCEB2127C27EE200BCF88C /* MousePointServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB2027C27EE200BCF88C /* MousePointServiceTests.swift */; }; 4FBCEB2327C6244900BCF88C /* UserSettingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB2227C6244900BCF88C /* UserSettingService.swift */; }; - 4FBCEB2D27C626FD00BCF88C /* Injectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB2C27C626FD00BCF88C /* Injectable.swift */; }; - 4FBCEB2F27C6273800BCF88C /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB2E27C6273800BCF88C /* Dependencies.swift */; }; - 4FBCEB3127C627C600BCF88C /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3027C627C600BCF88C /* Injector.swift */; }; - 4FBCEB3327C6282200BCF88C /* SwinjectInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3227C6282200BCF88C /* SwinjectInjector.swift */; }; 4FBCEB3527C6285000BCF88C /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3427C6285000BCF88C /* AppContainer.swift */; }; - 4FBCEB3727C62A4000BCF88C /* Container+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3627C62A4000BCF88C /* Container+.swift */; }; 4FBCEB3A27C62B6000BCF88C /* Swinject in Frameworks */ = {isa = PBXBuildFile; productRef = 4FBCEB3927C62B6000BCF88C /* Swinject */; }; 4FBCEB3D27C63A3000BCF88C /* SlideOverWindowPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3C27C63A3000BCF88C /* SlideOverWindowPresenter.swift */; }; 4FBCEB4027C63B6C00BCF88C /* SlideOverWindowAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEB3F27C63B6C00BCF88C /* SlideOverWindowAction.swift */; }; @@ -81,6 +87,16 @@ 4F88368D2841E22D003C6333 /* ApplicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationService.swift; sourceTree = ""; }; 4F9248F0283A3B5D0059C7AC /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverWindowState.swift; sourceTree = ""; }; + 4FB85F9828827BDE00D03E22 /* SlideOverAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverAction.swift; sourceTree = ""; }; + 4FB85F9A28827BE700D03E22 /* SlideOverUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverUseCase.swift; sourceTree = ""; }; + 4FB85F9C28827BEC00D03E22 /* SlideOverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverPresenter.swift; sourceTree = ""; }; + 4FB85F9E28827BF100D03E22 /* SlideOverState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverState.swift; sourceTree = ""; }; + 4FB85FA02882851000D03E22 /* SlideOverNotificationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverNotificationObserver.swift; sourceTree = ""; }; + 4FB85FA22882908E00D03E22 /* SlideOverComputation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverComputation.swift; sourceTree = ""; }; + 4FB85FA4288297AE00D03E22 /* ScreenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenManager.swift; sourceTree = ""; }; + 4FB85FA72882A06200D03E22 /* SlideOverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverCoordinator.swift; sourceTree = ""; }; + 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + 4FB85FAE2882A20600D03E22 /* Transitionable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transitionable.swift; sourceTree = ""; }; 4FBCEAF627C1FA3300BCF88C /* Fixture in Picture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Fixture in Picture.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverViewController.swift; sourceTree = ""; }; @@ -96,12 +112,7 @@ 4FBCEB1927C27EC800BCF88C /* slideover_for_macosTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = slideover_for_macosTests.swift; sourceTree = ""; }; 4FBCEB2027C27EE200BCF88C /* MousePointServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MousePointServiceTests.swift; sourceTree = ""; }; 4FBCEB2227C6244900BCF88C /* UserSettingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingService.swift; sourceTree = ""; }; - 4FBCEB2C27C626FD00BCF88C /* Injectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injectable.swift; sourceTree = ""; }; - 4FBCEB2E27C6273800BCF88C /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; - 4FBCEB3027C627C600BCF88C /* Injector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injector.swift; sourceTree = ""; }; - 4FBCEB3227C6282200BCF88C /* SwinjectInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwinjectInjector.swift; sourceTree = ""; }; 4FBCEB3427C6285000BCF88C /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContainer.swift; sourceTree = ""; }; - 4FBCEB3627C62A4000BCF88C /* Container+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Container+.swift"; sourceTree = ""; }; 4FBCEB3C27C63A3000BCF88C /* SlideOverWindowPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverWindowPresenter.swift; sourceTree = ""; }; 4FBCEB3F27C63B6C00BCF88C /* SlideOverWindowAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverWindowAction.swift; sourceTree = ""; }; 4FBCEB4127C63B8D00BCF88C /* SlideOverWindowUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverWindowUseCase.swift; sourceTree = ""; }; @@ -144,6 +155,7 @@ 4FBE657C282122C60001FB4E /* Magnet in Frameworks */, 4FBCEB0C27C1FB1D00BCF88C /* WebKit.framework in Frameworks */, 4FBCEB3A27C62B6000BCF88C /* Swinject in Frameworks */, + 4FB85FAB2882A11400D03E22 /* Injectable in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -174,6 +186,28 @@ path = FeaturePresent; sourceTree = ""; }; + 4FB85F9728827BCF00D03E22 /* SlideOver */ = { + isa = PBXGroup; + children = ( + 4FB85F9828827BDE00D03E22 /* SlideOverAction.swift */, + 4FB85F9A28827BE700D03E22 /* SlideOverUseCase.swift */, + 4FB85F9C28827BEC00D03E22 /* SlideOverPresenter.swift */, + 4FB85F9E28827BF100D03E22 /* SlideOverState.swift */, + 4FB85FA02882851000D03E22 /* SlideOverNotificationObserver.swift */, + ); + path = SlideOver; + sourceTree = ""; + }; + 4FB85FA62882A05100D03E22 /* Coordinator */ = { + isa = PBXGroup; + children = ( + 4FB85FA72882A06200D03E22 /* SlideOverCoordinator.swift */, + 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */, + 4FB85FAE2882A20600D03E22 /* Transitionable.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; 4FBCEAED27C1FA3300BCF88C = { isa = PBXGroup; children = ( @@ -200,11 +234,12 @@ 4FBCEB4C27CA1AC400BCF88C /* Info.plist */, 4F8836852841B82A003C6333 /* Util */, 4FBCEB5227CBBC0200BCF88C /* Entity */, - 4FBCEB2B27C6262400BCF88C /* DI */, + 4FB85FA62882A05100D03E22 /* Coordinator */, 4FBCEB2A27C625DB00BCF88C /* UI */, 4FBCEB3B27C63A2300BCF88C /* Module */, 4FBCEB2927C625C900BCF88C /* Service */, 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */, + 4FBCEB3427C6285000BCF88C /* AppContainer.swift */, 4FBCEAFD27C1FA3400BCF88C /* Assets.xcassets */, 4FBCEB0227C1FA3400BCF88C /* slideover_for_macos.entitlements */, 4FBCEB5027CBAE5600BCF88C /* WKPreferences.m */, @@ -241,6 +276,7 @@ 4FBCEB2227C6244900BCF88C /* UserSettingService.swift */, 4FBCEB0F27C2709F00BCF88C /* SlideOverComputable.swift */, 4FBCEB0D27C20BBA00BCF88C /* SlideOverService.swift */, + 4FB85FA22882908E00D03E22 /* SlideOverComputation.swift */, 4FBCEB4627C680A400BCF88C /* AlearService.swift */, 4FBCEB4827C6810300BCF88C /* URLValidationService.swift */, 4FBCEB4A27C7C8C100BCF88C /* URLEncodeService.swift */, @@ -249,6 +285,7 @@ 4FBE657D282124410001FB4E /* ShortcutService.swift */, 4F88368B2841DE32003C6333 /* WindowManager.swift */, 4F88368D2841E22D003C6333 /* ApplicationService.swift */, + 4FB85FA4288297AE00D03E22 /* ScreenManager.swift */, ); path = Service; sourceTree = ""; @@ -270,22 +307,10 @@ path = UI; sourceTree = ""; }; - 4FBCEB2B27C6262400BCF88C /* DI */ = { - isa = PBXGroup; - children = ( - 4FBCEB2C27C626FD00BCF88C /* Injectable.swift */, - 4FBCEB2E27C6273800BCF88C /* Dependencies.swift */, - 4FBCEB3027C627C600BCF88C /* Injector.swift */, - 4FBCEB3227C6282200BCF88C /* SwinjectInjector.swift */, - 4FBCEB3427C6285000BCF88C /* AppContainer.swift */, - 4FBCEB3627C62A4000BCF88C /* Container+.swift */, - ); - path = DI; - sourceTree = ""; - }; 4FBCEB3B27C63A2300BCF88C /* Module */ = { isa = PBXGroup; children = ( + 4FB85F9728827BCF00D03E22 /* SlideOver */, 4FBCEB3E27C63B5900BCF88C /* SlideOverWindow */, ); path = Module; @@ -400,6 +425,7 @@ packageProductDependencies = ( 4FBCEB3927C62B6000BCF88C /* Swinject */, 4FBE657B282122C60001FB4E /* Magnet */, + 4FB85FAA2882A11400D03E22 /* Injectable */, ); productName = "slideover-for-macos"; productReference = 4FBCEAF627C1FA3300BCF88C /* Fixture in Picture.app */; @@ -456,6 +482,7 @@ packageReferences = ( 4FBCEB3827C62B6000BCF88C /* XCRemoteSwiftPackageReference "Swinject" */, 4FBE657A282122C60001FB4E /* XCRemoteSwiftPackageReference "Magnet" */, + 4FB85FA92882A11400D03E22 /* XCRemoteSwiftPackageReference "Injectable" */, ); productRefGroup = 4FBCEAF727C1FA3300BCF88C /* Products */; projectDirPath = ""; @@ -494,34 +521,39 @@ files = ( 4FBE65862821601D0001FB4E /* SlideOverView.swift in Sources */, 4FBCEB3527C6285000BCF88C /* AppContainer.swift in Sources */, - 4FBCEB2D27C626FD00BCF88C /* Injectable.swift in Sources */, 4FB85F96288272E300D03E22 /* SlideOverWindowState.swift in Sources */, 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */, 4F88368A2841B8CB003C6333 /* FeaturePresentViewController.swift in Sources */, + 4FB85F9D28827BEC00D03E22 /* SlideOverPresenter.swift in Sources */, + 4FB85FA5288297AE00D03E22 /* ScreenManager.swift in Sources */, 4FBE658F282218E90001FB4E /* AntialiasedImageView.swift in Sources */, 4FEBB853282282F7007B5DA3 /* SettingViewController.swift in Sources */, 4FBCEB1027C2709F00BCF88C /* SlideOverComputable.swift in Sources */, - 4FBCEB2F27C6273800BCF88C /* Dependencies.swift in Sources */, 4FBCEB4727C680A400BCF88C /* AlearService.swift in Sources */, 4FBCEB4227C63B8D00BCF88C /* SlideOverWindowUseCase.swift in Sources */, 4FBE657E282124410001FB4E /* ShortcutService.swift in Sources */, 4FD65B9727F02BAF0021D3E5 /* MenuTree.swift in Sources */, 4F88368E2841E22D003C6333 /* ApplicationService.swift in Sources */, 4F9248F1283A3B5D0059C7AC /* String+.swift in Sources */, + 4FB85F9F28827BF100D03E22 /* SlideOverState.swift in Sources */, 4FEBB83028224183007B5DA3 /* UIQueue.swift in Sources */, - 4FBCEB3327C6282200BCF88C /* SwinjectInjector.swift in Sources */, + 4FB85FA12882851000D03E22 /* SlideOverNotificationObserver.swift in Sources */, + 4FB85FAF2882A20600D03E22 /* Transitionable.swift in Sources */, + 4FB85FA82882A06200D03E22 /* SlideOverCoordinator.swift in Sources */, 4FBCEB0927C1FA5600BCF88C /* SlideOverWindowController.swift in Sources */, + 4FB85F9B28827BE700D03E22 /* SlideOverUseCase.swift in Sources */, 4F8836882841B8A8003C6333 /* FeaturePresentWindowController.swift in Sources */, - 4FBCEB3127C627C600BCF88C /* Injector.swift in Sources */, + 4FB85F9928827BDE00D03E22 /* SlideOverAction.swift in Sources */, 4FBCEAFA27C1FA3300BCF88C /* AppDelegate.swift in Sources */, 4FBCEB5427CBBC2500BCF88C /* UserAgent.swift in Sources */, 4FBCEB1227C27B7700BCF88C /* MousePositionService.swift in Sources */, 4FBCEB4927C6810300BCF88C /* URLValidationService.swift in Sources */, - 4FBCEB3727C62A4000BCF88C /* Container+.swift in Sources */, + 4FB85FAD2882A1EE00D03E22 /* Coordinator.swift in Sources */, 4FBE658428215A540001FB4E /* SlideOverToolbar.swift in Sources */, 4FBCEB5127CBAE5600BCF88C /* WKPreferences.m in Sources */, 4FBCEB4427C667EF00BCF88C /* SlideOverWebView.swift in Sources */, 4FEBB8512822813F007B5DA3 /* SettingWindowController.swift in Sources */, + 4FB85FA32882908E00D03E22 /* SlideOverComputation.swift in Sources */, 4FBCEB5627D3259A00BCF88C /* WebViewService.swift in Sources */, 4FBCEB2327C6244900BCF88C /* UserSettingService.swift in Sources */, 4FBCEB0E27C20BBA00BCF88C /* SlideOverService.swift in Sources */, @@ -839,6 +871,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 4FB85FA92882A11400D03E22 /* XCRemoteSwiftPackageReference "Injectable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "git@github.com:nhiroyasu/Injectable.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; 4FBCEB3827C62B6000BCF88C /* XCRemoteSwiftPackageReference "Swinject" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Swinject/Swinject.git"; @@ -858,6 +898,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 4FB85FAA2882A11400D03E22 /* Injectable */ = { + isa = XCSwiftPackageProductDependency; + package = 4FB85FA92882A11400D03E22 /* XCRemoteSwiftPackageReference "Injectable" */; + productName = Injectable; + }; 4FBCEB3927C62B6000BCF88C /* Swinject */ = { isa = XCSwiftPackageProductDependency; package = 4FBCEB3827C62B6000BCF88C /* XCRemoteSwiftPackageReference "Swinject" */; diff --git a/slideover-for-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/slideover-for-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7d3166b..4358d16 100644 --- a/slideover-for-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/slideover-for-macos.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "injectable", + "kind" : "remoteSourceControl", + "location" : "git@github.com:nhiroyasu/Injectable.git", + "state" : { + "revision" : "580e4fb209fb65383810c277aeab0db4238babb9", + "version" : "1.0.0" + } + }, { "identity" : "magnet", "kind" : "remoteSourceControl", diff --git a/slideover-for-macos/AppContainer.swift b/slideover-for-macos/AppContainer.swift new file mode 100644 index 0000000..cc132ce --- /dev/null +++ b/slideover-for-macos/AppContainer.swift @@ -0,0 +1,32 @@ +import Foundation +import Swinject +import Injectable + +class AppContainer { + + static func build() -> Container { + let container = Container() + + container.register(ScreenManager.self) { _ in ScreenManagerImpl() } + container.register(ApplicationService.self) { _ in ApplicationServiceImpl() } + container.register(WindowManager.self) { _ in WindowManagerImpl() } + container.register(UIQueue.self) { _ in DispatchQueue.main } + container.register(GlobalShortcutService.self) { _ in GlobalShortcutServiceImpl() } + container.register(NotificationManager.self) { _ in NotificationManagerImpl() } + container.register(AlertService.self) { _ in AlertServiceImpl() } + container.register(URLValidationService.self) { _ in URLValidationServiceImpl() } + container.register(URLEncodeService.self) { _ in URLEncodeServiceImpl() } + container.register(WebViewService.self) { _ in WebViewServiceImpl() } + container.register(UserSettingService.self) { _ in UserSettingServiceImpl(userDefaults: UserDefaults.standard) }.inObjectScope(.container) + container.register(SlideOverComputation.self) { injector in SlideOverComputationImpl(injector: injector) } + container.register(SlideOverService.self) { injector in + SlideOverServiceImpl(injector: injector) + } + + return container + } +} + +extension Injector { + static let shared = Injector(container: AppContainer.build()) +} diff --git a/slideover-for-macos/AppDelegate.swift b/slideover-for-macos/AppDelegate.swift index 7716fc0..3bb68dd 100644 --- a/slideover-for-macos/AppDelegate.swift +++ b/slideover-for-macos/AppDelegate.swift @@ -1,27 +1,28 @@ import Cocoa import Magnet +import Injectable @main class AppDelegate: NSObject, NSApplicationDelegate { - private var mainWindowController: SlideOverWindowController? private var notificationManager: NotificationManager? { Injector.shared.buildSafe(NotificationManager.self) } private var userSetting: UserSettingService? { Injector.shared.buildSafe(UserSettingService.self) } + private var slideOverCoordinator: SlideOverCoordinator! func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application - let storyboard = NSStoryboard(name: "Main", bundle: nil) - mainWindowController = storyboard.instantiateController(identifier: "slideOverWindowController") { coder in - SlideOverWindowController(coder: coder, injector: Injector.shared) - } - if let mainWindowController = mainWindowController { - Injector.shared.container.register(SlideOverWindowControllable.self, impl: mainWindowController).inObjectScope(.container) - mainWindowController.showWindow(self) - } + let initState = SlideOverState() + let container = SlideOverContainerBuilder.build(parent: Injector.shared.container, state: initState) + slideOverCoordinator = .init( + injector: Injector(container: container), + state: initState + ) + let windowController = slideOverCoordinator.create() + windowController.showWindow(self) } func applicationDidUnhide(_ notification: Notification) { diff --git a/slideover-for-macos/Coordinator/Coordinator.swift b/slideover-for-macos/Coordinator/Coordinator.swift new file mode 100644 index 0000000..b46bfb0 --- /dev/null +++ b/slideover-for-macos/Coordinator/Coordinator.swift @@ -0,0 +1,10 @@ +import AppKit + +public protocol Coordinator { + func create() -> NSWindowController +} + +public protocol NavigationCoordinator: Coordinator { + func start() + func dismiss() +} diff --git a/slideover-for-macos/Coordinator/SlideOverCoordinator.swift b/slideover-for-macos/Coordinator/SlideOverCoordinator.swift new file mode 100644 index 0000000..bbf02b8 --- /dev/null +++ b/slideover-for-macos/Coordinator/SlideOverCoordinator.swift @@ -0,0 +1,48 @@ +import AppKit +import Swinject +import Injectable + +class SlideOverContainerBuilder { + static func build(parent: Container?, state: SlideOverState) -> Container { + let container = Container(parent: parent) + + container.register(SlideOverAction.self) { resolver in + SlideOverActionImpl(injector: resolver, state: state) + }.inObjectScope(.container) + container.register(SlideOverUseCase.self) { resolver in + SlideOverInteractor(injector: resolver) + }.inObjectScope(.container) + container.register(SlideOverPresenter.self) { resolver in + SlideOverPresenterImpl(injector: resolver, state: state) + }.inObjectScope(.container) + container.register(SlideOverNotificationObserver.self) { resolver in + SlideOverNotificationObserver(injector: resolver) + }.inObjectScope(.container) + + return container + } +} + +class SlideOverCoordinator: Coordinator { + private var windowController: SlideOverWindowController! + private let injector: Injectable + private let state: SlideOverState + private let notificationObserver: SlideOverNotificationObserver + private lazy var action: SlideOverAction = injector.build() + private lazy var useCase: SlideOverUseCase = injector.build() + private lazy var presenter: SlideOverPresenter = injector.build() + + init(injector: Injectable, state: SlideOverState) { + self.injector = injector + self.state = state + self.notificationObserver = injector.build() + } + + func create() -> NSWindowController { + let storyboard = NSStoryboard(name: "Main", bundle: nil) + windowController = storyboard.instantiateController(identifier: "slideOverWindowController") { coder in + SlideOverWindowController(coder: coder, injector: self.injector, state: self.state) + } + return windowController + } +} diff --git a/slideover-for-macos/Coordinator/Transitionable.swift b/slideover-for-macos/Coordinator/Transitionable.swift new file mode 100644 index 0000000..3bbe390 --- /dev/null +++ b/slideover-for-macos/Coordinator/Transitionable.swift @@ -0,0 +1,20 @@ +import AppKit + +public protocol Transitionable { + func present( + _ viewController: NSViewController, + animator: NSViewControllerPresentationAnimator + ) + func dismiss(_ viewController: NSViewController) + func present( + _ viewController: NSViewController, + asPopoverRelativeTo positioningRect: NSRect, + of positioningView: NSView, + preferredEdge: NSRectEdge, + behavior: NSPopover.Behavior + ) + func presentAsModalWindow(_ viewController: NSViewController) + func presentAsSheet(_ viewController: NSViewController) +} + +extension NSViewController: Transitionable {} diff --git a/slideover-for-macos/DI/AppContainer.swift b/slideover-for-macos/DI/AppContainer.swift deleted file mode 100644 index b0fb4d7..0000000 --- a/slideover-for-macos/DI/AppContainer.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import Swinject - -class AppContainer { - - static func build() -> Container { - let container = Container() - - container.register(ApplicationService.self, impl: ApplicationServiceImpl()) - container.register(WindowManager.self, impl: WindowManagerImpl()) - container.register(UIQueue.self, impl: DispatchQueue.main) - container.register(GlobalShortcutService.self, impl: GlobalShortcutServiceImpl()) - container.register(NotificationManager.self, impl: NotificationManagerImpl()) - container.register(AlertService.self, impl: AlertServiceImpl()) - container.register(URLValidationService.self, impl: URLValidationServiceImpl()) - container.register(URLEncodeService.self, impl: URLEncodeServiceImpl()) - container.register(WebViewService.self, impl: WebViewServiceImpl()) - container.register(UserSettingService.self, impl: UserSettingServiceImpl(userDefaults: UserDefaults.standard)).inObjectScope(.container) - container.register(SlideOverService.self) { injector in - SlideOverServiceImpl(injector: injector) - } - - container.register(SlideOverWindowAction.self) { injector in - SlideOverWindowActionImpl(injector: injector) - } - container.register(SlideOverWindowUseCase.self) { injector in - SlideOverWindowInteractor(injector: injector) - } - container.register(SlideOverWindowPresenter.self) { injector in - SlideOverWindowPresenterImpl(injector: injector) - } - - return container - } -} diff --git a/slideover-for-macos/DI/Container+.swift b/slideover-for-macos/DI/Container+.swift deleted file mode 100644 index 9bc2a13..0000000 --- a/slideover-for-macos/DI/Container+.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation -import Swinject - -extension Container { - @discardableResult - func register( - _ serviceType: Service.Type, - factory: @escaping (Injectable) -> Service - ) -> ServiceEntry { - self.register(serviceType, name: nil) { r in - factory(SwinjectInjector(r)) - } - } - - @discardableResult - func register(_ serviceType: Service.Type, impl: Service) -> ServiceEntry { - self.register(serviceType, name: nil) { _ in impl } - } -} diff --git a/slideover-for-macos/DI/Dependencies.swift b/slideover-for-macos/DI/Dependencies.swift deleted file mode 100644 index b266bfb..0000000 --- a/slideover-for-macos/DI/Dependencies.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation -import Swinject - -/// @mockable -protocol Dependencies { - func register(_ Type: T.Type, factory: @escaping (Resolver) -> T) - func registerAsSingleton(_ Type: T.Type, factory: @escaping (Resolver) -> T) -} diff --git a/slideover-for-macos/DI/Injectable.swift b/slideover-for-macos/DI/Injectable.swift deleted file mode 100644 index 5104962..0000000 --- a/slideover-for-macos/DI/Injectable.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -/// @mockable -public protocol Injectable { - func build(_ Type: T.Type) -> T - func buildSafe(_ Type: T.Type) -> T? -} diff --git a/slideover-for-macos/DI/Injector.swift b/slideover-for-macos/DI/Injector.swift deleted file mode 100644 index 5fcee03..0000000 --- a/slideover-for-macos/DI/Injector.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import Swinject - -class Injector: Injectable { - - static let shared: Injector = Injector(container: AppContainer.build()) - let container: Container - - init(container: Container) { - self.container = container - } - - func build(_ Type: T.Type) -> T { - self.container.resolve(T.self)! - } - - func buildSafe(_ Type: T.Type) -> T? { - self.container.resolve(T.self) - } - -// func register(_ Type: T.Type, factory: @escaping (Resolver) -> T) { -// self.container.register(Type.self, name: nil, factory: factory) -// } -// -// func registerAsSingleton(_ Type: T.Type, factory: @escaping (Resolver) -> T) { -// self.container.register(Type.self, name: nil, factory: factory).inObjectScope(.container) -// } -} diff --git a/slideover-for-macos/DI/SwinjectInjector.swift b/slideover-for-macos/DI/SwinjectInjector.swift deleted file mode 100644 index 5513631..0000000 --- a/slideover-for-macos/DI/SwinjectInjector.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation -import Swinject - -class SwinjectInjector: Injectable { - - private let resolver: Resolver - - init(_ resolver: Resolver) { - self.resolver = resolver - } - - func build(_ Type: T.Type) -> T { - resolver.resolve(T.self)! - } - - func buildSafe(_ Type: T.Type) -> T? { - resolver.resolve(T.self) - } -} diff --git a/slideover-for-macos/Module/SlideOver/SlideOverAction.swift b/slideover-for-macos/Module/SlideOver/SlideOverAction.swift new file mode 100644 index 0000000..a484f3c --- /dev/null +++ b/slideover-for-macos/Module/SlideOver/SlideOverAction.swift @@ -0,0 +1,144 @@ +import Foundation +import Injectable + +protocol SlideOverAction { + func showWindow() + func windowWillClose(size: NSSize?) + func windowDidEndLiveResize(_ frame: NSRect) + func didEnterSearchBar(input: String) + func didTapChangingPositionButton(type: SlideOverKind) + func didTapUpdateUserAgent(_ userAgent: UserAgent) + func didTapHideWindow() + func didTapHelp() + func didTapSetting() + func didTapReappearButton() + + func leftClickUpMouseButton() + func doubleRightClickMouseButton() + func reloadNotification() + func cacheClearNotification() + func openHelpNotification() + func searchFocusNotification() + func hideWindowNotification() + func displayNotification() + func switchWindowVisibilityShortcut() + func openUrlNotification(url: URL) + func zoomInNotification() + func zoomOutNotification() + func zoomResetNotification() +} + +class SlideOverActionImpl: SlideOverAction { + + private let useCase: SlideOverUseCase + private let urlValidationService: URLValidationService + private let state: SlideOverState + + public init(injector: Injectable = Injector.shared, state: SlideOverState) { + self.useCase = injector.build(SlideOverUseCase.self) + self.urlValidationService = injector.build(URLValidationService.self) + self.state = state + } + + func showWindow() { + useCase.setUp() + } + + func didEnterSearchBar(input: String) { + if urlValidationService.isUrl(text: input) { + useCase.load(url: URL(string: input)) + } else { + useCase.searchGoogle(keyword: input) + } + } + + func didTapChangingPositionButton(type: SlideOverKind) { + useCase.requestChangingPosition(type: type) + } + + func didTapUpdateUserAgent(_ userAgent: UserAgent) { + useCase.updateUserAgent(userAgent) + } + + func didTapHideWindow() { + useCase.disappear(for: ObjectFrame(from: state.cacheFrame)) + } + + func didTapHelp() { + useCase.showHelp() + } + + func didTapSetting() { + useCase.showSetting() + } + + func didTapReappearButton() { + useCase.appear(for: ObjectFrame(from: state.cacheFrame)) + } + + func windowWillClose(size: NSSize?) { + useCase.memorizeLatestWindowSize(size) + } + + func windowDidEndLiveResize(_ frame: NSRect) { + if !state.isHidden { + useCase.requestResize(nextFrame: ObjectFrame(from: frame)) + } + } + + func leftClickUpMouseButton() { + useCase.replace() + } + + func doubleRightClickMouseButton() { + useCase.reversePosition() + } + + func reloadNotification() { + useCase.reload() + } + + func cacheClearNotification() { + useCase.clearCache() + } + + func openHelpNotification() { + useCase.showHelp() + } + + func searchFocusNotification() { + useCase.focusSearchBar() + } + + func hideWindowNotification() { + useCase.disappear(for: ObjectFrame(from: state.cacheFrame)) + } + + func displayNotification() { + useCase.appear(for: ObjectFrame(from: state.cacheFrame)) + } + + func switchWindowVisibilityShortcut() { + if state.isHidden { + useCase.appear(for: ObjectFrame(from: state.cacheFrame)) + } else { + useCase.disappear(for: ObjectFrame(from: state.cacheFrame)) + } + } + + func openUrlNotification(url: URL) { + useCase.load(url: url) + } + + func zoomInNotification() { + useCase.zoomIn() + } + + func zoomOutNotification() { + useCase.zoomOut() + } + + func zoomResetNotification() { + useCase.zoomReset() + } +} diff --git a/slideover-for-macos/Module/SlideOver/SlideOverNotificationObserver.swift b/slideover-for-macos/Module/SlideOver/SlideOverNotificationObserver.swift new file mode 100644 index 0000000..7c2b4ec --- /dev/null +++ b/slideover-for-macos/Module/SlideOver/SlideOverNotificationObserver.swift @@ -0,0 +1,145 @@ +import Foundation +import Combine +import AppKit +import Injectable + +class SlideOverNotificationObserver { + private let action: SlideOverAction + private let notificationManager: NotificationManager + private let userSettingService: UserSettingService + private let globalShortcutService: GlobalShortcutService + + var didMoveNotificationToken: AnyCancellable? + var didDoubleRightClickNotificationToken: AnyCancellable? + var willMoveNotificationToken: AnyCancellable? + private var didLongRightClickNotificationToken: AnyCancellable? + private let leftMouseUpSubject = PassthroughSubject() + private let rightMouseDownSubject = PassthroughSubject() + private let rightMouseUpSubject = PassthroughSubject() + + init(injector: Injectable) { + self.action = injector.build(SlideOverAction.self) + self.notificationManager = injector.build(NotificationManager.self) + self.userSettingService = injector.build(UserSettingService.self) + self.globalShortcutService = injector.build(GlobalShortcutService.self) + + observeMouseEvent() + setWillMoveNotification() + setRightMouseUpSubject() + observeReloadNotification() + observeClearCacheNotification() + observeHelpNotification() + observeSearchFocusNotification() + observeHideWindowNotification() + registerSwitchWindowVisibilityShortcutKey() + observeUrlOpenUrlNotification() + observeZoomNotifications() + observeDisplaySlideOverNotification() + } + + private func observeMouseEvent() { + NSEvent.addLocalMonitorForEvents(matching: [.leftMouseUp]) { [weak self] event in + self?.leftMouseUpSubject.send(event) + return event + } + NSEvent.addLocalMonitorForEvents(matching: [.rightMouseUp]) { [weak self] event in + self?.rightMouseUpSubject.send(event) + return event + } + NSEvent.addLocalMonitorForEvents(matching: [.rightMouseDown]) { [weak self] event in + self?.rightMouseDownSubject.send(event) + return event + } + } + + private func setWillMoveNotification() { + willMoveNotificationToken = NotificationCenter.default + .publisher(for: NSWindow.willMoveNotification, object: nil) + .sink { [weak self] notification in + self?.setMoveNotification() + } + } + + private func setMoveNotification() { + didMoveNotificationToken = leftMouseUpSubject + .drop(untilOutputFrom: NotificationCenter.default.publisher(for: NSWindow.didMoveNotification, object: nil)) + .prefix(1) + .sink { [weak self] event in + self?.action.leftClickUpMouseButton() + } + } + + private func setRightMouseUpSubject() { + didDoubleRightClickNotificationToken = rightMouseUpSubject + .collect(.byTime(RunLoop.main, .milliseconds(600))) + .filter { $0.count >= 2 } + .sink { [weak self] _ in + self?.action.doubleRightClickMouseButton() + } + } + + private func observeReloadNotification() { + notificationManager.observe(name: .reload) { [weak self] _ in + self?.action.reloadNotification() + } + } + + private func observeClearCacheNotification() { + notificationManager.observe(name: .clearCache) { [weak self] _ in + self?.action.cacheClearNotification() + } + } + + private func observeHelpNotification() { + notificationManager.observe(name: .openHelp) { [weak self] _ in + self?.action.openHelpNotification() + } + } + + private func observeSearchFocusNotification() { + notificationManager.observe(name: .searchFocus) { [weak self] _ in + self?.action.searchFocusNotification() + } + } + + private func observeHideWindowNotification() { + notificationManager.observe(name: .hideWindow) { [weak self] _ in + self?.action.hideWindowNotification() + } + } + + private func registerSwitchWindowVisibilityShortcutKey() { + guard !userSettingService.isNotAllowedGlobalShortcut else { return } + globalShortcutService.register(keyType: .command_control_s) { [weak self] in + self?.action.switchWindowVisibilityShortcut() + } + } + + private func observeUrlOpenUrlNotification() { + notificationManager.observe(name: .openUrl) { [weak self] urlValue in + guard let urlStr = urlValue as? String, + let url = URL(string: urlStr) else { return } + self?.action.openUrlNotification(url: url) + } + } + + private func observeZoomNotifications() { + notificationManager.observe(name: .zoomInWebView) { [weak self] _ in + self?.action.zoomInNotification() + } + + notificationManager.observe(name: .zoomOutWebView) { [weak self] _ in + self?.action.zoomOutNotification() + } + + notificationManager.observe(name: .zoomResetWebView) { [weak self] _ in + self?.action.zoomResetNotification() + } + } + + private func observeDisplaySlideOverNotification() { + notificationManager.observe(name: .displaySlideOver) { [weak self] _ in + self?.action.displayNotification() + } + } +} diff --git a/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift new file mode 100644 index 0000000..50776d3 --- /dev/null +++ b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift @@ -0,0 +1,83 @@ +import Foundation +import Injectable + +protocol SlideOverPresenter { + func fix(at frame: NSRect) + func fixWithDisappear(at frame: NSRect) + func set(userAgent: UserAgent) + func loadWebPage(url: URL?) + func reloadWebPage() + func openBrowser(url: URL?) + func zoomIn() + func zoomOut() + func resetZoom() + func focusSearchBar() + func openSettingWindow() +} + +class SlideOverPresenterImpl: SlideOverPresenter { + + private let state: SlideOverState + private let applicationService: ApplicationService + private var userSettingService: UserSettingService + private let windowManager: WindowManager + + internal init(injector: Injectable = Injector.shared, state: SlideOverState) { + self.state = state + self.applicationService = injector.build(ApplicationService.self) + self.userSettingService = injector.build() + self.windowManager = injector.build() + } + + func fix(at frame: NSRect) { + // 順番依存 + state.isHidden = false + state.frame = frame + state.cacheFrame = frame + userSettingService.latestWindowSize = frame.size + } + + func fixWithDisappear(at frame: NSRect) { + // 順番依存 + state.isHidden = true + state.frame = frame + } + + func set(userAgent: UserAgent) { + state.userAgent = userAgent.rawValue + } + + func loadWebPage(url: URL?) { + state.url = url + userSettingService.latestPage = url + } + + func reloadWebPage() { + state.reloadAction?() + } + + func openBrowser(url: URL?) { + guard let url = url else { return } + applicationService.open(url) + } + + func zoomIn() { + state.zoom = state.zoom + 0.1 + } + + func zoomOut() { + state.zoom = state.zoom - 0.1 + } + + func resetZoom() { + state.zoom = 1.0 + } + + func focusSearchBar() { + state.focusAction?() + } + + func openSettingWindow() { + windowManager.lunch(.setting) + } +} diff --git a/slideover-for-macos/Module/SlideOver/SlideOverState.swift b/slideover-for-macos/Module/SlideOver/SlideOverState.swift new file mode 100644 index 0000000..15b69b6 --- /dev/null +++ b/slideover-for-macos/Module/SlideOver/SlideOverState.swift @@ -0,0 +1,15 @@ +import Foundation + +class SlideOverState: NSObject { + @objc dynamic var frame: NSRect = NSRect(origin: .zero, size: .init(width: 300, height: 400)) + @objc dynamic var cacheFrame: NSRect = NSRect(origin: .zero, size: .init(width: 300, height: 400)) + @objc dynamic var isHidden: Bool = false + @objc dynamic var isHiddenCompletely: Bool = false + @objc dynamic var progress: Double = 0.0 + @objc dynamic var userAgent: Int = UserAgent.desktop.rawValue + @objc dynamic var url: URL? = nil + @objc dynamic var zoom: Double = 1.0 + + var reloadAction: (() -> Void)? + var focusAction: (() -> Void)? +} diff --git a/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift b/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift new file mode 100644 index 0000000..7814ec9 --- /dev/null +++ b/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift @@ -0,0 +1,210 @@ +import Foundation +import Injectable + +protocol SlideOverUseCase { + /// このモジュールの初期設定 + func setUp() + /// 指定したURLを読み込む + func load(url: URL?) + /// ページを再読み込み + func reload() + /// ヘルプを表示する + func showHelp() + /// 設定を表示する + func showSetting() + /// 指定したキーワードをgoogleで検索する + func searchGoogle(keyword: String) + /// SlideOverを非表示にする + func disappear(for windowFrame: ObjectFrame) + /// SlideOverを表示する + func appear(for windowFrame: ObjectFrame) + /// SlideOverを再配置する + func replace() + /// SlideoVerを反対方向に移動する + func reversePosition() + /// 指定したサイズでリサイズする。サイズが大きすぎる場合、自動で修正される。 + func requestResize(nextFrame: ObjectFrame) + /// 最後に読んだページとしてurlを保存する + func memorizeLatestPage(url: URL?) + /// 最後に移動させたポジションを保存する + func memorizeLatestPosition(kind: SlideOverKind) + /// ウィンドウサイズを保存する + func memorizeLatestWindowSize(_ size: NSSize?) + /// 指定したユーザエージェントに切り替える + func updateUserAgent(_ userAgent: UserAgent) + /// 指定したポジションに移動させる + func requestChangingPosition(type: SlideOverKind) + /// キャッシュを削除 + func clearCache() + /// サーチバーにフォーカスする + func focusSearchBar() + /// ズームイン + func zoomIn() + /// ズームアウト + func zoomOut() + /// ズームリセット + func zoomReset() +} + +class SlideOverInteractor: SlideOverUseCase { + private let presenter: SlideOverPresenter + private var userSettingService: UserSettingService + private var urlValidationService: URLValidationService + private var urlEncodeService: URLEncodeService + private let webViewService: WebViewService + private let notificationManager: NotificationManager + private let globalShortcutService: GlobalShortcutService + private let windowManager: WindowManager + private let appInfoService: ApplicationService + private let slideOverComputation: SlideOverComputation + private let screenManager: ScreenManager + + private let defaultInitialPage: URL? = URL(string: "https://google.com") + private let helpUrl: URL? = URL(string: "https://nhiro.notion.site/Fixture-in-Picture-0eef7a658b4b481a84fbc57d6e43a8f2") + private let defaultUserAgent: UserAgent = .desktop + private let defaultSlideOverPosition: SlideOverKind = .right + + internal init(injector: Injectable = Injector.shared) { + self.presenter = injector.build(SlideOverPresenter.self) + self.userSettingService = injector.build(UserSettingService.self) + self.slideOverComputation = injector.build(SlideOverComputation.self) + self.urlValidationService = injector.build(URLValidationService.self) + self.urlEncodeService = injector.build(URLEncodeService.self) + self.webViewService = injector.build(WebViewService.self) + self.notificationManager = injector.build(NotificationManager.self) + self.globalShortcutService = injector.build(GlobalShortcutService.self) + self.windowManager = injector.build(WindowManager.self) + self.appInfoService = injector.build(ApplicationService.self) + self.screenManager = injector.build(ScreenManager.self) + } + + /// このモジュールの初期設定 + func setUp() { + // TODO: 初期SlideOverの表示(サイズ、ポジション、URL、UserAgent) + let windowFrame: NSRect + if let latestWindowSize = userSettingService.latestWindowSize { + windowFrame = slideOverComputation.arrangeWindow(for: ObjectFrame(from: latestWindowSize), at: screenManager.mainFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + } else { + windowFrame = slideOverComputation.fixWindow(at: screenManager.mainFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + } + presenter.fix(at: windowFrame) + presenter.loadWebPage(url: userSettingService.latestPage) + presenter.set(userAgent: userSettingService.latestUserAgent ?? defaultUserAgent) + // TODO: 新機能の表示 + } + + /// 指定したURLを読み込む + func load(url: URL?) { + presenter.loadWebPage(url: url) + } + + /// ページを再読み込み + func reload() { + presenter.reloadWebPage() + } + + /// ヘルプを表示する + func showHelp() { + presenter.loadWebPage(url: helpUrl) + } + + func showSetting() { + presenter.openSettingWindow() + } + + /// 指定したキーワードをgoogleで検索する + func searchGoogle(keyword: String) { + let encodedKeyword = urlEncodeService.encode(text: keyword) + let urlString = "https://www.google.co.jp/search?q=\(encodedKeyword)" + presenter.loadWebPage(url: URL(string: urlString)) + } + + /// SlideOverを非表示にする + func disappear(for windowFrame: ObjectFrame) { + let newFrame: NSRect + if userSettingService.isCompletelyHideWindow { + newFrame = slideOverComputation.disappearCompletely(for: windowFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + } else { + newFrame = slideOverComputation.disappearOutside(for: windowFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + } + presenter.fixWithDisappear(at: newFrame) + } + + /// SlideOverを表示する + func appear(for windowFrame: ObjectFrame) { + let newFrame = slideOverComputation.arrangeWindow(for: windowFrame, at: screenManager.mainFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + presenter.fix(at: newFrame) + } + + /// SlideOverを再配置する + func replace() { + let windowFrame = slideOverComputation.fixMovedWindow(at: screenManager.mainFrame) + presenter.fix(at: windowFrame) + } + + /// SlideoVerを反対方向に移動する + func reversePosition() { + let windowFrame = slideOverComputation.reverseMoveWindow(at: screenManager.mainFrame) + presenter.fix(at: windowFrame) + } + + func requestResize(nextFrame: ObjectFrame) { + guard let position = userSettingService.latestPosition else { return } + let windowSize = position.state.computeResize(screenSize: screenManager.mainFrame.size, to: nextFrame.size) + let windowFrame = ObjectFrame(from: NSRect(origin: nextFrame.origin, size: windowSize)) + let resizeWindowFrame = slideOverComputation.arrangeWindow(for: windowFrame, at: screenManager.mainFrame, type: position) + presenter.fix(at: resizeWindowFrame) + } + + /// 最後に読んだページとしてurlを保存する + func memorizeLatestPage(url: URL?) { + userSettingService.latestPage = url + } + + /// 最後に移動させたポジションを保存する + func memorizeLatestPosition(kind: SlideOverKind) { + userSettingService.latestPosition = kind + } + + /// ウィンドウサイズを保存する + func memorizeLatestWindowSize(_ size: NSSize?) { + userSettingService.latestWindowSize = size + } + + /// 指定したユーザエージェントに切り替える + func updateUserAgent(_ userAgent: UserAgent) { + userSettingService.latestUserAgent = userAgent + presenter.set(userAgent: userAgent) + } + + /// 指定したポジションに移動させる + func requestChangingPosition(type: SlideOverKind) { + let windowFrame = slideOverComputation.fixWindow(at: screenManager.mainFrame, type: type) + presenter.fix(at: windowFrame) + } + + /// キャッシュを削除 + func clearCache() { + webViewService.clearCache() + } + + /// サーチバーにフォーカスする + func focusSearchBar() { + presenter.focusSearchBar() + } + + /// ズームイン + func zoomIn() { + presenter.zoomIn() + } + + /// ズームアウト + func zoomOut() { + presenter.zoomOut() + } + + /// ズームリセット + func zoomReset() { + presenter.resetZoom() + } +} diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift index a054a7f..459c8da 100644 --- a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift @@ -1,4 +1,5 @@ import Foundation +import Injectable /// @mockable protocol SlideOverWindowAction { diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift index 7717d68..5935a5b 100644 --- a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift @@ -1,5 +1,6 @@ import Foundation import QuartzCore +import Injectable /// @mockable protocol SlideOverWindowPresenter { @@ -151,12 +152,12 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { } func setResizeHandler(handler: @escaping (NSSize, NSSize) -> (NSSize, SlideOverKind)) { - var window = output - window?.windowWillResizeHandler = { [weak self] currentWindow, next in - let (nextSize, type) = handler(currentWindow.frame.size, next) - self?.slideOverService.arrangeWindowPosition(for: currentWindow, windowSize: nextSize, type: type) - return nextSize - } +// var window = output +// window?.windowWillResizeHandler = { [weak self] currentWindow, next in +// let (nextSize, type) = handler(currentWindow.frame.size, next) +// self?.slideOverService.arrangeWindowPosition(for: currentWindow, windowSize: nextSize, type: type) +// return nextSize +// } } func focusSearchBar() { diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift index fce9f7f..740ab74 100644 --- a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift +++ b/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift @@ -1,6 +1,7 @@ import Foundation import AppKit import Combine +import Injectable /// @mockable protocol SlideOverWindowUseCase { @@ -152,10 +153,10 @@ class SlideOverWindowInteractor: SlideOverWindowUseCase { } private func resizeWindow() { - presenter.setResizeHandler { [weak self] current, next in - guard let currentPosition = self?.userSettingService.latestPosition, let screen = NSScreen.main else { return (next, .right) } - return (currentPosition.state.computeResize(screenSize: screen.visibleFrame.size, from: current, to: next), currentPosition) - } +// presenter.setResizeHandler { [weak self] current, next in +// guard let currentPosition = self?.userSettingService.latestPosition, let screen = NSScreen.main else { return (next, .right) } +// return (currentPosition.state.computeResize(screenSize: screen.visibleFrame.size, from: current, to: next), currentPosition) +// } } func requestChangingPosition(type: SlideOverKind) { diff --git a/slideover-for-macos/Service/ScreenManager.swift b/slideover-for-macos/Service/ScreenManager.swift new file mode 100644 index 0000000..93fa5b9 --- /dev/null +++ b/slideover-for-macos/Service/ScreenManager.swift @@ -0,0 +1,21 @@ +import Foundation + +protocol ScreenManager { + /// メインスクリーンの利用可能な領域 + var mainFrame: ObjectFrame { get } + /// メインスクリーン全体の領域 + var mainAbsoluteFrame: ObjectFrame { get } +} + +class ScreenManagerImpl: ScreenManager { + + var mainFrame: ObjectFrame { + guard let mainScreen = NSScreen.main else { return .init(from: NSRect.zero) } + return ObjectFrame(from: mainScreen.visibleFrame) + } + + var mainAbsoluteFrame: ObjectFrame { + guard let mainScreen = NSScreen.main else { return .init(from: NSRect.zero) } + return ObjectFrame(from: mainScreen.frame) + } +} diff --git a/slideover-for-macos/Service/SlideOverComputable.swift b/slideover-for-macos/Service/SlideOverComputable.swift index 92a7ba0..a8e5976 100644 --- a/slideover-for-macos/Service/SlideOverComputable.swift +++ b/slideover-for-macos/Service/SlideOverComputable.swift @@ -10,7 +10,7 @@ let hideOffsetSpace: CGFloat = 40.0 protocol SlideOverComputable { func computeWindowRect(screenSize: CGSize, screenOffset: CGPoint) -> CGRect func computeWindowPoint(windowSize: CGSize, screenSize: CGSize, screenOffset: CGPoint) -> CGPoint - func computeResize(screenSize: CGSize, from current: NSSize, to next: NSSize) -> NSSize + func computeResize(screenSize: CGSize, to next: NSSize) -> NSSize } class SlideOver { @@ -33,7 +33,7 @@ class SlideOver { return bestHeightSize } - func computeResize(screenSize: CGSize, from current: NSSize, to next: NSSize) -> NSSize { + func computeResize(screenSize: CGSize, to next: NSSize) -> NSSize { var resultWidth = next.width if next.width > maxWidthSize { resultWidth = maxWidthSize @@ -41,7 +41,7 @@ class SlideOver { if next.width < minWidthSize { resultWidth = minWidthSize } - return NSSize(width: resultWidth, height: current.height) + return NSSize(width: resultWidth, height: computeWindowHeight(screenHeight: screenSize.height)) } } @@ -118,7 +118,7 @@ class SlideOver { } } - func computeResize(screenSize: CGSize, from current: NSSize, to next: NSSize) -> NSSize { + func computeResize(screenSize: CGSize, to next: NSSize) -> NSSize { let maxHeightSize = screenSize.height * maxHeightRatio let maxWidthSize = screenSize.width * maxWidthRatio var width = next.width diff --git a/slideover-for-macos/Service/SlideOverComputation.swift b/slideover-for-macos/Service/SlideOverComputation.swift new file mode 100644 index 0000000..022e88b --- /dev/null +++ b/slideover-for-macos/Service/SlideOverComputation.swift @@ -0,0 +1,176 @@ +import Foundation +import AppKit +import Injectable + +struct ObjectFrame { + let frame: NSRect + let size: NSSize + let origin: NSPoint + + init(from rect: NSRect) { + self.frame = rect + self.size = rect.size + self.origin = rect.origin + } + + init(from size: NSSize) { + self.frame = NSRect(origin: .zero, size: size) + self.size = size + self.origin = .zero + } +} + +/// @mockable +protocol SlideOverComputation { + /// 指定したポジションに配置される場合のウィンドウフレームを計算する + func fixWindow(at screen: ObjectFrame, type: SlideOverKind) -> NSRect + /// 移動されたウィンドウに対して、適切なウィンドウフレームを計算する(実際はマウスの位置から計算する) + func fixMovedWindow(at screen: ObjectFrame) -> NSRect + /// 反対方向に移動した場合のウィンドウフレームを計算する + func reverseMoveWindow(at screen: ObjectFrame) -> NSRect + /// ウィンドウが指定したポジションに配置される場合のウィンドウフレームを計算 + func arrangeWindow(for window: ObjectFrame, at screen: ObjectFrame, type: SlideOverKind) -> NSRect + /// ウィンドウが指定したポジションに配置される場合のウィンドウフレームを計算。ただし、ウィンドウサイズは引数が優先される +// func arrangeWindow(for window: ObjectFrame, windowSize: NSSize, type: SlideOverKind) -> NSRect +// func arrangeWindowPosition(for window: WindowFrame, windowSize: NSSize, type: SlideOverKind) -> NSRect + /// 外側にウィンドウを隠す場合のウィンドウフレームを計算 + func disappearOutside(for window: ObjectFrame, type: SlideOverKind) -> NSRect + /// 完全にウィンドウを隠す場合のウィンドウフレームを計算 + func disappearCompletely(for window: ObjectFrame, type: SlideOverKind) -> NSRect +} + +class SlideOverComputationImpl: SlideOverComputation { + private var userSettingService: UserSettingService + private var mousePointService: MousePointService? { + MousePointServiceImpl.current + } + + init(injector: Injectable) { + self.userSettingService = injector.build(UserSettingService.self) + } + + public func fixWindow(at screen: ObjectFrame, type: SlideOverKind) -> NSRect { + userSettingService.latestPosition = type + let windowRect = type.state.computeWindowRect(screenSize: screen.size, screenOffset: screen.origin) + return windowRect + } + + func fixMovedWindow(at screen: ObjectFrame) -> NSRect { + guard let mousePointService = mousePointService else { return .zero } + + if mousePointService.getHorizontalCornerSplit() == .left && + mousePointService.getVerticalCornerSplit() == .top { + // 左上 + return fixWindow(at: screen, type: .topLeft) + } else if mousePointService.getHorizontalCornerSplit() == .left && + mousePointService.getVerticalCornerSplit() == .bottom { + // 左下 + return fixWindow(at: screen, type: .bottomLeft) + } else if mousePointService.getHorizontalCornerSplit() == .right && + mousePointService.getVerticalCornerSplit() == .top { + // 右上 + return fixWindow(at: screen, type: .topRight) + } else if mousePointService.getHorizontalCornerSplit() == .right && + mousePointService.getVerticalCornerSplit() == .bottom { + // 右下 + return fixWindow(at: screen, type: .bottomRight) + } else if mousePointService.getHorizontalSplit() == .left { + // 左 + return fixWindow(at: screen, type: .left) + } else if mousePointService.getHorizontalSplit() == .right { + // 右 + return fixWindow(at: screen, type: .right) + } + + return .zero + } + + func reverseMoveWindow(at screen: ObjectFrame) -> NSRect { + switch userSettingService.latestPosition { + case .left: + return fixWindow(at: screen, type: .right) + case .right: + return fixWindow(at: screen, type: .left) + case .topLeft: + return fixWindow(at: screen, type: .topRight) + case .topRight: + return fixWindow(at: screen, type: .topLeft) + case .bottomLeft: + return fixWindow(at: screen, type: .bottomRight) + case .bottomRight: + return fixWindow(at: screen, type: .bottomLeft) + case .none: + return .zero + } + } + + func arrangeWindow(for window: ObjectFrame, at screen: ObjectFrame, type: SlideOverKind) -> NSRect { + let windowPoint = type.state.computeWindowPoint(windowSize: window.frame.size, screenSize: screen.size, screenOffset: screen.origin) + let windowRect = NSRect(origin: windowPoint, size: window.frame.size) + return windowRect + } + +// func arrangeWindow(for window: ObjectFrame, windowSize: NSSize, type: SlideOverKind) -> NSRect { +// guard let screen = NSScreen.main else { return window.frame } +// let windowPoint = type.state.computeWindowPoint(windowSize: windowSize, screenSize: screen.visibleFrame.size, screenOffset: screen.visibleFrame.origin) +// let windowRect = NSRect(origin: windowPoint, size: windowSize) +// return windowRect +// } + +// func arrangeWindowPosition(for window: WindowFrame, windowSize: NSSize, type: SlideOverKind) -> NSRect { +// guard let screen = NSScreen.main else { return window.frame } +// let windowPoint = type.state.computeWindowPoint(windowSize: windowSize, screenSize: screen.visibleFrame.size, screenOffset: screen.visibleFrame.origin) +// return NSRect(origin: windowPoint, size: windowSize) +// } + + func disappearOutside(for window: ObjectFrame, type: SlideOverKind) -> NSRect { + switch type { + case .left, .topLeft, .bottomLeft: + let size = window.frame.size + let prevPoint = window.frame.origin + let windowPoint = NSPoint(x: prevPoint.x - size.width - marginRight + hideOffsetSpace, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: windowPoint, size: size) + if canSetFrame(nextFrame: nextWindowFrame) { + return nextWindowFrame + } else { + NSSound.beep() + return window.frame + } + case .right, .topRight, .bottomRight: + let size = window.frame.size + let prevPoint = window.frame.origin + let windowPoint = NSPoint(x: prevPoint.x + size.width + marginRight - hideOffsetSpace, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: windowPoint, size: size) + if canSetFrame(nextFrame: nextWindowFrame) { + return nextWindowFrame + } else { + NSSound.beep() + return window.frame + } + } + } + + func disappearCompletely(for window: ObjectFrame, type: SlideOverKind) -> NSRect { + let prevSize = window.frame.size + let prevPoint = window.frame.origin + + switch type { + case .right, .topRight, .bottomRight: + let nextWindowPoint = NSPoint(x: prevPoint.x + prevSize.width, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: nextWindowPoint, size: NSSize(width: 0, height: prevSize.height)) + return nextWindowFrame + case .left, .topLeft, .bottomLeft: + let nextWindowPoint = NSPoint(x: prevPoint.x, y: prevPoint.y) + let nextWindowFrame = NSRect(origin: nextWindowPoint, size: NSSize(width: 0, height: prevSize.height)) + return nextWindowFrame + } + } + + private func canSetFrame(nextFrame: NSRect) -> Bool { + // NOTE: 移動先のFrameが二つ以上のスクリーンに重なっており、Originがどのスクリーンにも含まれない場合、setFrameができない可能性あり + let isIntersectsTwoScreen = NSScreen.screens.filter { screen in screen.frame.intersects(nextFrame) }.count >= 2 + let isNotContainAllScreen = NSScreen.screens.allSatisfy { screen in screen.frame.contains(nextFrame.origin) == false } + return !(isIntersectsTwoScreen && isNotContainAllScreen) + } +} + diff --git a/slideover-for-macos/Service/SlideOverService.swift b/slideover-for-macos/Service/SlideOverService.swift index 6346662..b1c306c 100644 --- a/slideover-for-macos/Service/SlideOverService.swift +++ b/slideover-for-macos/Service/SlideOverService.swift @@ -1,5 +1,6 @@ import Foundation import AppKit +import Injectable enum SlideOverKind: Int { case left diff --git a/slideover-for-macos/UI/FeaturePresent/FeaturePresentViewController.swift b/slideover-for-macos/UI/FeaturePresent/FeaturePresentViewController.swift index 45e4508..2e2ff04 100644 --- a/slideover-for-macos/UI/FeaturePresent/FeaturePresentViewController.swift +++ b/slideover-for-macos/UI/FeaturePresent/FeaturePresentViewController.swift @@ -1,4 +1,5 @@ import Cocoa +import Injectable class FeaturePresentViewController: NSViewController { diff --git a/slideover-for-macos/UI/Setting/SettingViewController.swift b/slideover-for-macos/UI/Setting/SettingViewController.swift index 0ebfc65..93c9f98 100644 --- a/slideover-for-macos/UI/Setting/SettingViewController.swift +++ b/slideover-for-macos/UI/Setting/SettingViewController.swift @@ -1,4 +1,5 @@ import Cocoa +import Injectable class SettingViewController: NSViewController { diff --git a/slideover-for-macos/UI/SlideOverViewController.swift b/slideover-for-macos/UI/SlideOverViewController.swift index 971348c..0ab6ffb 100644 --- a/slideover-for-macos/UI/SlideOverViewController.swift +++ b/slideover-for-macos/UI/SlideOverViewController.swift @@ -1,5 +1,6 @@ import Cocoa import WebKit +import Injectable /// @mockable protocol SlideOverViewable { @@ -69,11 +70,23 @@ class SlideOverViewController: NSViewController { observers.append(webView.observe(\.canGoForward, options: [.new], changeHandler: { [weak self] webView, _ in self?.contentWindow?.setBrowserForward(enable: webView.canGoForward) })) - observers.append(webView.observe(\.url, options: [.new], changeHandler: { [weak self] webView, _ in - self?.contentWindow?.action.didChangePage(url: webView.url) - })) observers.append(webView.observe(\.estimatedProgress, options: [.new], changeHandler: { [weak self] webView, _ in - self?.contentWindow?.action.didUpdateProgress(value: webView.estimatedProgress) + let value = webView.estimatedProgress * 100.0 + self?.progressBar?.layer?.opacity = 1.0 + self?.progressBar?.doubleValue = value + if value == 100 { + guard let layer = self?.progressBar?.layer else { return } + DispatchQueue.main.mainAsyncAfter(deadline: .now() + 0.8) { + let animation = CABasicAnimation(keyPath: "opacity") + animation.duration = 0.8 + animation.fromValue = 1.0 + animation.toValue = 0.0 + animation.autoreverses = false + animation.isRemovedOnCompletion = false + animation.fillMode = .forwards + layer.add(animation, forKey: nil) + } + } })) } @@ -144,15 +157,9 @@ extension SlideOverViewController: SlideOverViewable { private func fadeOutViewIfNeeded(_ view: NSView, completion: @escaping () -> Void) { guard view.isHidden == false else { return } - view.alphaValue = 1.0 - NSAnimationContext.runAnimationGroup { context in - context.duration = 0.4 - view.animator().alphaValue = 0.0 - } completionHandler: { - view.isHidden = true - view.alphaValue = 0.0 - completion() - } + view.isHidden = true + view.alphaValue = 0.0 + completion() } } @@ -179,10 +186,6 @@ extension SlideOverViewController: SlideOverWebViewMenuDelegate { guard let url = webView.url else { return } NSWorkspace.shared.open(url) } - - func didTapRegisterInitialPage() { - contentWindow?.action.didTapInitialPageItem(currentUrl: webView.url) - } func didTapWindowLayout(type: SlideOverKind) { contentWindow?.action.didTapChangingPositionButton(type: type) @@ -199,4 +202,8 @@ extension SlideOverViewController: SlideOverWebViewMenuDelegate { func didTapHelp() { contentWindow?.action.didTapHelp() } + + func didTapSetting() { + contentWindow?.action.didTapSetting() + } } diff --git a/slideover-for-macos/UI/SlideOverWindowController.swift b/slideover-for-macos/UI/SlideOverWindowController.swift index 56a3d71..79dd580 100644 --- a/slideover-for-macos/UI/SlideOverWindowController.swift +++ b/slideover-for-macos/UI/SlideOverWindowController.swift @@ -1,5 +1,6 @@ import AppKit import Combine +import Injectable /// @mockable protocol SlideOverWindowControllable { @@ -11,10 +12,9 @@ protocol SlideOverWindowControllable { func setWindowAlpha(_ value: CGFloat) var isMiniaturized: Bool { get } var progressBar: NSProgressIndicator? { get } - var action: SlideOverWindowAction { get } + var action: SlideOverAction { get } var contentView: SlideOverViewable? { get } var webDisplayTypeItem: NSToolbarItem! { get } - var windowWillResizeHandler: ((NSWindow, NSSize) -> NSSize)? { get set } } class SlideOverWindowController: NSWindowController { @@ -26,30 +26,28 @@ class SlideOverWindowController: NSWindowController { browserReloadItem.action = #selector(didTapBrowserReloadItem(_:)) } } - @IBOutlet weak var registerInitialPageItem: NSToolbarItem! { - didSet { - registerInitialPageItem.action = #selector(didTapRegisterInitialPageItem(_:)) - } - } + @IBOutlet weak var registerInitialPageItem: NSToolbarItem! @IBOutlet weak var searchBar: NSSearchField! { didSet { searchBar.delegate = self } } - @IBOutlet weak var webDisplayTypeItem: NSToolbarItem! { - didSet { - webDisplayTypeItem.action = #selector(didTapWebDisplayTypeItem(_:)) - } - } + @IBOutlet weak var webDisplayTypeItem: NSToolbarItem! @IBOutlet weak var bookmarkItem: NSToolbarItem! + private let state: SlideOverState - let action: SlideOverWindowAction + let action: SlideOverAction private let urlValidationService: URLValidationService + private let userSettingService: UserSettingService - init?(coder: NSCoder, injector: Injectable) { - self.action = injector.build(SlideOverWindowAction.self) + init?(coder: NSCoder, injector: Injectable, state: SlideOverState) { + self.action = injector.build(SlideOverAction.self) self.urlValidationService = injector.build(URLValidationService.self) + self.userSettingService = injector.build() + self.state = state super.init(coder: coder) + observeState() + actionState() } required init?(coder: NSCoder) { @@ -60,7 +58,81 @@ class SlideOverWindowController: NSWindowController { window?.contentViewController as? SlideOverViewable } - var windowWillResizeHandler: ((NSWindow, NSSize) -> NSSize)? + private var frameObservation: NSKeyValueObservation? + private var isHiddenOutsideObservation: NSKeyValueObservation? + private var isHiddenCompletelyObservation: NSKeyValueObservation? + private var userAgentObservation: NSKeyValueObservation? + private var urlObservation: NSKeyValueObservation? + private var zoomObservation: NSKeyValueObservation? + + private func observeState() { + frameObservation = state.observe(\.frame, options: [.initial, .new]) { [weak self] state, changeValue in + guard let value = changeValue.newValue else { return } + self?.window?.setFrame(value, display: true, animate: true) + } + + isHiddenOutsideObservation = state.observe(\.isHidden, options: [.initial, .new]) { [weak self] state, changeValue in + guard let self = self, let value = changeValue.newValue else { return } + if self.userSettingService.isCompletelyHideWindow { + if value { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + NSApplication.shared.hide(nil) + } + } else { + NSApplication.shared.unhide(nil) + } + } else { + if value { + // hide + self.setWindowAlpha(0.4) + self.contentView?.showReappearRightButton(completion: {}) + self.contentView?.showReappearLeftButton(completion: {}) + } else { + // display + self.setWindowAlpha(1.0) + self.contentView?.hideReappearLeftButton(completion: {}) + self.contentView?.hideReappearRightButton(completion: {}) + } + } + } + + isHiddenCompletelyObservation = state.observe(\.isHiddenCompletely, options: [.initial, .new]) { state, changeValue in + guard let value = changeValue.newValue else { return } + if value { + // hide + NSApplication.shared.hide(nil) + } else { + // display + NSApplication.shared.unhide(nil) + } + } + + userAgentObservation = state.observe(\.userAgent, options: [.initial, .new]) { [weak self] state, changeValue in + guard let value = changeValue.newValue else { return } + self?.contentView?.webView.customUserAgent = (UserAgent(rawValue: value) ?? .desktop).context + self?.contentView?.webView.reloadFromOrigin() + } + + urlObservation = state.observe(\.url, options: [.initial, .new]) { [weak self] state, changeValue in + guard let value = changeValue.newValue else { return } + self?.contentView?.loadWebPage(url: value) + } + + zoomObservation = state.observe(\.zoom, options: [.initial, .new]) { [weak self] state, changeValue in + guard let value = changeValue.newValue else { return } + self?.contentView?.webView.setMagnification(value, centeredAt: .zero) + } + } + + private func actionState() { + state.reloadAction = { [weak self] in + self?.contentView?.browserReload() + } + + state.focusAction = { [weak self] in + self?.focusSearchBar() + } + } override func windowDidLoad() { window?.level = .floating @@ -84,15 +156,6 @@ class SlideOverWindowController: NSWindowController { @objc func didTapBrowserReloadItem(_ sender: Any) { contentView?.browserReload() } - - @objc func didTapRegisterInitialPageItem(_ sender: Any) { - guard let url = contentView?.currentUrl else { return } - action.didTapInitialPageItem(currentUrl: url) - } - - @objc func didTapWebDisplayTypeItem(_ sender: Any) { - action.didTapDisplayType() - } } extension SlideOverWindowController: NSWindowDelegate { @@ -100,9 +163,10 @@ extension SlideOverWindowController: NSWindowDelegate { action.windowWillClose(size: window?.frame.size) NSApplication.shared.terminate(nil) } - - func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize { - return windowWillResizeHandler?(sender, frameSize) ?? frameSize + + func windowDidEndLiveResize(_ notification: Notification) { + guard let windowFrame = window?.frame else { return } + action.windowDidEndLiveResize(windowFrame) } } @@ -110,7 +174,7 @@ extension SlideOverWindowController: NSSearchFieldDelegate { func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { if (commandSelector == #selector(NSResponder.insertNewline(_:))) { let urlString = searchBar.stringValue - action.inputSearchBar(input: urlString) + action.didEnterSearchBar(input: urlString) return false } else if (commandSelector == #selector(NSResponder.cancelOperation(_:))) { window?.makeFirstResponder(nil) diff --git a/slideover-for-macos/UI/Util/SlideOverWebView.swift b/slideover-for-macos/UI/Util/SlideOverWebView.swift index a84ef14..e69803e 100644 --- a/slideover-for-macos/UI/Util/SlideOverWebView.swift +++ b/slideover-for-macos/UI/Util/SlideOverWebView.swift @@ -1,15 +1,16 @@ import Foundation import WebKit +import Injectable /// @mockable protocol SlideOverWebViewMenuDelegate { func didTapCopyLink() func didTapOpenBrowser() - func didTapRegisterInitialPage() func didTapWindowLayout(type: SlideOverKind) func didTapUserAgent(_ userAgent: UserAgent) func didTapHideWindow() func didTapHelp() + func didTapSetting() } class SlideOverWebView: WKWebView { @@ -59,7 +60,8 @@ class SlideOverWebView: WKWebView { })), .item(data: .init(title: NSLocalizedString("Hide Window", comment: ""), action: #selector(didTapHideWindow), keyEquivalent: "s", keyEquivalentModify: [.command, .control], image: NSImage(systemSymbolName: "eye.slash", accessibilityDescription: nil))), .separator, - .item(data: .init(title: NSLocalizedString("Help", comment: ""), action: #selector(didTapHelp), keyEquivalent: "", image: NSImage(systemSymbolName: "questionmark.circle", accessibilityDescription: nil))) + .item(data: .init(title: NSLocalizedString("Help", comment: ""), action: #selector(didTapHelp), keyEquivalent: "", image: NSImage(systemSymbolName: "questionmark.circle", accessibilityDescription: nil))), + .item(data: .init(title: NSLocalizedString("Setting", comment: ""), action: #selector(didTapSetting), keyEquivalent: ",", image: NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil))) ] buildMenu(from: menuTree, for: menu) super.willOpenMenu(menu, with: event) @@ -72,11 +74,7 @@ class SlideOverWebView: WKWebView { @objc func didTapOpenBrowser() { delegate?.didTapOpenBrowser() } - - @objc func didTapRegisterInitialPage() { - delegate?.didTapRegisterInitialPage() - } - + @objc func didTapWindowLayoutForRight() { delegate?.didTapWindowLayout(type: .right) } @@ -116,4 +114,8 @@ class SlideOverWebView: WKWebView { @objc func didTapHelp() { delegate?.didTapHelp() } + + @objc func didTapSetting() { + delegate?.didTapSetting() + } } From cc83024dc95106b0881511c78cea90e1584e3587 Mon Sep 17 00:00:00 2001 From: N-HIROYASU Date: Sun, 17 Jul 2022 13:42:34 +0900 Subject: [PATCH 3/5] setting --- slideover-for-macos.xcodeproj/project.pbxproj | 30 ++++- slideover-for-macos/AppDelegate.swift | 1 + .../Coordinator/SettingCoordinator.swift | 32 ++++++ .../Coordinator/SlideOverCoordinator.swift | 27 +++-- .../SlideOverWindowAction.swift | 0 .../SlideOverWindowPresenter.swift | 0 .../SlideOverWindowState.swift | 0 .../SlideOverWindowUseCase.swift | 0 .../Module/SlideOver/SlideOverPresenter.swift | 13 ++- .../UI/Base.lproj/Main.storyboard | 105 ++++++------------ .../UI/Setting/Components/SwitchCell.swift | 35 ++++++ .../UI/Setting/Components/SwitchCell.xib | 88 +++++++++++++++ .../UI/Setting/SettingViewController.swift | 98 ++++++++++------ .../Util/NSCollectionView+.swift | 12 ++ .../ja.lproj/Localizable.strings | 3 + 15 files changed, 323 insertions(+), 121 deletions(-) create mode 100644 slideover-for-macos/Coordinator/SettingCoordinator.swift rename slideover-for-macos/Module/{SlideOverWindow => Deprecated_SlideOverWindow}/SlideOverWindowAction.swift (100%) rename slideover-for-macos/Module/{SlideOverWindow => Deprecated_SlideOverWindow}/SlideOverWindowPresenter.swift (100%) rename slideover-for-macos/Module/{SlideOverWindow => Deprecated_SlideOverWindow}/SlideOverWindowState.swift (100%) rename slideover-for-macos/Module/{SlideOverWindow => Deprecated_SlideOverWindow}/SlideOverWindowUseCase.swift (100%) create mode 100644 slideover-for-macos/UI/Setting/Components/SwitchCell.swift create mode 100644 slideover-for-macos/UI/Setting/Components/SwitchCell.xib create mode 100644 slideover-for-macos/Util/NSCollectionView+.swift diff --git a/slideover-for-macos.xcodeproj/project.pbxproj b/slideover-for-macos.xcodeproj/project.pbxproj index 9706fc5..807d53c 100644 --- a/slideover-for-macos.xcodeproj/project.pbxproj +++ b/slideover-for-macos.xcodeproj/project.pbxproj @@ -24,6 +24,10 @@ 4FB85FAB2882A11400D03E22 /* Injectable in Frameworks */ = {isa = PBXBuildFile; productRef = 4FB85FAA2882A11400D03E22 /* Injectable */; }; 4FB85FAD2882A1EE00D03E22 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */; }; 4FB85FAF2882A20600D03E22 /* Transitionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FAE2882A20600D03E22 /* Transitionable.swift */; }; + 4FB85FB12883B95500D03E22 /* SettingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FB02883B95500D03E22 /* SettingCoordinator.swift */; }; + 4FB85FB72883C10500D03E22 /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FB52883C10500D03E22 /* SwitchCell.swift */; }; + 4FB85FB82883C10500D03E22 /* SwitchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4FB85FB62883C10500D03E22 /* SwitchCell.xib */; }; + 4FB85FBA2883C29C00D03E22 /* NSCollectionView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB85FB92883C29C00D03E22 /* NSCollectionView+.swift */; }; 4FBCEAFA27C1FA3300BCF88C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */; }; 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */; }; 4FBCEAFE27C1FA3400BCF88C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FBCEAFD27C1FA3400BCF88C /* Assets.xcassets */; }; @@ -97,6 +101,10 @@ 4FB85FA72882A06200D03E22 /* SlideOverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverCoordinator.swift; sourceTree = ""; }; 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 4FB85FAE2882A20600D03E22 /* Transitionable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transitionable.swift; sourceTree = ""; }; + 4FB85FB02883B95500D03E22 /* SettingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingCoordinator.swift; sourceTree = ""; }; + 4FB85FB52883C10500D03E22 /* SwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchCell.swift; sourceTree = ""; }; + 4FB85FB62883C10500D03E22 /* SwitchCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SwitchCell.xib; sourceTree = ""; }; + 4FB85FB92883C29C00D03E22 /* NSCollectionView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSCollectionView+.swift"; sourceTree = ""; }; 4FBCEAF627C1FA3300BCF88C /* Fixture in Picture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Fixture in Picture.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4FBCEAF927C1FA3300BCF88C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4FBCEAFB27C1FA3300BCF88C /* SlideOverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideOverViewController.swift; sourceTree = ""; }; @@ -173,6 +181,7 @@ isa = PBXGroup; children = ( 4F9248F0283A3B5D0059C7AC /* String+.swift */, + 4FB85FB92883C29C00D03E22 /* NSCollectionView+.swift */, ); path = Util; sourceTree = ""; @@ -202,12 +211,22 @@ isa = PBXGroup; children = ( 4FB85FA72882A06200D03E22 /* SlideOverCoordinator.swift */, + 4FB85FB02883B95500D03E22 /* SettingCoordinator.swift */, 4FB85FAC2882A1EE00D03E22 /* Coordinator.swift */, 4FB85FAE2882A20600D03E22 /* Transitionable.swift */, ); path = Coordinator; sourceTree = ""; }; + 4FB85FB22883C0E400D03E22 /* Components */ = { + isa = PBXGroup; + children = ( + 4FB85FB52883C10500D03E22 /* SwitchCell.swift */, + 4FB85FB62883C10500D03E22 /* SwitchCell.xib */, + ); + path = Components; + sourceTree = ""; + }; 4FBCEAED27C1FA3300BCF88C = { isa = PBXGroup; children = ( @@ -311,12 +330,12 @@ isa = PBXGroup; children = ( 4FB85F9728827BCF00D03E22 /* SlideOver */, - 4FBCEB3E27C63B5900BCF88C /* SlideOverWindow */, + 4FBCEB3E27C63B5900BCF88C /* Deprecated_SlideOverWindow */, ); path = Module; sourceTree = ""; }; - 4FBCEB3E27C63B5900BCF88C /* SlideOverWindow */ = { + 4FBCEB3E27C63B5900BCF88C /* Deprecated_SlideOverWindow */ = { isa = PBXGroup; children = ( 4FBCEB3F27C63B6C00BCF88C /* SlideOverWindowAction.swift */, @@ -324,7 +343,7 @@ 4FBCEB3C27C63A3000BCF88C /* SlideOverWindowPresenter.swift */, 4FB85F95288272E300D03E22 /* SlideOverWindowState.swift */, ); - path = SlideOverWindow; + path = Deprecated_SlideOverWindow; sourceTree = ""; }; 4FBCEB4527C668A900BCF88C /* Util */ = { @@ -400,6 +419,7 @@ 4FEBB84F2822812E007B5DA3 /* Setting */ = { isa = PBXGroup; children = ( + 4FB85FB22883C0E400D03E22 /* Components */, 4FEBB8502822813F007B5DA3 /* SettingWindowController.swift */, 4FEBB852282282F7007B5DA3 /* SettingViewController.swift */, ); @@ -500,6 +520,7 @@ buildActionMask = 2147483647; files = ( 4FBCEAFE27C1FA3400BCF88C /* Assets.xcassets in Resources */, + 4FB85FB82883C10500D03E22 /* SwitchCell.xib in Resources */, 4FD65B9A27F5E5890021D3E5 /* Localizable.strings in Resources */, 4FBCEB0127C1FA3400BCF88C /* Main.storyboard in Resources */, ); @@ -520,6 +541,7 @@ buildActionMask = 2147483647; files = ( 4FBE65862821601D0001FB4E /* SlideOverView.swift in Sources */, + 4FB85FB12883B95500D03E22 /* SettingCoordinator.swift in Sources */, 4FBCEB3527C6285000BCF88C /* AppContainer.swift in Sources */, 4FB85F96288272E300D03E22 /* SlideOverWindowState.swift in Sources */, 4FBCEAFC27C1FA3300BCF88C /* SlideOverViewController.swift in Sources */, @@ -531,6 +553,7 @@ 4FBCEB1027C2709F00BCF88C /* SlideOverComputable.swift in Sources */, 4FBCEB4727C680A400BCF88C /* AlearService.swift in Sources */, 4FBCEB4227C63B8D00BCF88C /* SlideOverWindowUseCase.swift in Sources */, + 4FB85FB72883C10500D03E22 /* SwitchCell.swift in Sources */, 4FBE657E282124410001FB4E /* ShortcutService.swift in Sources */, 4FD65B9727F02BAF0021D3E5 /* MenuTree.swift in Sources */, 4F88368E2841E22D003C6333 /* ApplicationService.swift in Sources */, @@ -551,6 +574,7 @@ 4FB85FAD2882A1EE00D03E22 /* Coordinator.swift in Sources */, 4FBE658428215A540001FB4E /* SlideOverToolbar.swift in Sources */, 4FBCEB5127CBAE5600BCF88C /* WKPreferences.m in Sources */, + 4FB85FBA2883C29C00D03E22 /* NSCollectionView+.swift in Sources */, 4FBCEB4427C667EF00BCF88C /* SlideOverWebView.swift in Sources */, 4FEBB8512822813F007B5DA3 /* SettingWindowController.swift in Sources */, 4FB85FA32882908E00D03E22 /* SlideOverComputation.swift in Sources */, diff --git a/slideover-for-macos/AppDelegate.swift b/slideover-for-macos/AppDelegate.swift index 3bb68dd..cd219d2 100644 --- a/slideover-for-macos/AppDelegate.swift +++ b/slideover-for-macos/AppDelegate.swift @@ -21,6 +21,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { injector: Injector(container: container), state: initState ) + container.register(SlideOverTransition.self) { _ in self.slideOverCoordinator } let windowController = slideOverCoordinator.create() windowController.showWindow(self) } diff --git a/slideover-for-macos/Coordinator/SettingCoordinator.swift b/slideover-for-macos/Coordinator/SettingCoordinator.swift new file mode 100644 index 0000000..7a4e19e --- /dev/null +++ b/slideover-for-macos/Coordinator/SettingCoordinator.swift @@ -0,0 +1,32 @@ +import AppKit +import Swinject +import Injectable + +class SettingContainerBuilder { + static func build(parent: Container?) -> Container { + let container = Container(parent: parent) + return container + } +} + +class SettingCoordinator: Coordinator { + private var windowController: SettingWindowController? + private let injector: Injectable + + init(injector: Injectable) { + self.injector = injector + } + + func create() -> NSWindowController { + if let windowController = windowController { + return windowController + } else { + let storyboard = NSStoryboard(name: "Main", bundle: nil) + windowController = storyboard.instantiateController(identifier: "settingWindowController") { coder in + SettingWindowController(coder: coder) + } + return windowController! + } + } +} + diff --git a/slideover-for-macos/Coordinator/SlideOverCoordinator.swift b/slideover-for-macos/Coordinator/SlideOverCoordinator.swift index bbf02b8..4acdd92 100644 --- a/slideover-for-macos/Coordinator/SlideOverCoordinator.swift +++ b/slideover-for-macos/Coordinator/SlideOverCoordinator.swift @@ -24,13 +24,11 @@ class SlideOverContainerBuilder { } class SlideOverCoordinator: Coordinator { - private var windowController: SlideOverWindowController! + private var windowController: SlideOverWindowController? + private lazy var settingCoordinator: SettingCoordinator = .init(injector: injector) private let injector: Injectable private let state: SlideOverState - private let notificationObserver: SlideOverNotificationObserver - private lazy var action: SlideOverAction = injector.build() - private lazy var useCase: SlideOverUseCase = injector.build() - private lazy var presenter: SlideOverPresenter = injector.build() + private let notificationObserver: SlideOverNotificationObserver init(injector: Injectable, state: SlideOverState) { self.injector = injector @@ -39,10 +37,21 @@ class SlideOverCoordinator: Coordinator { } func create() -> NSWindowController { - let storyboard = NSStoryboard(name: "Main", bundle: nil) - windowController = storyboard.instantiateController(identifier: "slideOverWindowController") { coder in - SlideOverWindowController(coder: coder, injector: self.injector, state: self.state) + if let windowController = windowController { + return windowController + } else { + let storyboard = NSStoryboard(name: "Main", bundle: nil) + windowController = storyboard.instantiateController(identifier: "slideOverWindowController") { coder in + SlideOverWindowController(coder: coder, injector: self.injector, state: self.state) + } + return windowController! } - return windowController + } +} + +extension SlideOverCoordinator: SlideOverTransition { + func openSettingWindow() { + let windowController = settingCoordinator.create() + windowController.showWindow(self) } } diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowAction.swift similarity index 100% rename from slideover-for-macos/Module/SlideOverWindow/SlideOverWindowAction.swift rename to slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowAction.swift diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift similarity index 100% rename from slideover-for-macos/Module/SlideOverWindow/SlideOverWindowPresenter.swift rename to slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowState.swift similarity index 100% rename from slideover-for-macos/Module/SlideOverWindow/SlideOverWindowState.swift rename to slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowState.swift diff --git a/slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowUseCase.swift similarity index 100% rename from slideover-for-macos/Module/SlideOverWindow/SlideOverWindowUseCase.swift rename to slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowUseCase.swift diff --git a/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift index 50776d3..08a2781 100644 --- a/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift +++ b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift @@ -1,6 +1,10 @@ import Foundation import Injectable +protocol SlideOverTransition { + func openSettingWindow() +} + protocol SlideOverPresenter { func fix(at frame: NSRect) func fixWithDisappear(at frame: NSRect) @@ -17,16 +21,17 @@ protocol SlideOverPresenter { class SlideOverPresenterImpl: SlideOverPresenter { + private let injector: Injectable private let state: SlideOverState private let applicationService: ApplicationService private var userSettingService: UserSettingService - private let windowManager: WindowManager + private lazy var transition: SlideOverTransition? = injector.buildSafe() internal init(injector: Injectable = Injector.shared, state: SlideOverState) { + self.injector = injector self.state = state - self.applicationService = injector.build(ApplicationService.self) + self.applicationService = injector.build() self.userSettingService = injector.build() - self.windowManager = injector.build() } func fix(at frame: NSRect) { @@ -78,6 +83,6 @@ class SlideOverPresenterImpl: SlideOverPresenter { } func openSettingWindow() { - windowManager.lunch(.setting) + transition?.openSettingWindow() } } diff --git a/slideover-for-macos/UI/Base.lproj/Main.storyboard b/slideover-for-macos/UI/Base.lproj/Main.storyboard index c828762..c65caac 100644 --- a/slideover-for-macos/UI/Base.lproj/Main.storyboard +++ b/slideover-for-macos/UI/Base.lproj/Main.storyboard @@ -455,90 +455,49 @@ DQ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + + + - - + - + diff --git a/slideover-for-macos/UI/Setting/Components/SwitchCell.swift b/slideover-for-macos/UI/Setting/Components/SwitchCell.swift new file mode 100644 index 0000000..c9b7d61 --- /dev/null +++ b/slideover-for-macos/UI/Setting/Components/SwitchCell.swift @@ -0,0 +1,35 @@ +import Cocoa + +struct SwitchCellViewData { + let switchState: Bool + let title: String + let description: String? +} + +class SwitchCell: NSCollectionViewItem { + + @IBOutlet weak var switchItem: NSSwitch! + @IBOutlet weak var titleLabel: NSTextField! + @IBOutlet weak var descriptionLabel: NSTextField! + private var switchHandler: ((Bool) -> Void)? + + override func viewDidLoad() { + super.viewDidLoad() + } + + func set(_ viewData: SwitchCellViewData, switchHandler: @escaping (Bool) -> Void) { + switchItem.state = viewData.switchState ? .on : .off + titleLabel.stringValue = viewData.title + if let description = viewData.description { + descriptionLabel.isHidden = false + descriptionLabel.stringValue = description + } else { + descriptionLabel.isHidden = true + } + self.switchHandler = switchHandler + } + + @IBAction func didTapSwitchItem(_ sender: Any) { + switchHandler?(switchItem.state == .on) + } +} diff --git a/slideover-for-macos/UI/Setting/Components/SwitchCell.xib b/slideover-for-macos/UI/Setting/Components/SwitchCell.xib new file mode 100644 index 0000000..63037f8 --- /dev/null +++ b/slideover-for-macos/UI/Setting/Components/SwitchCell.xib @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slideover-for-macos/UI/Setting/SettingViewController.swift b/slideover-for-macos/UI/Setting/SettingViewController.swift index 93c9f98..69cd2de 100644 --- a/slideover-for-macos/UI/Setting/SettingViewController.swift +++ b/slideover-for-macos/UI/Setting/SettingViewController.swift @@ -1,52 +1,86 @@ import Cocoa import Injectable +struct SwitchItemProps { + let isOn: Bool + let title: String + let description: String + let action: (Bool) -> Void +} + class SettingViewController: NSViewController { - @IBOutlet weak var hideWindowShortcutSwitchButton: NSSwitch! - @IBOutlet weak var showALittleWhenHideWindowSwitch: NSSwitch! + @IBOutlet weak var collectionView: NSCollectionView! { + didSet { + collectionView.register(Type: SwitchCell.self) + collectionView.dataSource = self + collectionView.collectionViewLayout = createLayout() + } + } var userSetting: UserSettingService? - var globalShortcutService: GlobalShortcutService? var alertService: AlertService? + lazy var switchItems: [SwitchItemProps] = [ + .init( + isOn: !(userSetting?.isNotAllowedGlobalShortcut ?? false), + title: NSLocalizedString("⌘+⌃+s : hide window", comment: ""), + description: NSLocalizedString("The 'Hide Window' function can be invoked by pressing ⌘ (command key), ^ (control key) and s (S key) simultaneously.", comment: ""), + action: { [weak self] isOn in + if isOn { + self?.userSetting?.isNotAllowedGlobalShortcut = false + self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) + } else { + self?.userSetting?.isNotAllowedGlobalShortcut = true + self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) + } + } + ), + .init( + isOn: !(userSetting?.isCompletelyHideWindow ?? false), + title: NSLocalizedString("Show a little when 'Hide window'", comment: ""), + description: NSLocalizedString("When the 'Hide Window' function is executed, a portion of the window remains on the screen.", comment: ""), + action: { [weak self] isOn in + if isOn { + self?.userSetting?.isCompletelyHideWindow = false + } else { + self?.userSetting?.isCompletelyHideWindow = true + } + } + ) + ] override func viewDidLoad() { super.viewDidLoad() userSetting = Injector.shared.buildSafe(UserSettingService.self) - globalShortcutService = Injector.shared.buildSafe(GlobalShortcutService.self) alertService = Injector.shared.buildSafe(AlertService.self) - - if let isNotAllow = userSetting?.isNotAllowedGlobalShortcut { - hideWindowShortcutSwitchButton.state = isNotAllow ? .off : .on - } - - if let isCompletelyHide = userSetting?.isCompletelyHideWindow { - showALittleWhenHideWindowSwitch.state = isCompletelyHide ? .off : .on - } } - @IBAction func didTapHideWindowShortcutSwitch(_ sender: Any) { - switch hideWindowShortcutSwitchButton.state { - case .on: - userSetting?.isNotAllowedGlobalShortcut = false - alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) - case .off: - userSetting?.isNotAllowedGlobalShortcut = true - alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) - default: - break - } - + func createLayout() -> NSCollectionViewLayout { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .estimated(64.0)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, + subitems: [item]) + let section = NSCollectionLayoutSection(group: group) + let layout = NSCollectionViewCompositionalLayout(section: section) + return layout + } +} + +extension SettingViewController: NSCollectionViewDataSource { + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + switchItems.count } - @IBAction func didTapShowALittleWhenHideWindowSwitch(_ sender: Any) { - switch showALittleWhenHideWindowSwitch.state { - case .on: - userSetting?.isCompletelyHideWindow = false - case .off: - userSetting?.isCompletelyHideWindow = true - default: - break + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let view = collectionView.dequeueItem(Type: SwitchCell.self, for: indexPath) + let item = switchItems[indexPath.item] + view.set(SwitchCellViewData(switchState: item.isOn, title: item.title, description: item.description)) { isOn in + item.action(isOn) } + return view } } diff --git a/slideover-for-macos/Util/NSCollectionView+.swift b/slideover-for-macos/Util/NSCollectionView+.swift new file mode 100644 index 0000000..948600a --- /dev/null +++ b/slideover-for-macos/Util/NSCollectionView+.swift @@ -0,0 +1,12 @@ +import Cocoa + +extension NSCollectionView { + + func register(Type: Any.Type) { + self.register(NSNib(nibNamed: String(describing: Type), bundle: nil), forItemWithIdentifier: .init(rawValue: String(describing: Type))) + } + + func dequeueItem(Type: T.Type, for indexPath: IndexPath) -> T { + self.makeItem(withIdentifier: .init(rawValue: String(describing: Type)), for: indexPath) as! T + } +} diff --git a/slideover-for-macos/ja.lproj/Localizable.strings b/slideover-for-macos/ja.lproj/Localizable.strings index 1ab376e..3d8f294 100644 --- a/slideover-for-macos/ja.lproj/Localizable.strings +++ b/slideover-for-macos/ja.lproj/Localizable.strings @@ -18,3 +18,6 @@ "Setting" = "設定"; "⌘+⌃+s : hide window" = "⌘+⌃+s でウィンドウを隠す"; "Please restart the application" = "アプリを再起動してください"; +"Show a little when 'Hide window'" = "'ウィンドウを隠す'をした際、一部分だけ表示しておく"; +"The 'Hide Window' function can be invoked by pressing ⌘ (command key), ^ (control key) and s (S key) simultaneously." = "'ウィンドウを隠す' 機能を⌘(コマンドキー), ^(コントロールキー), s(Sキー)の同時押しで呼び出すことができます。"; +"When the 'Hide Window' function is executed, a portion of the window remains on the screen." = "'ウィンドウを隠す' 機能を実行すると、ウィンドウの一部が画面上に残ります。"; From 2429df7ed7fb8fd9a7889c0ce1a988496d9e0271 Mon Sep 17 00:00:00 2001 From: N-HIROYASU Date: Sun, 17 Jul 2022 14:50:19 +0900 Subject: [PATCH 4/5] fix hidden function --- slideover-for-macos.xcodeproj/project.pbxproj | 8 +++--- .../SlideOverWindowPresenter.swift | 4 +-- .../Module/SlideOver/SlideOverPresenter.swift | 2 ++ .../Module/SlideOver/SlideOverUseCase.swift | 4 +-- .../Service/UserSettingService.swift | 12 ++++++--- .../UI/Setting/SettingViewController.swift | 12 ++++----- .../UI/SlideOverWindowController.swift | 25 ++++++++++++++----- .../ja.lproj/Localizable.strings | 2 +- .../Mocks/mock.generated.swift | 4 +-- 9 files changed, 46 insertions(+), 27 deletions(-) diff --git a/slideover-for-macos.xcodeproj/project.pbxproj b/slideover-for-macos.xcodeproj/project.pbxproj index 807d53c..144ab14 100644 --- a/slideover-for-macos.xcodeproj/project.pbxproj +++ b/slideover-for-macos.xcodeproj/project.pbxproj @@ -769,7 +769,7 @@ CODE_SIGN_ENTITLEMENTS = "slideover-for-macos/slideover_for_macos.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = 2CMV7D36JC; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -784,7 +784,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nhiro1109.${BUNDLE_ID_PREFIX}slideover-for-macos"; PRODUCT_NAME = "${TARGET_NAME}"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -804,7 +804,7 @@ CODE_SIGN_ENTITLEMENTS = "slideover-for-macos/slideover_for_macos.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = 2CMV7D36JC; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -819,7 +819,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nhiro1109.slideover-for-macos"; PRODUCT_NAME = "${DISPLAY_NAME_PREFIX}${TARGET_NAME}"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift index 5935a5b..61e2f5e 100644 --- a/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift +++ b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift @@ -177,7 +177,7 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { return } - if self.userSetting.isCompletelyHideWindow { + if self.userSetting.hiddenActionIsMiniaturized { self.windowSizeBeforeHideCompletely = window.frame.size let isSuccess = self.slideOverService.hideWindowCompletely(for: window, type: position) NSApplication.shared.hide(nil) @@ -218,7 +218,7 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { return } - if self.userSetting.isCompletelyHideWindow { + if self.userSetting.hiddenActionIsMiniaturized { NSApplication.shared.unhide(nil) if let windowSize = self.windowSizeBeforeHideCompletely { self.slideOverService.arrangeWindow(for: window, windowSize: windowSize, type: position) diff --git a/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift index 08a2781..c0ee392 100644 --- a/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift +++ b/slideover-for-macos/Module/SlideOver/SlideOverPresenter.swift @@ -25,6 +25,7 @@ class SlideOverPresenterImpl: SlideOverPresenter { private let state: SlideOverState private let applicationService: ApplicationService private var userSettingService: UserSettingService + private let alertService: AlertService private lazy var transition: SlideOverTransition? = injector.buildSafe() internal init(injector: Injectable = Injector.shared, state: SlideOverState) { @@ -32,6 +33,7 @@ class SlideOverPresenterImpl: SlideOverPresenter { self.state = state self.applicationService = injector.build() self.userSettingService = injector.build() + self.alertService = injector.build() } func fix(at frame: NSRect) { diff --git a/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift b/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift index 7814ec9..a31fcaf 100644 --- a/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift +++ b/slideover-for-macos/Module/SlideOver/SlideOverUseCase.swift @@ -122,8 +122,8 @@ class SlideOverInteractor: SlideOverUseCase { /// SlideOverを非表示にする func disappear(for windowFrame: ObjectFrame) { let newFrame: NSRect - if userSettingService.isCompletelyHideWindow { - newFrame = slideOverComputation.disappearCompletely(for: windowFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) + if userSettingService.hiddenActionIsMiniaturized { + newFrame = windowFrame.frame } else { newFrame = slideOverComputation.disappearOutside(for: windowFrame, type: userSettingService.latestPosition ?? defaultSlideOverPosition) } diff --git a/slideover-for-macos/Service/UserSettingService.swift b/slideover-for-macos/Service/UserSettingService.swift index d2c7765..443daae 100644 --- a/slideover-for-macos/Service/UserSettingService.swift +++ b/slideover-for-macos/Service/UserSettingService.swift @@ -8,7 +8,7 @@ protocol UserSettingService { var latestWindowSize: NSSize? { get set } var latestUserAgent: UserAgent? { get set } var isNotAllowedGlobalShortcut: Bool { get set } - var isCompletelyHideWindow: Bool { get set } + var hiddenActionIsMiniaturized: Bool { get set } var latestShownFeatureVersion: String? { get set } } @@ -66,12 +66,16 @@ class UserSettingServiceImpl: UserSettingService { } } - var isCompletelyHideWindow: Bool { + var hiddenActionIsMiniaturized: Bool { get { - userDefaults.bool(forKey: "isCompletelyHideWindow") + if userDefaults.object(forKey: "hiddenActionIsMiniaturized") == nil { + return true + } else { + return userDefaults.bool(forKey: "hiddenActionIsMiniaturized") + } } set { - userDefaults.set(newValue, forKey: "isCompletelyHideWindow") + userDefaults.set(newValue, forKey: "hiddenActionIsMiniaturized") } } diff --git a/slideover-for-macos/UI/Setting/SettingViewController.swift b/slideover-for-macos/UI/Setting/SettingViewController.swift index 69cd2de..511a4db 100644 --- a/slideover-for-macos/UI/Setting/SettingViewController.swift +++ b/slideover-for-macos/UI/Setting/SettingViewController.swift @@ -27,23 +27,23 @@ class SettingViewController: NSViewController { action: { [weak self] isOn in if isOn { self?.userSetting?.isNotAllowedGlobalShortcut = false - self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) } else { self?.userSetting?.isNotAllowedGlobalShortcut = true - self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) } + self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) } ), .init( - isOn: !(userSetting?.isCompletelyHideWindow ?? false), + isOn: !(userSetting?.hiddenActionIsMiniaturized ?? true), title: NSLocalizedString("Show a little when 'Hide window'", comment: ""), - description: NSLocalizedString("When the 'Hide Window' function is executed, a portion of the window remains on the screen.", comment: ""), + description: NSLocalizedString("When the 'Hide Window' function is executed, a portion of the window remains on the screen.\nIf the switch is turned OFF, the window is minimized.", comment: ""), action: { [weak self] isOn in if isOn { - self?.userSetting?.isCompletelyHideWindow = false + self?.userSetting?.hiddenActionIsMiniaturized = false } else { - self?.userSetting?.isCompletelyHideWindow = true + self?.userSetting?.hiddenActionIsMiniaturized = true } + self?.alertService?.alert(msg: NSLocalizedString("Please restart the application", comment: ""), completionHandler: {}) } ) ] diff --git a/slideover-for-macos/UI/SlideOverWindowController.swift b/slideover-for-macos/UI/SlideOverWindowController.swift index 79dd580..7f1e248 100644 --- a/slideover-for-macos/UI/SlideOverWindowController.swift +++ b/slideover-for-macos/UI/SlideOverWindowController.swift @@ -58,12 +58,15 @@ class SlideOverWindowController: NSWindowController { window?.contentViewController as? SlideOverViewable } + // state observation private var frameObservation: NSKeyValueObservation? private var isHiddenOutsideObservation: NSKeyValueObservation? private var isHiddenCompletelyObservation: NSKeyValueObservation? private var userAgentObservation: NSKeyValueObservation? private var urlObservation: NSKeyValueObservation? private var zoomObservation: NSKeyValueObservation? + // window observation + private var isMiniaturizedObservation: NSKeyValueObservation? private func observeState() { frameObservation = state.observe(\.frame, options: [.initial, .new]) { [weak self] state, changeValue in @@ -73,13 +76,11 @@ class SlideOverWindowController: NSWindowController { isHiddenOutsideObservation = state.observe(\.isHidden, options: [.initial, .new]) { [weak self] state, changeValue in guard let self = self, let value = changeValue.newValue else { return } - if self.userSettingService.isCompletelyHideWindow { + if self.userSettingService.hiddenActionIsMiniaturized { if value { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - NSApplication.shared.hide(nil) - } + self.window?.miniaturize(nil) } else { - NSApplication.shared.unhide(nil) + self.window?.deminiaturize(nil) } } else { if value { @@ -136,7 +137,11 @@ class SlideOverWindowController: NSWindowController { override func windowDidLoad() { window?.level = .floating - window?.styleMask = [.borderless, .utilityWindow, .titled, .closable, .miniaturizable, .resizable] + if userSettingService.hiddenActionIsMiniaturized { + window?.styleMask = [.borderless, .utilityWindow, .titled, .closable, .miniaturizable, .resizable] + } else { + window?.styleMask = [.borderless, .utilityWindow, .titled, .closable, .resizable] + } window?.collectionBehavior = [.canJoinAllSpaces] } @@ -168,6 +173,14 @@ extension SlideOverWindowController: NSWindowDelegate { guard let windowFrame = window?.frame else { return } action.windowDidEndLiveResize(windowFrame) } + + func windowDidMiniaturize(_ notification: Notification) { + state.isHidden = true + } + + func windowDidDeminiaturize(_ notification: Notification) { + state.isHidden = false + } } extension SlideOverWindowController: NSSearchFieldDelegate { diff --git a/slideover-for-macos/ja.lproj/Localizable.strings b/slideover-for-macos/ja.lproj/Localizable.strings index 3d8f294..aafee51 100644 --- a/slideover-for-macos/ja.lproj/Localizable.strings +++ b/slideover-for-macos/ja.lproj/Localizable.strings @@ -20,4 +20,4 @@ "Please restart the application" = "アプリを再起動してください"; "Show a little when 'Hide window'" = "'ウィンドウを隠す'をした際、一部分だけ表示しておく"; "The 'Hide Window' function can be invoked by pressing ⌘ (command key), ^ (control key) and s (S key) simultaneously." = "'ウィンドウを隠す' 機能を⌘(コマンドキー), ^(コントロールキー), s(Sキー)の同時押しで呼び出すことができます。"; -"When the 'Hide Window' function is executed, a portion of the window remains on the screen." = "'ウィンドウを隠す' 機能を実行すると、ウィンドウの一部が画面上に残ります。"; +"When the 'Hide Window' function is executed, a portion of the window remains on the screen.\nIf the switch is turned OFF, the window is minimized." = "'ウィンドウを隠す' 機能を実行すると、ウィンドウの一部が画面上に残ります。\nスイッチをOFFにした場合はウィンドウが最小化されます。"; diff --git a/slideover-for-macosTests/Mocks/mock.generated.swift b/slideover-for-macosTests/Mocks/mock.generated.swift index a8522f9..2e77216 100644 --- a/slideover-for-macosTests/Mocks/mock.generated.swift +++ b/slideover-for-macosTests/Mocks/mock.generated.swift @@ -236,7 +236,7 @@ class UserSettingServiceMock: UserSettingService { self.latestWindowSize = latestWindowSize self.latestUserAgent = latestUserAgent self.isNotAllowedGlobalShortcut = isNotAllowedGlobalShortcut - self.isCompletelyHideWindow = isCompletelyHideWindow + self.hiddenActionIsMiniaturized = isCompletelyHideWindow self.latestShownFeatureVersion = latestShownFeatureVersion } @@ -260,7 +260,7 @@ class UserSettingServiceMock: UserSettingService { var isNotAllowedGlobalShortcut: Bool = false { didSet { isNotAllowedGlobalShortcutSetCallCount += 1 } } private(set) var isCompletelyHideWindowSetCallCount = 0 - var isCompletelyHideWindow: Bool = false { didSet { isCompletelyHideWindowSetCallCount += 1 } } + var hiddenActionIsMiniaturized: Bool = false { didSet { isCompletelyHideWindowSetCallCount += 1 } } private(set) var latestShownFeatureVersionSetCallCount = 0 var latestShownFeatureVersion: String? = nil { didSet { latestShownFeatureVersionSetCallCount += 1 } } From 7fda2fe79d7fba2b3ed638391fe9e92d01a91e50 Mon Sep 17 00:00:00 2001 From: N-HIROYASU Date: Sun, 17 Jul 2022 16:07:40 +0900 Subject: [PATCH 5/5] toolbar item --- .../SlideOverWindowPresenter.swift | 9 -- .../Module/SlideOver/SlideOverAction.swift | 59 +++++++++++- .../UI/Base.lproj/Main.storyboard | 30 ++++-- .../UI/SlideOverWindowController.swift | 94 ++++++++++++++++++- slideover-for-macos/UI/ja.lproj/Main.strings | 2 + 5 files changed, 171 insertions(+), 23 deletions(-) diff --git a/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift index 61e2f5e..f05cda8 100644 --- a/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift +++ b/slideover-for-macos/Module/Deprecated_SlideOverWindow/SlideOverWindowPresenter.swift @@ -140,15 +140,6 @@ class SlideOverWindowPresenterImpl: SlideOverWindowPresenter { func setUserAgent(_ userAgent: UserAgent) { self.output?.contentView?.webView.customUserAgent = userAgent.context self.output?.contentView?.webView.reloadFromOrigin() - - switch userAgent { - case .desktop: - self.output?.webDisplayTypeItem.image = NSImage(systemSymbolName: "iphone", accessibilityDescription: nil) - self.output?.webDisplayTypeItem.label = NSLocalizedString("Mobile", comment: "") - case .phone: - self.output?.webDisplayTypeItem.image = NSImage(systemSymbolName: "laptopcomputer", accessibilityDescription: nil) - self.output?.webDisplayTypeItem.label = NSLocalizedString("Desktop", comment: "") - } } func setResizeHandler(handler: @escaping (NSSize, NSSize) -> (NSSize, SlideOverKind)) { diff --git a/slideover-for-macos/Module/SlideOver/SlideOverAction.swift b/slideover-for-macos/Module/SlideOver/SlideOverAction.swift index a484f3c..1cbbf44 100644 --- a/slideover-for-macos/Module/SlideOver/SlideOverAction.swift +++ b/slideover-for-macos/Module/SlideOver/SlideOverAction.swift @@ -2,6 +2,7 @@ import Foundation import Injectable protocol SlideOverAction { + // view func showWindow() func windowWillClose(size: NSSize?) func windowDidEndLiveResize(_ frame: NSRect) @@ -12,7 +13,19 @@ protocol SlideOverAction { func didTapHelp() func didTapSetting() func didTapReappearButton() - + // menu + func didTapWindowLayoutForRightMenuItem() + func didTapWindowLayoutForLeftMenuItem() + func didTapWindowLayoutForRightTopMenuItem() + func didTapWindowLayoutForRightBottomMenuItem() + func didTapWindowLayoutForLeftTopMenuItem() + func didTapWindowLayoutForLeftBottomMenuItem() + func didTapUserAgentForMobileMenuItem() + func didTapUserAgentForDesktopMenuItem() + func didTapHideWindowMenuItem() + func didTapHelpMenuItem() + func didTapSettingMenuItem() + // notification func leftClickUpMouseButton() func doubleRightClickMouseButton() func reloadNotification() @@ -141,4 +154,48 @@ class SlideOverActionImpl: SlideOverAction { func zoomResetNotification() { useCase.zoomReset() } + + func didTapWindowLayoutForRightMenuItem() { + useCase.requestChangingPosition(type: .right) + } + + func didTapWindowLayoutForLeftMenuItem() { + useCase.requestChangingPosition(type: .left) + } + + func didTapWindowLayoutForRightTopMenuItem() { + useCase.requestChangingPosition(type: .topRight) + } + + func didTapWindowLayoutForRightBottomMenuItem() { + useCase.requestChangingPosition(type: .bottomRight) + } + + func didTapWindowLayoutForLeftTopMenuItem() { + useCase.requestChangingPosition(type: .topLeft) + } + + func didTapWindowLayoutForLeftBottomMenuItem() { + useCase.requestChangingPosition(type: .bottomLeft) + } + + func didTapUserAgentForMobileMenuItem() { + useCase.updateUserAgent(.phone) + } + + func didTapUserAgentForDesktopMenuItem() { + useCase.updateUserAgent(.desktop) + } + + func didTapHideWindowMenuItem() { + useCase.disappear(for: ObjectFrame(from: state.cacheFrame)) + } + + func didTapHelpMenuItem() { + useCase.showHelp() + } + + func didTapSettingMenuItem() { + useCase.showSetting() + } } diff --git a/slideover-for-macos/UI/Base.lproj/Main.storyboard b/slideover-for-macos/UI/Base.lproj/Main.storyboard index c65caac..6dd75be 100644 --- a/slideover-for-macos/UI/Base.lproj/Main.storyboard +++ b/slideover-for-macos/UI/Base.lproj/Main.storyboard @@ -527,15 +527,30 @@ DQ - - - + + + + + + + + + + + + + + + + + @@ -543,13 +558,12 @@ DQ - + + - - @@ -622,10 +636,8 @@ DQ - - + - diff --git a/slideover-for-macos/UI/SlideOverWindowController.swift b/slideover-for-macos/UI/SlideOverWindowController.swift index 7f1e248..98fd562 100644 --- a/slideover-for-macos/UI/SlideOverWindowController.swift +++ b/slideover-for-macos/UI/SlideOverWindowController.swift @@ -14,7 +14,6 @@ protocol SlideOverWindowControllable { var progressBar: NSProgressIndicator? { get } var action: SlideOverAction { get } var contentView: SlideOverViewable? { get } - var webDisplayTypeItem: NSToolbarItem! { get } } class SlideOverWindowController: NSWindowController { @@ -26,14 +25,21 @@ class SlideOverWindowController: NSWindowController { browserReloadItem.action = #selector(didTapBrowserReloadItem(_:)) } } - @IBOutlet weak var registerInitialPageItem: NSToolbarItem! @IBOutlet weak var searchBar: NSSearchField! { didSet { searchBar.delegate = self } } - @IBOutlet weak var webDisplayTypeItem: NSToolbarItem! - @IBOutlet weak var bookmarkItem: NSToolbarItem! + @IBOutlet weak var actionItem: NSToolbarItem! { + didSet { + actionItem.action = #selector(didTapActionItem(_:)) + } + } + @IBOutlet weak var actionPopupButton: NSPopUpButton! { + didSet { + setUpPopUpButton() + } + } private let state: SlideOverState let action: SlideOverAction @@ -161,6 +167,52 @@ class SlideOverWindowController: NSWindowController { @objc func didTapBrowserReloadItem(_ sender: Any) { contentView?.browserReload() } + + @objc func didTapActionItem(_ sender: Any) {} + + @objc func didTapWindowLayoutForRight() { + action.didTapWindowLayoutForRightMenuItem() + } + + @objc func didTapWindowLayoutForLeft() { + action.didTapWindowLayoutForLeftMenuItem() + } + + @objc func didTapWindowLayoutForRightTop() { + action.didTapWindowLayoutForRightTopMenuItem() + } + + @objc func didTapWindowLayoutForRightBottom() { + action.didTapWindowLayoutForRightBottomMenuItem() + } + + @objc func didTapWindowLayoutForLeftTop() { + action.didTapWindowLayoutForLeftTopMenuItem() + } + + @objc func didTapWindowLayoutForLeftBottom() { + action.didTapWindowLayoutForLeftBottomMenuItem() + } + + @objc func didTapUserAgentForMobile() { + action.didTapUserAgentForMobileMenuItem() + } + + @objc func didTapUserAgentForDesktop() { + action.didTapUserAgentForDesktopMenuItem() + } + + @objc func didTapHideWindow() { + action.didTapHideWindowMenuItem() + } + + @objc func didTapHelp() { + action.didTapHelpMenuItem() + } + + @objc func didTapSetting() { + action.didTapSettingMenuItem() + } } extension SlideOverWindowController: NSWindowDelegate { @@ -241,3 +293,37 @@ extension SlideOverWindowController: SlideOverWindowControllable { } } +extension SlideOverWindowController { + private func setUpPopUpButton() { + let menuTree: [MenuItemType] = [ + .separator, + .subMenu(data: .init(title: NSLocalizedString("Window Layout", comment: ""), image: NSImage(systemSymbolName: "uiwindow.split.2x1", accessibilityDescription: nil), items: [ + .init(title: NSLocalizedString("Left", comment: ""), action: #selector(didTapWindowLayoutForRight), keyEquivalent: "", image: NSImage(named: "window_layout_right"), value: SlideOverKind.right), + .init(title: NSLocalizedString("Right", comment: ""), action: #selector(didTapWindowLayoutForLeft), keyEquivalent: "", image: NSImage(named: "window_layout_left"), value: SlideOverKind.left), + .init(title: NSLocalizedString("Top Right", comment: ""), action: #selector(didTapWindowLayoutForRightTop), keyEquivalent: "", image: NSImage(named: "window_layout_right_top"), value: SlideOverKind.topRight), + .init(title: NSLocalizedString("Bottom Right", comment: ""), action: #selector(didTapWindowLayoutForRightBottom), keyEquivalent: "", image: NSImage(named: "window_layout_right_bottom"), value: SlideOverKind.bottomRight), + .init(title: NSLocalizedString("Top Left", comment: ""), action: #selector(didTapWindowLayoutForLeftTop), keyEquivalent: "", image: NSImage(named: "window_layout_left_top"), value: SlideOverKind.topLeft), + .init(title: NSLocalizedString("Bottom Left", comment: ""), action: #selector(didTapWindowLayoutForLeftBottom), keyEquivalent: "", image: NSImage(named: "window_layout_left_bottom"), value: SlideOverKind.bottomLeft), + ], customHandler: { [weak self] data, menuItem in + if self?.userSettingService.latestPosition == data.value as? SlideOverKind { + menuItem.state = .on + } + })), + .subMenu(data: .init(title: NSLocalizedString("Switch Display", comment: ""), image: NSImage(systemSymbolName: "display.2", accessibilityDescription: nil), items: [ + .init(title: NSLocalizedString("Mobile", comment: ""), action: #selector(didTapUserAgentForMobile), keyEquivalent: "", image: NSImage(systemSymbolName: "iphone", accessibilityDescription: nil), value: UserAgent.phone), + .init(title: NSLocalizedString("Desktop", comment: ""), action: #selector(didTapUserAgentForDesktop), keyEquivalent: "", image: NSImage(systemSymbolName: "laptopcomputer", accessibilityDescription: nil), value: UserAgent.desktop), + ], customHandler: { [weak self] data, menuItem in + if self?.userSettingService.latestUserAgent == data.value as? UserAgent { + menuItem.state = .on + } + })), + .item(data: .init(title: NSLocalizedString("Hide Window", comment: ""), action: #selector(didTapHideWindow), keyEquivalent: "s", keyEquivalentModify: [.command, .control], image: NSImage(systemSymbolName: "eye.slash", accessibilityDescription: nil))), + .separator, + .item(data: .init(title: NSLocalizedString("Help", comment: ""), action: #selector(didTapHelp), keyEquivalent: "", image: NSImage(systemSymbolName: "questionmark.circle", accessibilityDescription: nil))), + .item(data: .init(title: NSLocalizedString("Setting", comment: ""), action: #selector(didTapSetting), keyEquivalent: ",", image: NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil))) + ] + let menu = actionPopupButton.menu! + buildMenu(from: menuTree, for: menu) + actionPopupButton.menu = menu + } +} diff --git a/slideover-for-macos/UI/ja.lproj/Main.strings b/slideover-for-macos/UI/ja.lproj/Main.strings index 63de587..8ac0cd9 100644 --- a/slideover-for-macos/UI/ja.lproj/Main.strings +++ b/slideover-for-macos/UI/ja.lproj/Main.strings @@ -240,3 +240,5 @@ "VZZ-xk-zcZ.title" = "ズームリセット"; "Df5-dj-PCz.title" = "「ウィンドウを隠す」をした時、一部分だけ表示しておく"; + +"RSZ-k7-hNP.label" = "アクション";