Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion LocalPackage/Sources/Model/Stores/CustomRunnerEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public final class CustomRunnerEditor: Composable, Identifiable {
public let id: UUID
public var runnerName: String
public var isTemplate: Bool
public var allowsWideFrames: Bool
public var frameImages: [FrameImage]
public var selectingFrameImage: FrameImage?
public var previewingFrameImage: FrameImage?
Expand All @@ -53,6 +54,7 @@ public final class CustomRunnerEditor: Composable, Identifiable {
id: UUID,
runnerName: String = "",
isTemplate: Bool = true,
allowsWideFrames: Bool = false,
frameImages: [FrameImage] = [],
selectingFrameImage: FrameImage? = nil,
previewingFrameImage: FrameImage? = nil,
Expand All @@ -69,6 +71,7 @@ public final class CustomRunnerEditor: Composable, Identifiable {
self.id = id
self.runnerName = runnerName
self.isTemplate = isTemplate
self.allowsWideFrames = allowsWideFrames
self.frameImages = frameImages
self.selectingFrameImage = selectingFrameImage
self.previewingFrameImage = previewingFrameImage
Expand Down Expand Up @@ -98,6 +101,9 @@ public final class CustomRunnerEditor: Composable, Identifiable {
case let .renderingModePickerSelected(renderingMode):
isTemplate = renderingMode.isTemplate

case let .allowWideFramesToggleSwitched(allowsWideFrames):
self.allowsWideFrames = allowsWideFrames

case let .frameImageCellTapped(frameImage):
selectingFrameImage = frameImage

Expand Down Expand Up @@ -200,11 +206,15 @@ public final class CustomRunnerEditor: Composable, Identifiable {
}
}

private var maxFrameWidth: Int {
allowsWideFrames ? 300 : 100
}

private func appendFrameImage(from url: URL) throws {
guard let nsImage = NSImage(contentsOf: url),
let cgImage = nsImage.cgImage(forProposedRect: nil, context: nil, hints: nil),
cgImage.height == 36,
(10 ... 100).contains(cgImage.width) else {
(10 ... maxFrameWidth).contains(cgImage.width) else {
throw RCNError.customRunner(.invalidFrameImage)
}
guard frameImages.count < 30 else {
Expand All @@ -217,6 +227,7 @@ public final class CustomRunnerEditor: Composable, Identifiable {
case viewAppeared
case viewDisappeared
case renderingModePickerSelected(RenderingMode)
case allowWideFramesToggleSwitched(Bool)
case frameImageCellTapped(FrameImage)
case collectionBackgroundTapped
case filesDropped([URL])
Expand Down
34 changes: 34 additions & 0 deletions LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,23 @@
}
}
},
"allowWideFrames" : {
"comment" : "A label for a toggle that allows custom runner frames wider than 100px.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Allow wide frames (up to 300px)"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "幅広フレームを許可(最大300px)"
}
}
}
},
"appearance" : {
"comment" : "A heading for the appearance settings.",
"isCommentAutoGenerated" : true,
Expand Down Expand Up @@ -5742,6 +5759,23 @@
}
}
}
},
"wideFramesWarning" : {
"comment" : "A warning shown when wide custom runner frames are allowed.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Frames wider than 100px may be hidden behind the camera notch on MacBooks. RunCat Neo keeps running, but the runner can look as if it has stopped."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "幅が100pxを超えるフレームは、MacBookのカメラノッチに隠れて表示されないことがあります。RunCat Neoは動作し続けますが、ランナーが止まったように見えることがあります。"
}
}
}
}
},
"version" : "1.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ struct CustomRunnerEditorView: View {
} label: {
Text("renderingMode", bundle: .module)
}
Toggle(isOn: Binding<Bool>(
get: { store.allowsWideFrames },
asyncSet: { await store.send(.allowWideFramesToggleSwitched($0)) }
)) {
Text("allowWideFrames", bundle: .module)
if store.allowsWideFrames {
Text("wideFramesWarning", bundle: .module)
}
}
LabeledContent {
FrameImagesCollectionView(store: store)
} label: {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ struct CustomRunnerEditorTests {
#expect(sut.frameImages.isEmpty)
}

@MainActor @Test
func send_filesDropped_forwards_error_when_image_is_wider_than_100px_by_default() async {
let recorder = errorRecorder()
let sut = CustomRunnerEditor(.testDependencies(), id: .init(), action: recorder.action)
await sut.send(.filesDropped([URL.fixture(name: "solid_red_200x36")]))
#expect(recorder.lock.withLock(\.self) == .customRunner(.invalidFrameImage))
#expect(sut.frameImages.isEmpty)
}

@MainActor @Test
func send_filesDropped_appends_wide_image_when_wide_frames_are_allowed() async {
let recorder = errorRecorder()
let sut = CustomRunnerEditor(.testDependencies(), id: .init(), action: recorder.action)
await sut.send(.allowWideFramesToggleSwitched(true))
#expect(sut.allowsWideFrames)
await sut.send(.filesDropped([URL.fixture(name: "solid_red_200x36")]))
#expect(sut.frameImages.count == 1)
#expect(recorder.lock.withLock(\.self) == nil)
}

@MainActor @Test
func send_filesDropped_forwards_error_when_frame_limit_is_reached() async {
let recorder = errorRecorder()
Expand Down