mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 13:27:16 +00:00
fix(editor): stabilize themes and line numbers for v0.4.19
This commit is contained in:
parent
62e4a9d65e
commit
0e8c91e8de
8 changed files with 117 additions and 24 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -4,6 +4,18 @@ All notable changes to **Neon Vision Editor** are documented in this file.
|
|||
|
||||
The format follows *Keep a Changelog*. Versions use semantic versioning with prerelease tags.
|
||||
|
||||
## [v0.4.19] - 2026-02-16
|
||||
|
||||
### Added
|
||||
- Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.
|
||||
|
||||
### Improved
|
||||
- Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.
|
||||
|
||||
### Fixed
|
||||
- Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.
|
||||
- Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds.
|
||||
|
||||
## [v0.4.18] - 2026-02-15
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 234;
|
||||
CURRENT_PROJECT_VERSION = 235;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
@ -438,7 +438,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 234;
|
||||
CURRENT_PROJECT_VERSION = 235;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
|
|||
|
|
@ -398,10 +398,8 @@ extension ContentView {
|
|||
if enabled {
|
||||
// Keep toolbar material blended with the titlebar instead of rendering as a separate solid strip.
|
||||
window.toolbarStyle = .unified
|
||||
window.toolbar?.showsBaselineSeparator = false
|
||||
window.styleMask.insert(.fullSizeContentView)
|
||||
} else {
|
||||
window.toolbar?.showsBaselineSeparator = true
|
||||
window.styleMask.remove(.fullSizeContentView)
|
||||
}
|
||||
if #available(macOS 13.0, *) {
|
||||
|
|
|
|||
|
|
@ -2092,6 +2092,7 @@ final class LineNumberedTextViewContainer: UIView {
|
|||
lineNumberView.isEditable = false
|
||||
lineNumberView.isSelectable = false
|
||||
lineNumberView.isScrollEnabled = true
|
||||
lineNumberView.bounces = false
|
||||
lineNumberView.isUserInteractionEnabled = false
|
||||
lineNumberView.backgroundColor = UIColor.secondarySystemBackground.withAlphaComponent(0.65)
|
||||
lineNumberView.textColor = .secondaryLabel
|
||||
|
|
@ -2132,6 +2133,7 @@ final class LineNumberedTextViewContainer: UIView {
|
|||
let numbers = (1...lineCount).map(String.init).joined(separator: "\n")
|
||||
lineNumberView.font = UIFont.monospacedDigitSystemFont(ofSize: max(11, fontSize - 1), weight: .regular)
|
||||
lineNumberView.text = numbers
|
||||
lineNumberView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2532,7 +2534,11 @@ struct CustomTextEditor: UIViewRepresentable {
|
|||
|
||||
func syncLineNumberScroll() {
|
||||
guard let textView, let lineView = container?.lineNumberView else { return }
|
||||
lineView.contentOffset = CGPoint(x: 0, y: textView.contentOffset.y)
|
||||
let targetY = textView.contentOffset.y + textView.adjustedContentInset.top - lineView.adjustedContentInset.top
|
||||
let minY = -lineView.adjustedContentInset.top
|
||||
let maxY = max(minY, lineView.contentSize.height - lineView.bounds.height + lineView.adjustedContentInset.bottom)
|
||||
let clampedY = min(max(targetY, minY), maxY)
|
||||
lineView.setContentOffset(CGPoint(x: 0, y: clampedY), animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ final class LineNumberRulerView: NSRulerView {
|
|||
|
||||
let visibleRectInContainer = visibleRect.offsetBy(dx: -tcOrigin.x, dy: -tcOrigin.y)
|
||||
let visibleGlyphRange = lm.glyphRange(forBoundingRect: visibleRectInContainer, in: textContainer)
|
||||
guard visibleGlyphRange.location != NSNotFound, visibleGlyphRange.length > 0 else { return }
|
||||
guard visibleGlyphRange.location != NSNotFound else { return }
|
||||
|
||||
var drawnLineStarts = Set<Int>()
|
||||
lm.enumerateLineFragments(forGlyphRange: visibleGlyphRange) { [self] _, usedRect, _, glyphRange, _ in
|
||||
|
|
@ -111,6 +111,21 @@ final class LineNumberRulerView: NSRulerView {
|
|||
let drawPoint = NSPoint(x: self.bounds.maxX - size.width - self.inset, y: drawY)
|
||||
numberString.draw(at: drawPoint, withAttributes: attributes)
|
||||
}
|
||||
|
||||
// Keep the last line number visible near end-of-document/bottom-scroll edge cases
|
||||
// where AppKit can report an empty visible glyph range.
|
||||
if drawnLineStarts.isEmpty, textLength > 0 {
|
||||
let lastLineNumber = fullString.components(separatedBy: .newlines).count
|
||||
let numberString = NSString(string: "\(lastLineNumber)")
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: font,
|
||||
.foregroundColor: textColor
|
||||
]
|
||||
let size = numberString.size(withAttributes: attributes)
|
||||
let drawY = max(bounds.minY + 2, bounds.maxY - size.height - 6)
|
||||
let drawPoint = NSPoint(x: bounds.maxX - size.width - inset, y: drawY)
|
||||
numberString.draw(at: drawPoint, withAttributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private func installObservers(textView: NSTextView) {
|
||||
|
|
|
|||
|
|
@ -220,12 +220,12 @@ struct WelcomeTourView: View {
|
|||
private let pages: [TourPage] = [
|
||||
TourPage(
|
||||
title: "What’s New in This Release",
|
||||
subtitle: "Major changes since v0.4.17:",
|
||||
subtitle: "Major changes since v0.4.18:",
|
||||
bullets: [
|
||||
"Added iOS/macOS regression coverage in the editor refresh path so syntax highlighting remains stable across toolbar/menu and focus transitions.",
|
||||
"Improved editor rendering consistency by preventing view-update color assignments from overriding attributed syntax token colors.",
|
||||
"Fixed iOS issue where opening the toolbar `...` menu could temporarily drop syntax highlighting.",
|
||||
"Fixed macOS issue where moving focus away from the editor/window could temporarily drop syntax highlighting."
|
||||
"Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.",
|
||||
"Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.",
|
||||
"Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.",
|
||||
"Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds."
|
||||
],
|
||||
iconName: "sparkles.rectangle.stack",
|
||||
colors: [Color(red: 0.40, green: 0.28, blue: 0.90), Color(red: 0.96, green: 0.46, blue: 0.55)],
|
||||
|
|
|
|||
|
|
@ -44,6 +44,69 @@ struct ThemePaletteColors {
|
|||
let builtin: Color
|
||||
}
|
||||
|
||||
private struct RGBColorComponents {
|
||||
let red: Double
|
||||
let green: Double
|
||||
let blue: Double
|
||||
}
|
||||
|
||||
private func colorComponents(_ color: Color) -> RGBColorComponents? {
|
||||
#if os(macOS)
|
||||
let platform = PlatformColor(color)
|
||||
guard let srgb = platform.usingColorSpace(.sRGB) else { return nil }
|
||||
return RGBColorComponents(
|
||||
red: Double(srgb.redComponent),
|
||||
green: Double(srgb.greenComponent),
|
||||
blue: Double(srgb.blueComponent)
|
||||
)
|
||||
#else
|
||||
let platform = PlatformColor(color)
|
||||
var red: CGFloat = 0
|
||||
var green: CGFloat = 0
|
||||
var blue: CGFloat = 0
|
||||
var alpha: CGFloat = 0
|
||||
guard platform.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return nil }
|
||||
return RGBColorComponents(red: Double(red), green: Double(green), blue: Double(blue))
|
||||
#endif
|
||||
}
|
||||
|
||||
private func relativeLuminance(_ components: RGBColorComponents) -> Double {
|
||||
(0.2126 * components.red) + (0.7152 * components.green) + (0.0722 * components.blue)
|
||||
}
|
||||
|
||||
private func blend(_ source: Color, with target: Color, amount: Double) -> Color {
|
||||
guard let sourceComponents = colorComponents(source), let targetComponents = colorComponents(target) else {
|
||||
return source
|
||||
}
|
||||
let clamped = min(1.0, max(0.0, amount))
|
||||
return Color(
|
||||
red: sourceComponents.red + ((targetComponents.red - sourceComponents.red) * clamped),
|
||||
green: sourceComponents.green + ((targetComponents.green - sourceComponents.green) * clamped),
|
||||
blue: sourceComponents.blue + ((targetComponents.blue - sourceComponents.blue) * clamped)
|
||||
)
|
||||
}
|
||||
|
||||
private func modeAdjustedEditorBackground(_ background: Color, colorScheme: ColorScheme) -> Color {
|
||||
guard let components = colorComponents(background) else { return background }
|
||||
let luminance = relativeLuminance(components)
|
||||
|
||||
if colorScheme == .light {
|
||||
// Keep all themes readable in light/system-light by lifting dark palettes.
|
||||
if luminance >= 0.78 { return background }
|
||||
let targetLuminance = 0.96
|
||||
let normalized = (targetLuminance - luminance) / max(0.0001, 1.0 - luminance)
|
||||
let mixAmount = min(0.95, max(0.70, normalized))
|
||||
return blend(background, with: .white, amount: mixAmount)
|
||||
}
|
||||
|
||||
// Keep all themes readable in dark/system-dark by lowering bright palettes.
|
||||
if luminance <= 0.28 { return background }
|
||||
let targetLuminance = 0.14
|
||||
let normalized = (luminance - targetLuminance) / max(0.0001, luminance)
|
||||
let mixAmount = min(0.90, max(0.55, normalized))
|
||||
return blend(background, with: .black, amount: mixAmount)
|
||||
}
|
||||
|
||||
///MARK: Theme Name Canonicalization
|
||||
|
||||
// Canonical theme names shown in settings and used for palette lookup.
|
||||
|
|
@ -322,7 +385,7 @@ func currentEditorTheme(colorScheme: ColorScheme) -> EditorTheme {
|
|||
|
||||
return EditorTheme(
|
||||
text: baseTextColor,
|
||||
background: palette.background,
|
||||
background: modeAdjustedEditorBackground(palette.background, colorScheme: colorScheme),
|
||||
cursor: palette.cursor,
|
||||
selection: palette.selection,
|
||||
syntax: syntax
|
||||
|
|
|
|||
23
README.md
23
README.md
|
|
@ -17,7 +17,7 @@
|
|||
</p>
|
||||
|
||||
> Status: **active release**
|
||||
> Latest release: **v0.4.18**
|
||||
> Latest release: **v0.4.19**
|
||||
> Platform target: **macOS 26 (Tahoe)** compatible with **macOS Sequoia**
|
||||
> Apple Silicon: tested / Intel: not tested
|
||||
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
Prebuilt binaries are available on [GitHub Releases](https://github.com/h3pdesign/Neon-Vision-Editor/releases).
|
||||
|
||||
- Latest release: **v0.4.18**
|
||||
- Latest release: **v0.4.19**
|
||||
- Apple AppStore [On the AppStore](https://apps.apple.com/de/app/neon-vision-editor/id6758950965)
|
||||
- TestFlight beta: [Join here](https://testflight.apple.com/join/YWB2fGAP)
|
||||
- Architecture: Apple Silicon (Intel not tested)
|
||||
|
|
@ -123,6 +123,13 @@ If macOS blocks first launch:
|
|||
|
||||
## Changelog
|
||||
|
||||
### v0.4.19 (summary)
|
||||
|
||||
- Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.
|
||||
- Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.
|
||||
- Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.
|
||||
- Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds.
|
||||
|
||||
### v0.4.18 (summary)
|
||||
|
||||
- Added iOS/macOS regression coverage in the editor refresh path so syntax highlighting remains stable across toolbar/menu and focus transitions.
|
||||
|
|
@ -136,14 +143,6 @@ If macOS blocks first launch:
|
|||
- Improved syntax-highlighting stability during appearance/translucency transitions by forcing an immediate refresh instead of waiting for unrelated edits.
|
||||
- Fixed a macOS editor bug where toggling translucent window mode could temporarily hide syntax highlighting until another action (for example changing font size) forced a rehighlight.
|
||||
|
||||
### v0.4.16 (summary)
|
||||
|
||||
- Added a release-doc synchronization gate to `release_all.sh` via `prepare_release_docs.py --check` so releases fail fast when docs are stale.
|
||||
- Added a delegate-based updater download service that reports live progress into the update dialog.
|
||||
- Improved updater install flow to stay user-driven/manual after verification, with Finder handoff instead of in-place app replacement.
|
||||
- Improved editor appearance switching so base text colors are enforced immediately on light/dark mode changes across macOS and iOS.
|
||||
- Fixed light-mode editor base text color to consistently use dark text across themes.
|
||||
|
||||
Full release history: [`CHANGELOG.md`](CHANGELOG.md)
|
||||
|
||||
## Known Limitations
|
||||
|
|
@ -163,12 +162,12 @@ Full release history: [`CHANGELOG.md`](CHANGELOG.md)
|
|||
|
||||
## Release Integrity
|
||||
|
||||
- Tag: `v0.4.18`
|
||||
- Tag: `v0.4.19`
|
||||
- Tagged commit: `TBD`
|
||||
- Verify local tag target:
|
||||
|
||||
```bash
|
||||
git rev-parse --verify v0.4.18
|
||||
git rev-parse --verify v0.4.19
|
||||
```
|
||||
|
||||
- Verify downloaded artifact checksum locally:
|
||||
|
|
|
|||
Loading…
Reference in a new issue