Skip to content

Commit

Permalink
Minor tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed May 4, 2024
1 parent 3c96cbd commit 9dabd35
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 52 deletions.
8 changes: 4 additions & 4 deletions Pasteboard Viewer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.1;
MACOSX_DEPLOYMENT_TARGET = 14.1;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
MACOSX_DEPLOYMENT_TARGET = 14.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -338,8 +338,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.1;
MACOSX_DEPLOYMENT_TARGET = 14.1;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
MACOSX_DEPLOYMENT_TARGET = 14.4;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand Down
31 changes: 26 additions & 5 deletions Pasteboard Viewer/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ struct AppMain: App {
.keyboardShortcut("t", modifiers: [.control, .command])
}
CommandGroup(after: .toolbar) {
Toggle("View as Text", isOn: .init(get: { viewAsText }, set: { _ in viewAsText = true }))
.keyboardShortcut("1", modifiers: .command)

Toggle("View as Hex", isOn: .init(get: { !viewAsText }, set: { _ in viewAsText = false }))
.keyboardShortcut("2", modifiers: .command)
formatPicker
}
#endif
CommandGroup(replacing: .help) {
Expand Down Expand Up @@ -85,4 +81,29 @@ struct AppMain: App {

try? Tips.configure()
}

private var formatPicker: some View {
Section {
Toggle(
"View as Text",
isOn: .init(
get: { viewAsText },
set: { _ in
viewAsText = true
}
)
)
.keyboardShortcut("1", modifiers: .command)
Toggle(
"View as Hex",
isOn: .init(
get: { !viewAsText },
set: { _ in
viewAsText = false
}
)
)
.keyboardShortcut("2", modifiers: .command)
}
}
}
100 changes: 57 additions & 43 deletions Pasteboard Viewer/ContentsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,45 +33,49 @@ struct ContentsScreen: View {
}

#if os(macOS)
private var hexBody: some View {
HexView(data: type.data(), selection: $selectedByteRanges)
.extraInfo(hexBodyExtraInfo)
}

