mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 13:27:16 +00:00
Add fit/readable/wrap layout modes for code snapshot preview
This commit is contained in:
parent
cac752dc22
commit
f1ca1576c2
2 changed files with 78 additions and 14 deletions
|
|
@ -361,7 +361,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 522;
|
||||
CURRENT_PROJECT_VERSION = 523;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
@ -444,7 +444,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 522;
|
||||
CURRENT_PROJECT_VERSION = 523;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
|
|||
|
|
@ -92,10 +92,27 @@ enum CodeSnapshotFrameStyle: String, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
enum CodeSnapshotLayoutMode: String, CaseIterable, Identifiable {
|
||||
case fit
|
||||
case readable
|
||||
case wrap
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .fit: return "Fit"
|
||||
case .readable: return "Readable"
|
||||
case .wrap: return "Wrap"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CodeSnapshotStyle: Equatable {
|
||||
var appearance: CodeSnapshotAppearance = .dark
|
||||
var backgroundPreset: CodeSnapshotBackgroundPreset = .sunrise
|
||||
var frameStyle: CodeSnapshotFrameStyle = .macWindow
|
||||
var layoutMode: CodeSnapshotLayoutMode = .fit
|
||||
var showLineNumbers: Bool = true
|
||||
var padding: CGFloat = 26
|
||||
}
|
||||
|
|
@ -186,7 +203,7 @@ private enum CodeSnapshotRenderer {
|
|||
style: CodeSnapshotStyle
|
||||
) -> Data? {
|
||||
let renderWidth = snapshotRenderWidth(payload: payload, style: style)
|
||||
let card = CodeSnapshotCardView(payload: payload, style: style)
|
||||
let card = CodeSnapshotCardView(payload: payload, style: style, cardWidth: renderWidth)
|
||||
.frame(width: renderWidth)
|
||||
let renderer = ImageRenderer(content: card)
|
||||
renderer.scale = 2
|
||||
|
|
@ -217,6 +234,9 @@ private enum CodeSnapshotRenderer {
|
|||
}
|
||||
|
||||
private static func snapshotRenderWidth(payload: CodeSnapshotPayload, style: CodeSnapshotStyle) -> CGFloat {
|
||||
if style.layoutMode != .readable {
|
||||
return 940
|
||||
}
|
||||
let longestLine = payload.text
|
||||
.components(separatedBy: "\n")
|
||||
.map(\.count)
|
||||
|
|
@ -236,6 +256,7 @@ private typealias PlatformFont = UIFont
|
|||
private struct CodeSnapshotCardView: View {
|
||||
let payload: CodeSnapshotPayload
|
||||
let style: CodeSnapshotStyle
|
||||
let cardWidth: CGFloat
|
||||
|
||||
private var lines: [AttributedString] {
|
||||
CodeSnapshotRenderer.attributedLines(
|
||||
|
|
@ -279,10 +300,15 @@ private struct CodeSnapshotCardView: View {
|
|||
.max() ?? 0
|
||||
}
|
||||
|
||||
private var preferredCardWidth: CGFloat {
|
||||
let baseInsets = (style.padding * 2) + (style.showLineNumbers ? 70 : 26) + 84
|
||||
let estimated = CGFloat(max(1, longestLineLength)) * 9.0 + baseInsets
|
||||
return min(max(940, estimated), 2200)
|
||||
private var codeFontSize: CGFloat {
|
||||
guard style.layoutMode == .fit else { return 15 }
|
||||
let maxLineLength = max(1, longestLineLength)
|
||||
let lineNumberWidth: CGFloat = style.showLineNumbers ? 48 : 0
|
||||
let availableCodeWidth = max(220, cardWidth - 84 - (style.padding * 2) - lineNumberWidth)
|
||||
let estimatedCharacterWidthAtSize15: CGFloat = 9.0
|
||||
let scale = availableCodeWidth / (CGFloat(maxLineLength) * estimatedCharacterWidthAtSize15)
|
||||
let fitted = 15 * min(1, max(0.25, scale))
|
||||
return max(5.0, min(15.0, fitted))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -316,7 +342,10 @@ private struct CodeSnapshotCardView: View {
|
|||
}
|
||||
Text(line)
|
||||
.font(.system(size: 15, weight: .regular, design: .monospaced))
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
.font(.system(size: codeFontSize, weight: .regular, design: .monospaced))
|
||||
.lineLimit(style.layoutMode == .wrap ? nil : 1)
|
||||
.minimumScaleFactor(style.layoutMode == .fit ? 0.2 : 1.0)
|
||||
.fixedSize(horizontal: style.layoutMode == .readable, vertical: style.layoutMode == .wrap)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
|
@ -352,18 +381,32 @@ struct CodeSnapshotComposerView: View {
|
|||
NavigationStack {
|
||||
GeometryReader { proxy in
|
||||
let availableWidth = max(320, proxy.size.width - 40)
|
||||
let estimatedControlsHeight: CGFloat = usesCompactScrollingLayout ? 180 : 132
|
||||
let estimatedControlsHeight: CGFloat = usesCompactScrollingLayout ? 210 : 152
|
||||
let availablePreviewHeight = max(220, proxy.size.height - estimatedControlsHeight - 44)
|
||||
let fittedPreviewWidth = min(980, availableWidth, availablePreviewHeight * 1.25)
|
||||
let previewCardWidth = max(fittedPreviewWidth, min(2200, estimatedCardWidth))
|
||||
|
||||
VStack(spacing: 16) {
|
||||
snapshotControls
|
||||
ScrollView([.vertical, .horizontal]) {
|
||||
CodeSnapshotCardView(payload: payload, style: style)
|
||||
.frame(width: usesCompactScrollingLayout ? min(980, availableWidth) : previewCardWidth)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
if style.layoutMode == .fit {
|
||||
CodeSnapshotCardView(payload: payload, style: style, cardWidth: fittedPreviewWidth)
|
||||
.frame(width: fittedPreviewWidth)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.padding(.bottom, 92)
|
||||
} else if style.layoutMode == .wrap {
|
||||
ScrollView(.vertical) {
|
||||
CodeSnapshotCardView(payload: payload, style: style, cardWidth: fittedPreviewWidth)
|
||||
.frame(width: fittedPreviewWidth)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
.padding(.bottom, 92)
|
||||
}
|
||||
} else {
|
||||
ScrollView([.vertical, .horizontal]) {
|
||||
CodeSnapshotCardView(payload: payload, style: style, cardWidth: previewCardWidth)
|
||||
.frame(width: usesCompactScrollingLayout ? min(980, availableWidth) : previewCardWidth)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
.padding(.bottom, 92)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
|
|
@ -395,7 +438,7 @@ struct CodeSnapshotComposerView: View {
|
|||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 1320, minHeight: 920)
|
||||
.frame(minWidth: 1480, minHeight: 980)
|
||||
#else
|
||||
.presentationDetents(usesCompactScrollingLayout ? [.large] : [.fraction(0.96), .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
|
|
@ -460,6 +503,17 @@ struct CodeSnapshotComposerView: View {
|
|||
.labelsHidden()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Picker("Layout", selection: $style.layoutMode) {
|
||||
ForEach(CodeSnapshotLayoutMode.allCases) { mode in
|
||||
Text(mode.title).tag(mode)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.labelsHidden()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
HStack(spacing: 10) {
|
||||
Toggle("Line Numbers", isOn: $style.showLineNumbers)
|
||||
.toggleStyle(.switch)
|
||||
.lineLimit(1)
|
||||
|
|
@ -483,6 +537,11 @@ struct CodeSnapshotComposerView: View {
|
|||
Text(frame.title).tag(frame)
|
||||
}
|
||||
}
|
||||
Picker("Layout", selection: $style.layoutMode) {
|
||||
ForEach(CodeSnapshotLayoutMode.allCases) { mode in
|
||||
Text(mode.title).tag(mode)
|
||||
}
|
||||
}
|
||||
Toggle("Line Numbers", isOn: $style.showLineNumbers)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
|
@ -504,6 +563,11 @@ struct CodeSnapshotComposerView: View {
|
|||
Text(frame.title).tag(frame)
|
||||
}
|
||||
}
|
||||
Picker("Layout", selection: $style.layoutMode) {
|
||||
ForEach(CodeSnapshotLayoutMode.allCases) { mode in
|
||||
Text(mode.title).tag(mode)
|
||||
}
|
||||
}
|
||||
Toggle("Line Numbers", isOn: $style.showLineNumbers)
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in a new issue