Skip to content

Commit db41fd4

Browse files
committed
Add WrappingHSack view
1 parent 6e99774 commit db41fd4

File tree

3 files changed

+149
-11
lines changed

3 files changed

+149
-11
lines changed

CotEditor.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145
2A2792991D1E57DA00F3FC5D /* FormatPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2792971D1E57DA00F3FC5D /* FormatPaneController.swift */; };
146146
2A2B086028046E3B0028D733 /* WarningInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B085F28046E3B0028D733 /* WarningInspectorView.swift */; };
147147
2A2B086128046E3B0028D733 /* WarningInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B085F28046E3B0028D733 /* WarningInspectorView.swift */; };
148+
2A2EEF182B778BB1001FEDFB /* WrappingHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2EEF172B778BB1001FEDFB /* WrappingHStack.swift */; };
149+
2A2EEF192B778BB1001FEDFB /* WrappingHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2EEF172B778BB1001FEDFB /* WrappingHStack.swift */; };
148150
2A30C7DB2B1380BE002F6381 /* ShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A30C7DA2B1380BE002F6381 /* ShortcutView.swift */; };
149151
2A30C7DC2B1380BE002F6381 /* ShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A30C7DA2B1380BE002F6381 /* ShortcutView.swift */; };
150152
2A33D07E1D1C75B8005977B9 /* SyntaxValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A33D07D1D1C75B8005977B9 /* SyntaxValidationView.swift */; };
@@ -933,6 +935,7 @@
933935
2A2792941D1DBDAC00F3FC5D /* String+Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Constants.swift"; sourceTree = "<group>"; };
934936
2A2792971D1E57DA00F3FC5D /* FormatPaneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatPaneController.swift; sourceTree = "<group>"; };
935937
2A2B085F28046E3B0028D733 /* WarningInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningInspectorView.swift; sourceTree = "<group>"; };
938+
2A2EEF172B778BB1001FEDFB /* WrappingHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappingHStack.swift; sourceTree = "<group>"; };
936939
2A30C7DA2B1380BE002F6381 /* ShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutView.swift; sourceTree = "<group>"; };
937940
2A33D07D1D1C75B8005977B9 /* SyntaxValidationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntaxValidationView.swift; sourceTree = "<group>"; };
938941
2A33D0801D1C7935005977B9 /* SyntaxTermsEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntaxTermsEditViewController.swift; sourceTree = "<group>"; };
@@ -2308,6 +2311,7 @@
23082311
2A5D13121D1EE8FF00D38E6A /* HUDView.swift */,
23092312
2AA175F92AC5634500F6462C /* PopoverHolderView.swift */,
23102313
2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */,
2314+
2A2EEF172B778BB1001FEDFB /* WrappingHStack.swift */,
23112315
);
23122316
name = Views;
23132317
sourceTree = "<group>";
@@ -3068,6 +3072,7 @@
30683072
2A78BFB11D1B168E00A583D2 /* WebDocumentWindowController.swift in Sources */,
30693073
2A17A3141D2D16F1001DD717 /* WindowContentViewController.swift in Sources */,
30703074
2A78BFA51D1B02ED00A583D2 /* WindowPaneController.swift in Sources */,
3075+
2A2EEF182B778BB1001FEDFB /* WrappingHStack.swift in Sources */,
30713076
2AEE84B31E8158D700BA7982 /* WriteToConsoleCommand.swift in Sources */,
30723077
);
30733078
runOnlyForDeploymentPostprocessing = 0;
@@ -3430,6 +3435,7 @@
34303435
2A78BFB01D1B168E00A583D2 /* WebDocumentWindowController.swift in Sources */,
34313436
2A17A3131D2D16F1001DD717 /* WindowContentViewController.swift in Sources */,
34323437
2A78BFA41D1B02ED00A583D2 /* WindowPaneController.swift in Sources */,
3438+
2A2EEF192B778BB1001FEDFB /* WrappingHStack.swift in Sources */,
34333439
2AEE84B21E8158D700BA7982 /* WriteToConsoleCommand.swift in Sources */,
34343440
);
34353441
runOnlyForDeploymentPostprocessing = 0;

CotEditor/Sources/DocumentInspectorView.swift

