Skip to content

Commit

Permalink
Match theme selection color to the theme dark/light mode
Browse files Browse the repository at this point in the history
  • Loading branch information
1024jp committed May 11, 2024
1 parent 27b3547 commit b8aae54
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Improvements

- Display the current editor mode in the document inspector.
- Use more appropriate system color for the selection background when dark mode or not differs between the editor and theme.
- Improve VoiceOver support.
- Deprecate exact recalculation of insertion points when documents are updated by external processes to improve application stability.
- Improve stability of application launch behavior.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ <h2>Improvements</h2>

<ul>
<li>Display the current editor mode in the document inspector.</li>
<li>Use more appropriate system color for the selection background when dark mode or not differs between the editor and theme.</li>
<li>Improve VoiceOver support.</li>
<li>Deprecate exact recalculation of insertion points when documents are updated by external processes to improve application stability.</li>
<li>Improve stability of application launch behavior.</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ <h2>改良</h2>

<ul>
<li>書類インスペクタに現在のモードを表示</li>
<li>エディタとテーマのダークモードかどうかが異なるときにもより適切なシステムカラーを選択範囲に用いるように改良</li>
<li>VoiceOverサポートを強化</li>
<li>アプリケーションの安定性向上のために、外部プロセスから書類が更新されたときに新しいカーソル位置を推算する機能おを廃止</li>
<li>アプリケーション起動時の安定性を向上</li>
Expand Down
2 changes: 1 addition & 1 deletion CotEditor/Sources/EditorTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
indicator.color = self.insertionPointColor
}
}
self.selectedTextAttributes[.backgroundColor] = theme.effectiveSelectionColor
self.selectedTextAttributes[.backgroundColor] = theme.effectiveSelectionColor(for: self.effectiveAppearance)
(self.layoutManager as? LayoutManager)?.invisiblesColor = theme.invisibles.color

(self.window as? DocumentWindow)?.contentBackgroundColor = theme.background.color
Expand Down
9 changes: 4 additions & 5 deletions CotEditor/Sources/LayoutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,11 @@ class LayoutManager: NSLayoutManager, InvisibleDrawing, ValidationIgnorable, Lin
// -> Otherwise, `.unemphasizedSelectedContentBackgroundColor` will be used forcibly and text becomes unreadable
// when the window appearance and theme are inconsistent.
if color == .unemphasizedSelectedContentBackgroundColor, // check if inactive
let textContainer = self.textContainer(forGlyphAt: self.glyphIndexForCharacter(at: charRange.location),
effectiveRange: nil, withoutAdditionalLayout: true),
let theme = (textContainer.textView as? any Themable)?.theme,
let secondarySelectionColor = theme.secondarySelectionColor
let textView = self.textContainer(forGlyphAt: self.glyphIndexForCharacter(at: charRange.location),
effectiveRange: nil, withoutAdditionalLayout: true)?.textView,
let theme = (textView as? any Themable)?.theme
{
secondarySelectionColor.setFill()
theme.effectiveSecondarySelectionColor(for: textView.effectiveAppearance)?.setFill()
}

super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)
Expand Down
71 changes: 70 additions & 1 deletion CotEditor/Sources/NSAppearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2018-2023 1024jp
// © 2018-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,4 +45,73 @@ extension NSAppearance {
false
}
}


/// THe corresponding appearance for the light/dark mode.
///
/// - Parameter isDark: Whether the return value to be for dark mode or not.
/// - Returns: A new appearance, or `self` if no change required.
final func appearance(for isDark: Bool) -> Self {

if isDark == self.isDark {
self
} else {
Self(named: self.name.name(for: isDark)) ?? self
}
}
}


