gpui and gpui_platform now come from Zed's repo by git at 1a28cff, the code gpui-pre 0.3.7 republishes, so the gallery can later build for the browser. gpui takes stacker, as 0.2.2 always did, and gpui_platform takes font-kit, without which macOS draws no text; runtime_shaders moves to gpui_platform. The API moved: focus, blur and Tab steps take cx, Corner is Anchor, shaped lines paint with an alignment, defer_draw takes a content mask, the inspector registers a factory, async updates return their value, and wrapped lines are shared, not cloned. Updates through a window context go through the window, so a closed window still ends them as it did. Where the new gpui behaves differently, Ely keeps its old intent. Holds and reveals under a resting pointer survive typing through gpui's input-modality-independent hover; previews the keyboard also moves keep the default, and the calendar's leave ignores a key press. Enter reaches a button inside the code editor, since gpui now arms a key click on the press. The event logger skips a lone modifier. A video and an image grid keep their shape in a flex column under taffy 0.13, as a picture does. AGENTS.md records the new stack and the rules this port changes; the rest of the old gpui and taffy rules are re-tested in T44c.
217 lines
6.0 KiB
Rust
217 lines
6.0 KiB
Rust
mod calendar;
|
|
mod canvas;
|
|
mod chart;
|
|
mod dashboard;
|
|
mod files;
|
|
mod generative;
|
|
mod interaction;
|
|
mod maps;
|
|
mod media;
|
|
mod messaging;
|
|
mod misc;
|
|
mod onboarding;
|
|
mod palette;
|
|
mod platform;
|
|
mod project;
|
|
mod syntax;
|
|
mod tokens;
|
|
mod tooling;
|
|
|
|
#[cfg(all(test, feature = "test-support"))]
|
|
mod tests;
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
use gpui::{App, Global, SharedString, WindowAppearance};
|
|
|
|
use crate::motion;
|
|
pub use calendar::CalendarSizes;
|
|
pub use canvas::CanvasSizes;
|
|
pub use chart::ChartSizes;
|
|
pub use dashboard::DashboardSizes;
|
|
pub use files::FileSizes;
|
|
pub use generative::GenerativeSizes;
|
|
pub use interaction::InteractionSizes;
|
|
pub use maps::MapSizes;
|
|
pub use media::MediaSizes;
|
|
pub use messaging::MessagingSizes;
|
|
pub use misc::MiscSizes;
|
|
pub use onboarding::OnboardingSizes;
|
|
pub use palette::{HUE_NAMES, Mix, Palette, Syntax};
|
|
pub use platform::Platform;
|
|
pub use project::ProjectSizes;
|
|
pub use syntax::{SyntaxTheme, syntax_themes};
|
|
pub use tokens::{
|
|
AvatarSize, ContainerSize, ControlSize, Density, Elevation, IconSize, Radius, TextSize,
|
|
};
|
|
pub use tooling::ToolingSizes;
|
|
|
|
const FRAME: Duration = Duration::from_millis(8);
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum Mode {
|
|
#[default]
|
|
Light,
|
|
Dark,
|
|
}
|
|
|
|
impl From<WindowAppearance> for Mode {
|
|
fn from(appearance: WindowAppearance) -> Self {
|
|
match appearance {
|
|
WindowAppearance::Dark | WindowAppearance::VibrantDark => Mode::Dark,
|
|
WindowAppearance::Light | WindowAppearance::VibrantLight => Mode::Light,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Design tokens. One global per app.
|
|
pub struct Theme {
|
|
mode: Mode,
|
|
high_contrast: bool,
|
|
color_blind_safe: bool,
|
|
transition: u64,
|
|
custom: [Option<Palette>; 2],
|
|
pub colors: Palette,
|
|
pub density: Density,
|
|
pub radius_scale: f32,
|
|
pub font_scale: f32,
|
|
pub font_family: SharedString,
|
|
pub mono_family: SharedString,
|
|
pub reduced_motion: bool,
|
|
pub platform: Platform,
|
|
}
|
|
|
|
impl Global for Theme {}
|
|
|
|
pub trait ActiveTheme {
|
|
fn theme(&self) -> &Theme;
|
|
}
|
|
|
|
impl ActiveTheme for App {
|
|
fn theme(&self) -> &Theme {
|
|
self.global::<Theme>()
|
|
}
|
|
}
|
|
|
|
impl Theme {
|
|
pub(crate) fn init(cx: &mut App) {
|
|
cx.set_global(Theme {
|
|
mode: Mode::Light,
|
|
high_contrast: false,
|
|
color_blind_safe: false,
|
|
transition: 0,
|
|
custom: [None, None],
|
|
colors: Palette::light(false),
|
|
density: Density::Standard,
|
|
radius_scale: 1.0,
|
|
font_scale: 1.0,
|
|
font_family: "Inter".into(),
|
|
mono_family: "JetBrains Mono".into(),
|
|
reduced_motion: false,
|
|
platform: Platform::current(),
|
|
});
|
|
}
|
|
|
|
pub fn mode(&self) -> Mode {
|
|
self.mode
|
|
}
|
|
|
|
pub fn high_contrast(&self) -> bool {
|
|
self.high_contrast
|
|
}
|
|
|
|
pub fn color_blind_safe(&self) -> bool {
|
|
self.color_blind_safe
|
|
}
|
|
|
|
pub fn is_dark(&self) -> bool {
|
|
self.mode == Mode::Dark
|
|
}
|
|
|
|
/// Cross-fades the palette to `mode`.
|
|
pub fn set_mode(mode: Mode, cx: &mut App) {
|
|
log::info!("theme: mode -> {mode:?}");
|
|
cx.global_mut::<Theme>().mode = mode;
|
|
Self::fade_to_target(cx);
|
|
}
|
|
|
|
pub fn set_high_contrast(on: bool, cx: &mut App) {
|
|
log::info!("theme: high contrast -> {on}");
|
|
cx.global_mut::<Theme>().high_contrast = on;
|
|
Self::fade_to_target(cx);
|
|
}
|
|
|
|
/// Chart hues that stay apart for color-blind eyes, in Ely's own palettes.
|
|
pub fn set_color_blind_safe(on: bool, cx: &mut App) {
|
|
log::info!("theme: color-blind safe charts -> {on}");
|
|
cx.global_mut::<Theme>().color_blind_safe = on;
|
|
Self::fade_to_target(cx);
|
|
}
|
|
|
|
/// The owner's palette for `mode`, as an edited or imported theme gives, or none to bring Ely's back. The colors fade to it when `mode` shows. High contrast and color-blind safe charts change Ely's own palettes alone.
|
|
pub fn set_palette(mode: Mode, palette: Option<Palette>, cx: &mut App) {
|
|
log::info!(
|
|
"theme: {mode:?} palette -> {}",
|
|
if palette.is_some() {
|
|
"the owner's"
|
|
} else {
|
|
"Ely's"
|
|
}
|
|
);
|
|
cx.global_mut::<Theme>().custom[mode as usize] = palette;
|
|
Self::fade_to_target(cx);
|
|
}
|
|
|
|
/// The palette the shown mode fades to: the owner's, or Ely's.
|
|
pub fn palette(&self) -> Palette {
|
|
self.target()
|
|
}
|
|
|
|
/// Edits non-color tokens, then repaints.
|
|
pub fn update(cx: &mut App, edit: impl FnOnce(&mut Theme)) {
|
|
edit(cx.global_mut::<Theme>());
|
|
cx.refresh_windows();
|
|
}
|
|
|
|
fn target(&self) -> Palette {
|
|
if let Some(palette) = &self.custom[self.mode as usize] {
|
|
return palette.clone();
|
|
}
|
|
let mut palette = match self.mode {
|
|
Mode::Light => Palette::light(self.high_contrast),
|
|
Mode::Dark => Palette::dark(self.high_contrast),
|
|
};
|
|
if self.color_blind_safe {
|
|
palette.chart = palette::color_blind_chart(self.mode);
|
|
}
|
|
palette
|
|
}
|
|
|
|
fn fade_to_target(cx: &mut App) {
|
|
let duration = motion::duration(motion::THEME, cx);
|
|
let theme = cx.global_mut::<Theme>();
|
|
theme.transition += 1;
|
|
let (id, from, to) = (theme.transition, theme.colors.clone(), theme.target());
|
|
let start = Instant::now();
|
|
cx.spawn(async move |cx| {
|
|
loop {
|
|
cx.background_executor().timer(FRAME).await;
|
|
let t = (start.elapsed().as_secs_f32() / duration.as_secs_f32()).min(1.0);
|
|
let running = cx.update(|cx| {
|
|
let theme = cx.global_mut::<Theme>();
|
|
if theme.transition != id {
|
|
return false;
|
|
}
|
|
theme.colors = from.mix(&to, motion::ease_in_out_cubic(t));
|
|
cx.refresh_windows();
|
|
t < 1.0
|
|
});
|
|
if !running {
|
|
return;
|
|
}
|
|
}
|
|
})
|
|
.detach();
|
|
}
|
|
}
|