forked from reya/gpui-native-kit
theme: build out the theme engine core with resolve pipeline
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
//! The adapter for unknown systems:
|
||||
//! no colors of its own, resolution degrades to the neutral presets.
|
||||
|
||||
use super::ThemeSource;
|
||||
use crate::ThemeVariant;
|
||||
use crate::system::SystemTheme;
|
||||
|
||||
// Constructed only by `platform_source` on non-macOS targets.
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub(crate) struct FallbackSource;
|
||||
|
||||
impl ThemeSource for FallbackSource {
|
||||
fn snapshot(&self, variant: ThemeVariant) -> SystemTheme {
|
||||
SystemTheme::fallback(variant)
|
||||
}
|
||||
|
||||
fn label(&self) -> &'static str {
|
||||
crate::system::SourceKind::Fallback.label()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//! macOS Adapter
|
||||
//!
|
||||
//! Reads AppKit's semantic colors in the effective appearance and watches
|
||||
//! the system's color preferences.
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
|
||||
use objc2_app_kit::{
|
||||
NSAppearance, NSAppearanceNameAqua, NSAppearanceNameDarkAqua, NSColor, NSColorSpace,
|
||||
NSWorkspace,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
NSDistributedNotificationCenter, NSNotification, NSNotificationName, NSOperationQueue,
|
||||
};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::ThemeSource;
|
||||
use crate::ThemeVariant;
|
||||
use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, SystemTheme};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MacosSource {
|
||||
_observers: Vec<Retained<ProtocolObject<dyn NSObjectProtocol>>>,
|
||||
}
|
||||
|
||||
impl MacosSource {
|
||||
fn color_to_hsla(color: &NSColor) -> Option<gpui::Hsla> {
|
||||
let srgb = NSColorSpace::sRGBColorSpace();
|
||||
let converted = color.colorUsingColorSpace(&srgb)?;
|
||||
|
||||
Some(gpui::Hsla::from(gpui::Rgba {
|
||||
r: converted.redComponent() as f32,
|
||||
g: converted.greenComponent() as f32,
|
||||
b: converted.blueComponent() as f32,
|
||||
a: converted.alphaComponent() as f32,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Resolves the semantic colors with `variant` as the current drawing appearance.
|
||||
fn snapshot_in_appearance(&self, variant: ThemeVariant) -> PartialPalette {
|
||||
let name = match variant {
|
||||
ThemeVariant::Light => {
|
||||
// SAFETY: extern statics exported by AppKit, always initialized.
|
||||
unsafe { NSAppearanceNameAqua }
|
||||
}
|
||||
ThemeVariant::Dark => {
|
||||
// SAFETY: as above.
|
||||
unsafe { NSAppearanceNameDarkAqua }
|
||||
}
|
||||
};
|
||||
|
||||
let Some(appearance) = NSAppearance::appearanceNamed(name) else {
|
||||
return self.read_palette();
|
||||
};
|
||||
|
||||
let palette = std::cell::Cell::new(PartialPalette::default());
|
||||
appearance.performAsCurrentDrawingAppearance(&block2::RcBlock::new(|| {
|
||||
palette.set(self.read_palette());
|
||||
}));
|
||||
|
||||
palette.into_inner()
|
||||
}
|
||||
|
||||
fn read_palette(&self) -> PartialPalette {
|
||||
PartialPalette {
|
||||
background: Self::color_to_hsla(&NSColor::windowBackgroundColor()),
|
||||
foreground: Self::color_to_hsla(&NSColor::labelColor()),
|
||||
surface: Self::color_to_hsla(&NSColor::textBackgroundColor()),
|
||||
surface_foreground: Self::color_to_hsla(&NSColor::labelColor()),
|
||||
primary_foreground: Self::color_to_hsla(&NSColor::alternateSelectedControlTextColor()),
|
||||
accent_foreground: Self::color_to_hsla(&NSColor::alternateSelectedControlTextColor()),
|
||||
secondary: None,
|
||||
secondary_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
||||
muted: None,
|
||||
muted_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
||||
destructive: Self::color_to_hsla(&NSColor::systemRedColor()),
|
||||
border: Self::color_to_hsla(&NSColor::separatorColor()),
|
||||
input: Self::color_to_hsla(&NSColor::controlBackgroundColor()),
|
||||
ring: Self::color_to_hsla(&NSColor::keyboardFocusIndicatorColor()),
|
||||
selection: Self::color_to_hsla(&NSColor::selectedTextBackgroundColor()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeSource for MacosSource {
|
||||
fn snapshot(&self, variant: ThemeVariant) -> SystemTheme {
|
||||
let workspace = NSWorkspace::sharedWorkspace();
|
||||
|
||||
let accessibility = AccessibilityPreferences {
|
||||
high_contrast: workspace.accessibilityDisplayShouldIncreaseContrast(),
|
||||
reduce_motion: workspace.accessibilityDisplayShouldReduceMotion(),
|
||||
reduce_transparency: workspace.accessibilityDisplayShouldReduceTransparency(),
|
||||
};
|
||||
|
||||
let palette = self.snapshot_in_appearance(variant);
|
||||
|
||||
SystemTheme {
|
||||
variant,
|
||||
accent: Self::color_to_hsla(&NSColor::controlAccentColor()),
|
||||
palette,
|
||||
accessibility,
|
||||
source: SourceKind::Macos,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires on accent and appearance changes (System Settings > Appearance)
|
||||
/// and re-runs the resolve pipeline through `async_cx`.
|
||||
fn install_watchers(&mut self, async_cx: gpui::AsyncApp) {
|
||||
let center = NSDistributedNotificationCenter::defaultCenter();
|
||||
let queue = NSOperationQueue::mainQueue();
|
||||
|
||||
for name in [
|
||||
"AppleColorPreferencesChangedNotification",
|
||||
"AppleInterfaceThemeChangedNotification",
|
||||
] {
|
||||
let async_cx = async_cx.clone();
|
||||
let block = block2::RcBlock::new(move |_notification: NonNull<NSNotification>| {
|
||||
async_cx.update(crate::reapply);
|
||||
});
|
||||
let name = NSNotificationName::from_str(name);
|
||||
// SAFETY: the observer token is retained for the source's lifetime,
|
||||
// the block is sendable (AsyncApp is Clone + 'static)
|
||||
// and the main queue runs it outside any App borrow.
|
||||
let observer = unsafe {
|
||||
center.addObserverForName_object_queue_usingBlock(
|
||||
Some(&name),
|
||||
None,
|
||||
Some(&queue),
|
||||
&block,
|
||||
)
|
||||
};
|
||||
self._observers.push(observer);
|
||||
}
|
||||
}
|
||||
|
||||
fn label(&self) -> &'static str {
|
||||
SourceKind::Macos.label()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user