Skip to content

Implement View.italic and Font.italic modifiers #209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions Sources/AppKitBackend/AppKitBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,11 @@ public final class AppKitBackend: AppBackend {
onChange: @escaping (Bool) -> Void
) {
let toggle = toggle as! NSButton
toggle.attributedTitle = Self.attributedString(
for: label,
in: environment.with(\.multilineTextAlignment, .center)
)
toggle.isEnabled = environment.isEnabled
toggle.title = label
toggle.onAction = { toggle in
let toggle = toggle as! NSButton
onChange(toggle.state == .on)
Expand Down Expand Up @@ -1131,15 +1134,23 @@ public final class AppKitBackend: AppBackend {
private static func font(for font: Font.Resolved) -> NSFont {
let size = CGFloat(font.pointSize)
let weight = weight(for: font.weight)

let nsFont: NSFont
switch font.identifier.kind {
case .system:
switch font.design {
case .default:
return NSFont.systemFont(ofSize: size, weight: weight)
nsFont = NSFont.systemFont(ofSize: size, weight: weight)
case .monospaced:
return NSFont.monospacedSystemFont(ofSize: size, weight: weight)
nsFont = NSFont.monospacedSystemFont(ofSize: size, weight: weight)
}
}

if font.isItalic {
return NSFontManager.shared.convert(nsFont, toHaveTrait: .italicFontMask)
} else {
return nsFont
}
}

private static func weight(for weight: Font.Weight) -> NSFont.Weight {
Expand Down
4 changes: 4 additions & 0 deletions Sources/Gtk/Utility/CSS/CSSProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public struct CSSProperty: Equatable {
CSSProperty(key: "font-family", value: family)
}

public static func fontStyle(_ style: String) -> CSSProperty {
CSSProperty(key: "font-style", value: style)
}

public static func rgba(_ color: Color) -> String {
let red = color.red * 255
let green = color.green * 255
Expand Down
4 changes: 4 additions & 0 deletions Sources/Gtk3/Utility/CSS/CSSProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public struct CSSProperty: Equatable {
CSSProperty(key: "font-family", value: family)
}

public static func fontStyle(_ style: String) -> CSSProperty {
CSSProperty(key: "font-style", value: style)
}

public static func rgba(_ color: Color) -> String {
let red = color.red * 255
let green = color.green * 255
Expand Down
8 changes: 8 additions & 0 deletions Sources/Gtk3Backend/Gtk3Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,10 @@ public final class Gtk3Backend: AppBackend {
toggle.toggled = { widget in
onChange(widget.active)
}
toggle.css.clear()
// This is a control, but we set isControl to false anyway because isControl overrides
// the button background and makes the on and off states of the toggle look identical.
toggle.css.set(properties: Self.cssProperties(for: environment, isControl: false))
}

public func setState(ofToggle toggle: Widget, to state: Bool) {
Expand Down Expand Up @@ -1474,6 +1478,10 @@ public final class Gtk3Backend: AppBackend {
}
}

if font.isItalic {
properties.append(.fontStyle("italic"))
}

if isControl {
switch environment.colorScheme {
case .light:
Expand Down
8 changes: 8 additions & 0 deletions Sources/GtkBackend/GtkBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,10 @@ public final class GtkBackend: AppBackend {
toggle.toggled = { widget in
onChange(widget.active)
}
toggle.css.clear()
// This is a control, but we set isControl to false anyway because isControl overrides
// the button background and makes the on and off states of the toggle look identical.
toggle.css.set(properties: Self.cssProperties(for: environment, isControl: false))
}

public func setState(ofToggle toggle: Widget, to state: Bool) {
Expand Down Expand Up @@ -1525,6 +1529,10 @@ public final class GtkBackend: AppBackend {
}
}

if font.isItalic {
properties.append(.fontStyle("italic"))
}

if isControl {
switch environment.colorScheme {
case .light:
Expand Down
21 changes: 19 additions & 2 deletions Sources/SwiftCrossUI/Values/Font.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ public struct Font: Hashable, Sendable {
return font
}

/// Selects whether or not to italicize the font.
public func italic(_ italic: Bool = true) -> Font {
var font = self
font.overlay.italicize = italic
return font
}

/// Overrides the font's weight. Takes an optional for convenience. Does
/// nothing if given `nil`.
public func weight(_ weight: Weight?) -> Font {
Expand Down Expand Up @@ -167,6 +174,10 @@ public struct Font: Hashable, Sendable {
/// overlay has been applied if one is present.
var emphasize: Bool = false

/// If `true`, overrides the font to be italicized. If `false`, does
/// nothing.
var italicize: Bool = false

/// Overrides the font's design.
var design: Design?

Expand All @@ -185,6 +196,9 @@ public struct Font: Hashable, Sendable {
if emphasize {
resolvedFont.weight = emphasizedWeight
}
if italicize {
resolvedFont.isItalic = true
}
if let pointSize {
resolvedFont.pointSize = pointSize
}
Expand Down Expand Up @@ -212,6 +226,7 @@ public struct Font: Hashable, Sendable {
public var lineHeight: Double
public var weight: Weight
public var design: Design
public var isItalic: Bool
}

public struct Context: Sendable {
Expand All @@ -237,7 +252,8 @@ public struct Font: Hashable, Sendable {
// constant ratio).
lineHeight: (size * 1.25).rounded(.awayFromZero),
weight: weight ?? .regular,
design: design ?? .default
design: design ?? .default,
isItalic: false
)
}
case .dynamic(let textStyle):
Expand All @@ -248,7 +264,8 @@ public struct Font: Hashable, Sendable {
pointSize: resolvedTextStyle.pointSize,
lineHeight: resolvedTextStyle.lineHeight,
weight: resolvedTextStyle.weight,
design: .default
design: .default,
isItalic: false
)
}

Expand Down
10 changes: 10 additions & 0 deletions Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
/// a ``Font/TextStyle``, forces the style's emphasized weight to be
/// used.
///
/// Deprecated and renamed for clarity. Use ``View.fontWeight(_:)``

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/NavigationLink/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/TupleView7/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/Capsule/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/List/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/EitherView/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/GeometryReader/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/TupleView10/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/WebView/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/Circle/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/Menu/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/NavigationLink/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/Table/bold()'

Check warning on line 32 in Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift

View workflow job for this annotation

GitHub Actions / uikit (iPhone)

'View.fontWeight(_:)' doesn't exist at '/SwiftCrossUI/NavigationSplitView/bold()'
/// to make text bold.
@available(
*, deprecated,
Expand All @@ -52,4 +52,14 @@
)
}
}

/// Forces any contained text to become italic.
public func italic() -> some View {
EnvironmentModifier(self) { environment in
return environment.with(
\.fontOverlay.italicize,
true
)
}
}
}
15 changes: 13 additions & 2 deletions Sources/UIKitBackend/Font+UIFont.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import UIKit

extension Font.Resolved {
var uiFont: UIFont {
let uiFont: UIFont
switch identifier.kind {
case .system:
let weight: UIFont.Weight =
Expand All @@ -29,10 +30,20 @@ extension Font.Resolved {

switch design {
case .monospaced:
return .monospacedSystemFont(ofSize: CGFloat(pointSize), weight: weight)
uiFont = .monospacedSystemFont(ofSize: CGFloat(pointSize), weight: weight)
case .default:
return .systemFont(ofSize: CGFloat(pointSize), weight: weight)
uiFont = .systemFont(ofSize: CGFloat(pointSize), weight: weight)
}
}

if isItalic {
let descriptor = uiFont.fontDescriptor.withSymbolicTraits(.traitItalic)
return UIFont(
descriptor: descriptor ?? uiFont.fontDescriptor,
size: CGFloat(pointSize)
)
} else {
return uiFont
}
}
}
6 changes: 6 additions & 0 deletions Sources/WinUIBackend/WinUIBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,9 @@ extension EnvironmentValues {
control.fontWeight.weight = resolvedFont.winUIFontWeight
control.foreground = winUIForegroundBrush
control.isEnabled = isEnabled
if resolvedFont.isItalic {
control.fontStyle = .italic
}
switch colorScheme {
case .light:
control.requestedTheme = .light
Expand All @@ -1766,6 +1769,9 @@ extension EnvironmentValues {
textBlock.fontSize = resolvedFont.pointSize
textBlock.fontWeight.weight = resolvedFont.winUIFontWeight
textBlock.foreground = winUIForegroundBrush
if resolvedFont.isItalic {
textBlock.fontStyle = .italic
}
}
}

Expand Down
Loading