extension NSColor {

/// Forcibly creates the appropriate color for the given appearance.
///
/// - Parameter appearance: The appearance to match.
/// - Returns: A new color.
func solve(for appearance: NSAppearance?) -> NSColor {

guard self.type != .componentBased else { return self }

var color = self
appearance?.performAsCurrentDrawingAppearance {
color = NSColor(cgColor: color.cgColor) ?? color
}
return color
}


/// Forcibly creates the appropriate color for the light/dark mode.
///
/// - Parameter isDark: Whether the return value to be for dark mode or not.
/// - Returns: A new color.
func forDarkMode(_ isDark: Bool) -> NSColor {

guard self.type != .componentBased else { return self }

return self.solve(for: NSAppearance(named: isDark ? .darkAqua : .aqua)!)
}
}


private extension NSAppearance.Name {

/// The corresponding appearance name for the light/dark mode.
///
/// - Parameter isDark: Whether the return value to be for dark mode or not.
/// - Returns: An appearance name.
func name(for isDark: Bool) -> Self {

switch self {
case .aqua, .darkAqua:
isDark ? .darkAqua : .aqua
case .vibrantLight, .vibrantDark:
isDark ? .vibrantDark : .vibrantLight
case .accessibilityHighContrastAqua, .accessibilityHighContrastDarkAqua:
isDark ? .accessibilityHighContrastDarkAqua : .accessibilityHighContrastAqua
case .accessibilityHighContrastVibrantLight, .accessibilityHighContrastVibrantDark:
isDark ? .accessibilityHighContrastVibrantDark : .accessibilityHighContrastVibrantLight
default:
self
}
}
}
51 changes: 35 additions & 16 deletions CotEditor/Sources/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,6 @@ struct Theme: Equatable {
}


/// Selection color for inactive text view.
var secondarySelectionColor: NSColor? {

guard
!self.selection.usesSystemSetting,
let color = self.selection.color.usingColorSpace(.genericRGB)
else { return nil }

return NSColor(calibratedWhite: color.lightnessComponent, alpha: 1.0)
}


/// Insertion point color to use.
var effectiveInsertionPointColor: NSColor {

Expand All @@ -168,10 +156,41 @@ struct Theme: Equatable {
}


/// Selected text background color to use.
var effectiveSelectionColor: NSColor {

self.selection.usesSystemSetting ? .selectedTextBackgroundColor : self.selection.color
/// Returns the selection color to use with the given appearance.
///
/// - Parameter appearance: The current appearance of the text view to draw.
/// - Returns: A color.
func effectiveSelectionColor(for appearance: NSAppearance) -> NSColor {

if self.selection.usesSystemSetting {
if self.isDarkTheme == appearance.isDark {
.selectedTextBackgroundColor
} else {
.selectedTextBackgroundColor.solve(for: appearance.appearance(for: self.isDarkTheme))
}
} else {
self.selection.color
}
}


/// Returns the selection color to use for inactive views.
///
/// - Parameter appearance: The current appearance of the text view to draw.
/// - Returns: A color.
func effectiveSecondarySelectionColor(for appearance: NSAppearance) -> NSColor? {

if self.selection.usesSystemSetting {
return if self.isDarkTheme == appearance.isDark {
.unemphasizedSelectedTextBackgroundColor
} else {
.unemphasizedSelectedTextBackgroundColor.solve(for: appearance.appearance(for: self.isDarkTheme))
}
} else {
guard let color = self.selection.color.usingColorSpace(.genericRGB) else { return nil }

return NSColor(calibratedWhite: color.lightnessComponent, alpha: 1.0)
}
}


Expand Down
2 changes: 1 addition & 1 deletion CotEditor/Sources/ThemeEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct ThemeEditorView: View {
selection: $theme.lineHighlight.binding, supportsOpacity: false)
SystemColorPicker(String(localized: "Selection:", table: "ThemeEditor"),
selection: $theme.selection,
systemColor: Color(nsColor: .selectedTextBackgroundColor),
systemColor: Color(nsColor: .selectedTextBackgroundColor.forDarkMode(self.theme.isDarkTheme)),
supportsOpacity: false)
}.accessibilityElement(children: .contain)
}.accessibilityElement(children: .contain)
Expand Down

0 comments on commit b8aae54

Please sign in to comment.