Checkpoint: Working toward compatibility with themes

This commit is contained in:
Nathan Sobo 2023-08-30 10:11:00 -06:00
parent 48d3e2d9b9
commit d763946b18
5 changed files with 180 additions and 114 deletions

View file

@ -1,8 +1,9 @@
#![allow(dead_code)]
use std::{num::ParseIntError, ops::Range};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use smallvec::SmallVec;
use std::fmt;
use std::{num::ParseIntError, ops::Range};
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
@ -19,6 +20,40 @@ pub struct Rgba {
pub a: f32,
}
struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
type Value = Rgba;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
if value.len() == 7 || value.len() == 9 {
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
let a = if value.len() == 9 {
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
} else {
Err(E::custom(
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
))
}
}
}
impl<'de> Deserialize<'de> for Rgba {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(RgbaVisitor)
}
}
pub trait Lerp {
fn lerp(&self, level: f32) -> Hsla;
}
@ -219,6 +254,19 @@ impl Into<gpui::color::Color> for Hsla {
}
}
impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First, deserialize it into Rgba
let rgba = Rgba::deserialize(deserializer)?;
// Then, use the From<Rgba> for Hsla implementation to convert it
Ok(Hsla::from(rgba))
}
}
pub struct ColorScale {
colors: SmallVec<[Hsla; 2]>,
positions: SmallVec<[f32; 2]>,

View file

@ -4,7 +4,7 @@ use crate::{
interactive::Interactive,
style::StyleHelpers,
text::ArcCow,
themes::Theme,
// themes::Theme,
};
use gpui::{platform::MouseButton, ViewContext};
use playground_macros::Element;
@ -82,10 +82,10 @@ impl<V: 'static, D: 'static> Button<V, D> {
view: &mut V,
cx: &mut ViewContext<V>,
) -> impl IntoElement<V> + Interactive<V> {
let colors = &cx.theme::<Theme>().colors;
// let colors = &cx.theme::<Theme>().colors;
let button = div()
.fill(colors.error(0.5))
// .fill(colors.error(0.5))
.h_4()
.children(self.label.clone());

View file

@ -1,12 +1,12 @@
#![allow(dead_code, unused_variables)]
use element::Element;
use crate::element::Element;
use gpui::{
geometry::{rect::RectF, vector::vec2f},
platform::WindowOptions,
};
use log::LevelFilter;
use simplelog::SimpleLogger;
use themes::{rose_pine, Theme, ThemeColors};
use themes::Theme;
use view::view;
use workspace::workspace;
@ -39,13 +39,7 @@ fn main() {
center: true,
..Default::default()
},
|_| {
view(|cx| {
playground(Theme {
colors: rose_pine::dawn(),
})
})
},
|_| view(|cx| playground(Theme::default())),
);
cx.platform().activate(true);
});

View file