+12-11
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,15 @@ private struct CharacterPaneView: View {
220220
Form {
221221
if let character {
222222
LabeledContent(String(localized: "Code Points", table: "Inspector")) {
223-
ForEach(Array(character.unicodeScalars.enumerated()), id: \.offset) { (_, scalar) in
224-
Text(scalar.codePoint)
225-
.monospacedDigit()
226-
.textSelection(.enabled)
227-
.padding(.horizontal, 2)
228-
.overlay(RoundedRectangle(cornerRadius: 3.5).inset(by: 0.5)
229-
.stroke(Color.tertiaryLabel))
230-
.fixedSize()
223+
WrappingHStack {
224+
ForEach(Array(character.unicodeScalars.enumerated()), id: \.offset) { (_, scalar) in
225+
Text(scalar.codePoint)
226+
.monospacedDigit()
227+
.textSelection(.enabled)
228+
.padding(.horizontal, 2)
229+
.overlay(RoundedRectangle(cornerRadius: 3.5).inset(by: 0.5)
230+
.stroke(Color.tertiaryLabel))
231+
}
231232
}
232233
}
233234
} else {
@@ -340,16 +341,16 @@ private extension DocumentInspectorView.Model {
340341
// MARK: - Preview
341342

342343
@available(macOS 14, *)
343-
#Preview(traits: .fixedLayout(width: 240, height: 500)) {
344+
#Preview(traits: .fixedLayout(width: 240, height: 540)) {
344345
let model = DocumentInspectorView.Model()
345346
model.attributes = .init(
346347
creationDate: .now,
347348
modificationDate: .now,
348349
size: 1024,
349350
permissions: FilePermissions(mask: 0o420),
350-
owner: "1024jp"
351+
owner: "clarus"
351352
)
352-
model.fileURL = URL(filePath: "/User/Claus/Desktop/My Script.py")
353+
model.fileURL = URL(filePath: "/Users/clarus/Desktop/My Script.py")
353354
model.encoding = .init(encoding: .utf8, withUTF8BOM: true)
354355
model.countResult = .init(
355356
lines: .init(entire: 10, selected: 4),
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//
2+
// WrappingHStack.swift
3+
//
4+
// CotEditor
5+
// https://coteditor.com
6+
//
7+
// Created by 1024jp on 2024-02-10.
8+
//
9+
// ---------------------------------------------------------------------------
10+
//
11+
// © 2024 1024jp
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
26+
import SwiftUI
27+
28+
struct WrappingHStack<Content: View>: View {
29+
30+
var horizontalSpacing: Double = 4
31+
var verticalSpacing: Double = 4
32+
var content: () -> Content
33+
34+
35+
var body: some View {
36+
37+
WrappingHStackLayout(horizontalSpacing: self.horizontalSpacing, verticalSpacing: self.verticalSpacing) {
38+
self.content()
39+
}
40+
}
41+
}
42+
43+
44+
private struct WrappingHStackLayout: Layout {
45+
46+
let horizontalSpacing: Double
47+
let verticalSpacing: Double
48+
49+
50+
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
51+
52+
let width = proposal.replacingUnspecifiedDimensions().width
53+
let rowCount = Double(self.countRows(for: subviews, in: width))
54+
let minHeight = subviews
55+
.map { $0.sizeThatFits(proposal).height }
56+
.reduce(0) { max($0, $1).rounded(.up) }
57+
let height = rowCount * minHeight + max(rowCount - 1, 0) * self.verticalSpacing
58+
59+
return CGSize(width: width, height: height)
60+
}
61+
62+
63+
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
64+
65+
let minHeight = subviews
66+
.map { $0.sizeThatFits(proposal).height }
67+
.reduce(0) { max($0, $1).rounded(.up) }
68+
69+
var point = bounds.origin
70+
for subview in subviews {
71+
let width = subview.sizeThatFits(proposal).width
72+
73+
if point.x + width > bounds.maxX {
74+
point.x = bounds.minX
75+
point.y += minHeight + self.verticalSpacing
76+
}
77+
78+
subview.place(at: point, anchor: .topLeading, proposal: proposal)
79+
point.x += width + self.horizontalSpacing
80+
}
81+
}
82+
83+
84+
/// Calculates the number of rows when the subviews are laid out within the given `containerWidth`.
85+
///
86+
/// - Parameters:
87+
/// - subviews: The subviews to lay out.
88+
/// - containerWidth: The width of the view to fit in.
89+
/// - Returns: The number of rows.
90+
private func countRows(for subviews: Subviews, in containerWidth: Double) -> Int {
91+
92+
var count = 0
93+
94+
var x: Double = 0
95+
for subview in subviews {
96+
let width = subview.sizeThatFits(.unspecified).width
97+
98+
if x + width > containerWidth {
99+
x = 0
100+
count += 1
101+
}
102+
x += width + self.horizontalSpacing
103+
}
104+
105+
if x > 0 {
106+
count += 1
107+
}
108+
109+
return count
110+
}
111+
}
112+
113+
114+
115+
// MARK: - Preview
116+
117+
#Preview {
118+
let words = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do"]
119+
120+
return WrappingHStack {
121+
ForEach(words, id: \.self) {
122+
Text($0)
123+
.monospacedDigit()
124+
.padding(.horizontal, 2)
125+
.overlay(RoundedRectangle(cornerRadius: 3).fill(Color.accentColor.opacity(0.2)))
126+
}
127+
}
128+
.border(.separator)
129+
.padding()
130+
.frame(width: 180)
131+
}

0 commit comments

Comments
 (0)