private var hexBodyExtraInfo: String {
// this uses a .number formatter instead of .byteCount,
// because a byte count format doesn't have a way to exclude the grouping character
// This uses a `.number` formatter instead of `.byteCount`,
// because a byte count format doesn't have a way to exclude the grouping character.
let count = (type.data()?.count ?? 0)
let length = count.formatted(.number.grouping(.never)) + " " + (count == 1 ? "byte" : "bytes")
if selectedByteRanges.isEmpty {
// no selection

guard !selectedByteRanges.isEmpty else {
// No selection
return length
}

if let range = selectedByteRanges.first, selectedByteRanges.count == 1 {
// single or empty selection
if
let range = selectedByteRanges.first,
selectedByteRanges.count == 1
{
// Single or empty selection.
let offset = range.lowerBound.formatted(.number.grouping(.never))
if range.lowerBound == range.upperBound {
// no selection
// No selection.
return length
}

if range.upperBound - range.lowerBound == 1 {
// single byte
// Single byte.
return "Byte \(offset) selected out of \(length)"
}

// multiple selected bytes
// Multiple selected bytes.
let count = (range.upperBound - range.lowerBound).formatted(.number.grouping(.never))
return "\(count) bytes selected at offset \(offset) out of \(length)"
}

// multiple selection
// Multiple selection.
let totalCount = selectedByteRanges.reduce(into: UInt64.zero, { $0 += ($1.upperBound - $1.lowerBound) })
.formatted(.number.grouping(.never))

return "\(totalCount) selected at multiple offsets out of \(length)"
}

private var hexBody: some View {
HexView(data: type.data(), selection: $selectedByteRanges)
.extraInfo(hexBodyExtraInfo)
}
#endif

private var textBody: some View {
Expand All @@ -93,9 +97,9 @@ struct ContentsScreen: View {
.extraInfo(customExtraInfo ?? textSubtitle(string))
}

#if DEBUG
#if DEBUG
print("Type", type.xType.rawValue)
#endif
#endif

return VStack {
// Ensure plain text is always rendered as plain text.
Expand Down Expand Up @@ -183,9 +187,9 @@ struct ContentsScreen: View {
Section {
if type.xType == .URL {
Button("Open in Browser", systemImage: "safari") {
#if os(macOS)
#if os(macOS)
type.string()?.toURL?.open()
#else
#else
// On iOS, `.URL` is usually wrapped in a plist, but in case it's a plain string, we try it first.
if let url = type.string()?.toURL {
url.open()
Expand All @@ -201,7 +205,7 @@ struct ContentsScreen: View {
}

url.openUrl()
#endif
#endif
}
}
}
Expand Down Expand Up @@ -236,9 +240,9 @@ struct ContentsScreen: View {

extension View {
fileprivate func extraInfo(_ text: String) -> some View {
#if os(macOS)
#if os(macOS)
navigationSubtitleIfMacOS(text)
#else
#else
// Note: Using `ToolbarItem(placement: .bottomBar)` caused it to loose the "back" button when starting to swiping to go back, but then deciding not to.
fillFrame()
.safeAreaInset(edge: .bottom) {
Expand All @@ -247,7 +251,7 @@ extension View {
.font(.system(.subheadline))
.padding(4)
}
#endif
#endif
}
}

Expand Down Expand Up @@ -341,9 +345,9 @@ extension Pasteboard.Type_ {

#if os(macOS)
struct HexView: NSViewRepresentable {
class Coordinator: HFRepresenter {
let selectionBinding: Binding<Set<Range<UInt64>>>
var isUpdating = false
final class Coordinator: HFRepresenter {
private let selectionBinding: Binding<Set<Range<UInt64>>>
fileprivate var isUpdating = false

init(selectionBinding: Binding<Set<Range<UInt64>>>) {
self.selectionBinding = selectionBinding
Expand All @@ -356,51 +360,61 @@ struct HexView: NSViewRepresentable {
}

override func createView() -> NSView {
// required when subclassing HFRepresenter
// Required when subclassing HFRepresenter.
NSView()
}

override func controllerDidChange(_ bits: HFControllerPropertyBits) {
guard isUpdating == false else {
guard
!isUpdating,
bits.contains(.contentValue) || bits.contains(.selectedRanges)
else {
return
}
guard bits.contains(.contentValue) || bits.contains(.selectedRanges) else {

// Update the selection binding.
guard let controller = controller() else {
selectionBinding.wrappedValue = Set()
return
}
// update the selection binding
if let controller = self.controller() {
let ranges = controller.selectedContentsRanges.compactMap { rangeWrapper -> Range<UInt64>? in
guard let hf = (rangeWrapper as? HFRangeWrapper)?.hfRange() else {
return nil
}
return hf.location ..< (hf.location + hf.length)

let ranges = controller.selectedContentsRanges.compactMap { rangeWrapper -> Range<UInt64>? in
guard let hf = (rangeWrapper as? HFRangeWrapper)?.hfRange() else {
return nil
}
selectionBinding.wrappedValue = Set(ranges)
} else {
selectionBinding.wrappedValue = Set()

return hf.location..<(hf.location + hf.length)
}

selectionBinding.wrappedValue = Set(ranges)
}
}

var data: Data?
@Binding var selection: Set<Range<UInt64>>

func makeCoordinator() -> Coordinator {
Coordinator(selectionBinding: $selection)
.init(selectionBinding: $selection)
}

func makeNSView(context: Context) -> HFTextView {
context.coordinator.isUpdating = true
defer { context.coordinator.isUpdating = false }
let textView = HFTextView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
defer {
context.coordinator.isUpdating = false
}

let textView = HFTextView(frame: .init(x: 0, y: 0, width: 100, height: 100))
textView.controller.addRepresenter(context.coordinator)
textView.controller.editMode = .readOnlyMode
return textView
}

func updateNSView(_ nsView: HFTextView, context: Context) {
context.coordinator.isUpdating = true
defer { context.coordinator.isUpdating = false }
defer {
context.coordinator.isUpdating = false
}

nsView.data = data
}
}
Expand Down

0 comments on commit 9dabd35

Please sign in to comment.