@ -1,107 +1,134 @@
use crate::{
color::{Hsla, Lerp},
color::Hsla,
element::{Element, PaintContext},
layout_context::LayoutContext,
};
use gpui::{AppContext, WindowContext};
use std::{marker::PhantomData, ops::Range};
use gpui::WindowContext;
use serde::{de::Visitor, Deserialize, Deserializer};
use std::{collections::HashMap, fmt, marker::PhantomData};
pub mod rose_pine;
#[derive(Clone, Debug)]
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Theme {
pub colors: ThemeColors,
name: String,
is_light: bool,
lowest: Layer,
middle: Layer,
highest: Layer,
popover_shadow: Shadow,
modal_shadow: Shadow,
#[serde(deserialize_with = "deserialize_player_colors")]
players: Vec<PlayerColors>,
#[serde(deserialize_with = "deserialize_syntax_colors")]
syntax: HashMap<String, Hsla>,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Layer {
base: StyleSet,
variant: StyleSet,
on: StyleSet,
accent: StyleSet,
positive: StyleSet,
warning: StyleSet,
negative: StyleSet,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct StyleSet {
#[serde(rename = "default")]
default: ContainerColors,
hovered: ContainerColors,
pressed: ContainerColors,
active: ContainerColors,
disabled: ContainerColors,
inverted: ContainerColors,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct ContainerColors {
background: Hsla,
foreground: Hsla,
border: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct PlayerColors {
selection: Hsla,
cursor: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Shadow {
blur: u8,
color: Hsla,
offset: Vec<u8>,
}
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
cx.theme::<Theme>()
}
#[derive(Clone, Debug)]
pub struct ThemeColors {
pub base: Range<Hsla>,
pub surface: Range<Hsla>,
pub overlay: Range<Hsla>,
pub muted: Range<Hsla>,
pub subtle: Range<Hsla>,
pub text: Range<Hsla>,
pub highlight_low: Range<Hsla>,
pub highlight_med: Range<Hsla>,
pub highlight_high: Range<Hsla>,
pub success: Range<Hsla>,
pub warning: Range<Hsla>,
pub error: Range<Hsla>,
pub inserted: Range<Hsla>,
pub deleted: Range<Hsla>,
pub modified: Range<Hsla>,
fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
where
D: Deserializer<'de>,
{
struct PlayerArrayVisitor;
impl<'de> Visitor<'de> for PlayerArrayVisitor {
type Value = Vec<PlayerColors>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an object with integer keys")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut players = Vec::with_capacity(8);
while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
if key < 8 {
players.push(value);
} else {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(key as u64),
&"a key in range 0..7",
));
}
}
Ok(players)
}
}
deserializer.deserialize_map(PlayerArrayVisitor)
}
impl ThemeColors {
fn current(cx: &AppContext) -> &Self {
cx.global::<Vec<Self>>()
.last()
.expect("must call within a theme provider")
}
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SyntaxVisitor;
pub fn base(&self, level: f32) -> Hsla {
self.base.lerp(level)
}
impl<'de> Visitor<'de> for SyntaxVisitor {
type Value = HashMap<String, Hsla>;
pub fn surface(&self, level: f32) -> Hsla {
self.surface.lerp(level)
}
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with keys and objects with a single color field as values")
}
pub fn overlay(&self, level: f32) -> Hsla {
self.overlay.lerp(level)
}
pub fn muted(&self, level: f32) -> Hsla {
self.muted.lerp(level)
}
pub fn subtle(&self, level: f32) -> Hsla {
self.subtle.lerp(level)
}
pub fn text(&self, level: f32) -> Hsla {
self.text.lerp(level)
}
pub fn highlight_low(&self, level: f32) -> Hsla {
self.highlight_low.lerp(level)
}
pub fn highlight_med(&self, level: f32) -> Hsla {
self.highlight_med.lerp(level)
}
pub fn highlight_high(&self, level: f32) -> Hsla {
self.highlight_high.lerp(level)
}
pub fn success(&self, level: f32) -> Hsla {
self.success.lerp(level)
}
pub fn warning(&self, level: f32) -> Hsla {
self.warning.lerp(level)
}
pub fn error(&self, level: f32) -> Hsla {
self.error.lerp(level)
}
pub fn inserted(&self, level: f32) -> Hsla {
self.inserted.lerp(level)
}
pub fn deleted(&self, level: f32) -> Hsla {
self.deleted.lerp(level)
}
pub fn modified(&self, level: f32) -> Hsla {
self.modified.lerp(level)
fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut result = HashMap::new();
while let Some(key) = map.next_key()? {
let hsla: Hsla = map.next_value()?; // Deserialize values as Hsla
result.insert(key, hsla);
}
Ok(result)
}
}
deserializer.deserialize_map(SyntaxVisitor)
}
pub struct Themed<V: 'static, E> {

View file

@ -1,10 +1,9 @@
use crate::div::div;
use crate::element::{IntoElement, ParentElement};
use crate::style::StyleHelpers;
use crate::themes::theme;
use crate::{element::Element, themes::Theme};
use gpui::geometry::pixels;
use gpui::ViewContext;
use crate::{
div::div,
element::{Element, IntoElement, ParentElement},
style::StyleHelpers,
};
use gpui::{geometry::pixels, ViewContext};
use playground_macros::Element;
use crate as playground;
@ -17,29 +16,27 @@ pub fn workspace<V: 'static>() -> impl Element<V> {
impl WorkspaceElement {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = &cx.theme::<Theme>().colors;
// let theme = &cx.theme::<Theme>().colors;
div()
.full()
.flex()
.flex_col()
.fill(theme.base(0.5))
// .fill(theme.base(0.5))
.child(self.title_bar(cx))
.child(self.stage(cx))
.child(self.status_bar(cx))
}
fn title_bar<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let colors = &theme(cx).colors;
div().h(pixels(cx.titlebar_height())).fill(colors.base(0.))
// let colors = &theme(cx).colors;
div().h(pixels(cx.titlebar_height())) //.fill(colors.base(0.))
}
fn status_bar<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let colors = &theme(cx).colors;
div().h(pixels(cx.titlebar_height())).fill(colors.base(0.))
div().h(pixels(cx.titlebar_height())) //.fill(colors.base(0.))
}
fn stage<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let colors = &theme(cx).colors;
div().flex_grow()
}
}