mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 19:54:10 +03:00
WIP
This commit is contained in:
parent
5b0e333967
commit
44608517c1
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -3363,6 +3363,7 @@ dependencies = [
|
||||
"font-kit",
|
||||
"foreign-types 0.3.2",
|
||||
"futures 0.3.28",
|
||||
"gpui2_macros",
|
||||
"gpui_macros",
|
||||
"image",
|
||||
"itertools",
|
||||
@ -7696,6 +7697,21 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "storybook2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui3",
|
||||
"log",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"settings",
|
||||
"simplelog",
|
||||
"theme",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.3"
|
||||
|
@ -65,6 +65,7 @@ members = [
|
||||
"crates/sqlez_macros",
|
||||
"crates/feature_flags",
|
||||
"crates/storybook",
|
||||
"crates/storybook2",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
"crates/text",
|
||||
|
@ -16,6 +16,7 @@ doctest = false
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui_macros = { path = "../gpui_macros" }
|
||||
gpui2_macros = { path = "../gpui2_macros" }
|
||||
util = { path = "../util" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
|
@ -1,7 +1,8 @@
|
||||
pub mod div;
|
||||
pub mod editor;
|
||||
mod div;
|
||||
mod img;
|
||||
mod svg;
|
||||
mod text;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use div::div;
|
||||
pub use editor::field;
|
||||
pub use div::*;
|
||||
|
@ -77,7 +77,7 @@ impl<S: 'static> Element for Div<S> {
|
||||
// }
|
||||
|
||||
for child in &mut self.children {
|
||||
child.paint(scrolled_origin, state, cx);
|
||||
child.paint(scrolled_origin, state, cx)?;
|
||||
}
|
||||
|
||||
// if pop_layer {
|
||||
|
@ -1,61 +0,0 @@
|
||||
use super::{Element, Handle, Layout, LayoutId, Result, SharedString, ViewContext};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub fn field<S>(editor: Handle<Editor>) -> EditorElement<S> {
|
||||
EditorElement {
|
||||
editor,
|
||||
field: true,
|
||||
placeholder_text: None,
|
||||
parent_state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorElement<S> {
|
||||
editor: Handle<Editor>,
|
||||
field: bool,
|
||||
placeholder_text: Option<SharedString>,
|
||||
parent_state: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> EditorElement<S> {
|
||||
pub fn field(mut self) -> Self {
|
||||
self.field = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn placeholder_text(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.placeholder_text = Some(text.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Element for EditorElement<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
self.editor.update(cx, |_editor, _cx| todo!())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_layout: Layout,
|
||||
_state: &mut Self::State,
|
||||
_frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
self.editor.update(cx, |_editor, _cx| todo!())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Editor {}
|
||||
|
||||
impl Editor {
|
||||
pub fn new(_: &mut ViewContext<Self>) -> Self {
|
||||
Editor {}
|
||||
}
|
||||
}
|
@ -1,101 +1,93 @@
|
||||
use crate as gpui2;
|
||||
use crate::{
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
Element,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
use gpui::scene;
|
||||
use gpui2_macros::IntoElement;
|
||||
use crate::{Element, Layout, LayoutId, Result, Style, Styled};
|
||||
use refineable::RefinementCascade;
|
||||
use util::arc_cow::ArcCow;
|
||||
use util::ResultExt;
|
||||
use std::marker::PhantomData;
|
||||
use util::{arc_cow::ArcCow, ResultExt};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Img {
|
||||
pub struct Img<S> {
|
||||
style: RefinementCascade<Style>,
|
||||
uri: Option<ArcCow<'static, str>>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn img() -> Img {
|
||||
pub fn img<S>() -> Img<S> {
|
||||
Img {
|
||||
style: RefinementCascade::default(),
|
||||
uri: None,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl Img {
|
||||
impl<S> Img<S> {
|
||||
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.uri = Some(uri.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Img {
|
||||
type PaintState = ();
|
||||
impl<S: 'static> Element for Img<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
|
||||
_: &mut Self::State,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let layout_id = cx.add_layout_node(style, [])?;
|
||||
let layout_id = cx.request_layout(style, [])?;
|
||||
Ok((layout_id, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &gpui::Layout,
|
||||
_: &mut Self::PaintState,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
_: &mut Self::FrameState,
|
||||
cx: &mut crate::ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
let style = self.computed_style();
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
let bounds = layout.bounds;
|
||||
|
||||
style.paint_background(bounds, cx);
|
||||
|
||||
if let Some(uri) = &self.uri {
|
||||
let image_future = cx.image_cache.get(uri.clone());
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
.and_then(ResultExt::log_err)
|
||||
{
|
||||
let rem_size = cx.rem_size();
|
||||
cx.scene().push_image(scene::Image {
|
||||
bounds,
|
||||
border: gpui::Border {
|
||||
color: style.border_color.unwrap_or_default().into(),
|
||||
top: style.border_widths.top.to_pixels(rem_size),
|
||||
right: style.border_widths.right.to_pixels(rem_size),
|
||||
bottom: style.border_widths.bottom.to_pixels(rem_size),
|
||||
left: style.border_widths.left.to_pixels(rem_size),
|
||||
},
|
||||
corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
grayscale: false,
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if image_future.await.log_err().is_some() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
// if let Some(uri) = &self.uri {
|
||||
// let image_future = cx.image_cache.get(uri.clone());
|
||||
// if let Some(data) = image_future
|
||||
// .clone()
|
||||
// .now_or_never()
|
||||
// .and_then(ResultExt::log_err)
|
||||
// {
|
||||
// let rem_size = cx.rem_size();
|
||||
// cx.scene().push_image(scene::Image {
|
||||
// bounds,
|
||||
// border: gpui::Border {
|
||||
// color: style.border_color.unwrap_or_default().into(),
|
||||
// top: style.border_widths.top.to_pixels(rem_size),
|
||||
// right: style.border_widths.right.to_pixels(rem_size),
|
||||
// bottom: style.border_widths.bottom.to_pixels(rem_size),
|
||||
// left: style.border_widths.left.to_pixels(rem_size),
|
||||
// },
|
||||
// corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
// grayscale: false,
|
||||
// data,
|
||||
// })
|
||||
// } else {
|
||||
// cx.spawn(|this, mut cx| async move {
|
||||
// if image_future.await.log_err().is_some() {
|
||||
// this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
// }
|
||||
// })
|
||||
// .detach();
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for Img {
|
||||
impl<S> Styled for Img<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
@ -106,5 +98,3 @@ impl Styleable for Img {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleHelpers for Img {}
|
||||
|
@ -1,75 +1,72 @@
|
||||
use crate::{
|
||||
self as gpui2, scene,
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
Element, IntoElement, Layout, LayoutId, Rgba,
|
||||
};
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
use crate::{Element, Layout, LayoutId, Result, Style, Styled};
|
||||
use refineable::RefinementCascade;
|
||||
use std::borrow::Cow;
|
||||
use util::ResultExt;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Svg {
|
||||
pub struct Svg<S> {
|
||||
path: Option<Cow<'static, str>>,
|
||||
style: RefinementCascade<Style>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn svg() -> Svg {
|
||||
pub fn svg<S>() -> Svg<S> {
|
||||
Svg {
|
||||
path: None,
|
||||
style: RefinementCascade::<Style>::default(),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
impl<S> Svg<S> {
|
||||
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Svg {
|
||||
type PaintState = ();
|
||||
impl<S: 'static> Element for Svg<S> {
|
||||
type State = S;
|
||||
type FrameState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) -> anyhow::Result<(LayoutId, Self::PaintState)>
|
||||
_: &mut S,
|
||||
cx: &mut crate::ViewContext<S>,
|
||||
) -> anyhow::Result<(LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
Ok((cx.add_layout_node(style, [])?, ()))
|
||||
Ok((cx.request_layout(style, [])?, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
_: &mut Self::PaintState,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) where
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
_: &mut Self::FrameState,
|
||||
cx: &mut crate::ViewContext<S>,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||
if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
|
||||
let icon = scene::Icon {
|
||||
bounds: layout.bounds + parent_origin,
|
||||
svg: svg_tree,
|
||||
path: path.clone(),
|
||||
color: Rgba::from(fill_color).into(),
|
||||
};
|
||||
// let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||
// if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||
// if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
|
||||
// let icon = scene::Icon {
|
||||
// bounds: layout.bounds + parent_origin,
|
||||
// svg: svg_tree,
|
||||
// path: path.clone(),
|
||||
// color: Rgba::from(fill_color).into(),
|
||||
// };
|
||||
|
||||
cx.scene().push_icon(icon);
|
||||
}
|
||||
}
|
||||
// cx.scene().push_icon(icon);
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for Svg {
|
||||
impl<S> Styled for Svg<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
|
||||
@ -80,5 +77,3 @@ impl Styleable for Svg {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleHelpers for Svg {}
|
||||
|
@ -1,69 +1,67 @@
|
||||
use crate::{
|
||||
element::{Element, IntoElement, Layout},
|
||||
AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, LineLayout, Pixels, Result, Size,
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
geometry::{vector::Vector2F, Size},
|
||||
text_layout::LineLayout,
|
||||
LayoutId,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
impl<V: 'static> IntoElement<V> for ArcCow<'static, str> {
|
||||
type Element = Text;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
Text { text: self }
|
||||
impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
Text {
|
||||
text: self,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoElement<V> for &'static str {
|
||||
type Element = Text;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
impl<V: 'static> IntoAnyElement<V> for &'static str {
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
Text {
|
||||
text: ArcCow::from(self),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
pub struct Text<S> {
|
||||
text: ArcCow<'static, str>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Text {
|
||||
type PaintState = Arc<Mutex<Option<TextLayout>>>;
|
||||
impl<S: 'static> Element for Text<S> {
|
||||
type State = S;
|
||||
type FrameState = Arc<Mutex<Option<TextLayout>>>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)> {
|
||||
let fonts = cx.platform().fonts();
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<(LayoutId, Self::FrameState)> {
|
||||
let text_system = cx.text_system().clone();
|
||||
let text_style = cx.text_style();
|
||||
let line_height = cx.font_cache().line_height(text_style.font_size);
|
||||
let line_height = cx.text_system().line_height(text_style.font_size);
|
||||
let text = self.text.clone();
|
||||
let paint_state = Arc::new(Mutex::new(None));
|
||||
|
||||
let layout_id = cx.add_measured_layout_node(Default::default(), {
|
||||
let paint_state = paint_state.clone();
|
||||
move |_params| {
|
||||
let line_layout = fonts.layout_line(
|
||||
let layout_id = cx.request_measured_layout(Default::default(), cx.rem_size(), {
|
||||
let frame_state = paint_state.clone();
|
||||
move |_, _| {
|
||||
let line_layout = text_system.layout_str(
|
||||
text.as_ref(),
|
||||
text_style.font_size,
|
||||
&[(text.len(), text_style.to_run())],
|
||||
);
|
||||
|
||||
let size = Size {
|
||||
width: line_layout.width,
|
||||
width: line_layout.width(),
|
||||
height: line_height,
|
||||
};
|
||||
|
||||
paint_state.lock().replace(TextLayout {
|
||||
line_layout: Arc::new(line_layout),
|
||||
frame_state.lock().replace(TextLayout {
|
||||
line: Arc::new(line_layout),
|
||||
line_height,
|
||||
});
|
||||
|
||||
@ -76,44 +74,33 @@ impl<V: 'static> Element<V> for Text {
|
||||
|
||||
fn paint<'a>(
|
||||
&mut self,
|
||||
_view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
layout: Layout,
|
||||
_: &mut Self::State,
|
||||
paint_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<S>,
|
||||
) {
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
let bounds = layout.bounds;
|
||||
|
||||
let line_layout;
|
||||
let line;
|
||||
let line_height;
|
||||
{
|
||||
let paint_state = paint_state.lock();
|
||||
let paint_state = paint_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
line_layout = paint_state.line_layout.clone();
|
||||
line = paint_state.line.clone();
|
||||
line_height = paint_state.line_height;
|
||||
}
|
||||
|
||||
let text_style = cx.text_style();
|
||||
let line =
|
||||
gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
|
||||
|
||||
// TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
|
||||
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
|
||||
let visible_bounds = bounds;
|
||||
line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoElement<V> for Text {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
line.paint(bounds.origin, visible_bounds, line_height, cx.legacy_cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextLayout {
|
||||
line_layout: Arc<LineLayout>,
|
||||
line_height: f32,
|
||||
line: Arc<Line>,
|
||||
line_height: Pixels,
|
||||
}
|
||||
|
@ -79,6 +79,15 @@ pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
|
||||
Size { width, height }
|
||||
}
|
||||
|
||||
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
|
||||
fn from(val: Size<Option<Pixels>>) -> Self {
|
||||
Size {
|
||||
width: val.width.map(|p| p.0 as f32),
|
||||
height: val.height.map(|p| p.0 as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
@ -252,6 +261,12 @@ impl From<f64> for Pixels {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Pixels {
|
||||
fn from(val: f32) -> Self {
|
||||
Pixels(val)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Pixels {}
|
||||
unsafe impl bytemuck::Zeroable for Pixels {}
|
||||
|
||||
|
@ -24,6 +24,8 @@ pub use geometry::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
pub use serde;
|
||||
pub use serde_json;
|
||||
pub use smallvec;
|
||||
pub use smol::Timer;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@ -97,49 +99,3 @@ impl<'a, T> DerefMut for Reference<'a, T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct Workspace {
|
||||
left_panel: AnyView<Self>,
|
||||
}
|
||||
|
||||
fn workspace(cx: &mut WindowContext) -> View<Workspace> {
|
||||
let workspace = cx.entity(|cx| Workspace {
|
||||
left_panel: collab_panel(cx).into_any(),
|
||||
});
|
||||
view(workspace, |workspace, _cx| {
|
||||
div().child(workspace.left_panel.clone())
|
||||
})
|
||||
}
|
||||
|
||||
struct CollabPanel {
|
||||
filter_editor: Handle<editor::Editor>,
|
||||
}
|
||||
|
||||
fn collab_panel(cx: &mut WindowContext) -> View<CollabPanel> {
|
||||
let panel = cx.entity(|cx| CollabPanel::new(cx));
|
||||
view(panel, |panel, _cx| {
|
||||
div().child(div()).child(
|
||||
field(panel.filter_editor.clone()).placeholder_text("Search channels, contacts"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
impl CollabPanel {
|
||||
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
filter_editor: cx.entity(|cx| editor::Editor::new(cx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut cx = AppContext::test();
|
||||
|
||||
cx.open_window(WindowOptions::default(), |cx| workspace(cx));
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,19 @@ pub struct TextStyle {
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
TextStyle {
|
||||
color: Hsla::default(),
|
||||
font_family: SharedString::default(),
|
||||
font_size: rems(1.),
|
||||
font_weight: FontWeight::default(),
|
||||
font_style: FontStyle::default(),
|
||||
underline: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
|
||||
if let Some(weight) = style.font_weight {
|
||||
|
@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
pub use taffy::tree::NodeId as LayoutId;
|
||||
pub use taffy::*;
|
||||
use taffy::{style::AvailableSpace, tree::MeasureFunc, *};
|
||||
pub struct TaffyLayoutEngine(Taffy);
|
||||
|
||||
impl TaffyLayoutEngine {
|
||||
@ -26,11 +26,38 @@ impl TaffyLayoutEngine {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_measured_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
measure: impl FnOnce(Size<Option<Pixels>>, Size<AvailableSpace>) + 'static,
|
||||
) -> Result<LayoutId> {
|
||||
let style = style.to_taffy(rem_size);
|
||||
|
||||
self.0
|
||||
.new_leaf_with_measure(style, Box::new(Measureable(measure)))
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
|
||||
Ok(self.0.layout(id).map(Into::into)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct Measureable<F>(F);
|
||||
|
||||
impl<F> taffy::tree::Measurable for Measureable<F>
|
||||
where
|
||||
F: Send + Sync + FnOnce(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>,
|
||||
{
|
||||
fn measure(
|
||||
&self,
|
||||
known_dimensions: taffy::prelude::Size<Option<f32>>,
|
||||
available_space: taffy::prelude::Size<AvailableSpace>,
|
||||
) -> taffy::prelude::Size<f32> {
|
||||
(self.0)(known_dimensions.into(), available_space.into()).into()
|
||||
}
|
||||
}
|
||||
|
||||
trait ToTaffy<Output> {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> Output;
|
||||
}
|
||||
@ -228,9 +255,3 @@ impl From<&taffy::tree::Layout> for Layout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Pixels {
|
||||
fn from(pixels: f32) -> Self {
|
||||
Pixels(pixels)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{PlatformWindow, Point, Style, TextStyleRefinement};
|
||||
use crate::{PlatformWindow, Point, Size, Style, TextStyle, TextStyleRefinement};
|
||||
|
||||
use super::{
|
||||
px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference,
|
||||
@ -6,10 +6,12 @@ use super::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use refineable::Refineable;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use taffy::style::AvailableSpace;
|
||||
|
||||
pub struct AnyWindow {}
|
||||
|
||||
@ -72,6 +74,19 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
.request_layout(style, rem_size, &self.app.layout_id_buffer)
|
||||
}
|
||||
|
||||
pub fn request_measured_layout<
|
||||
F: FnOnce(Size<Option<Pixels>>, Size<AvailableSpace>) + 'static,
|
||||
>(
|
||||
&mut self,
|
||||
style: Style,
|
||||
rem_size: Pixels,
|
||||
measure: F,
|
||||
) -> Result<LayoutId> {
|
||||
self.window
|
||||
.layout_engine
|
||||
.request_measured_layout(style, rem_size, measure)
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
|
||||
Ok(self
|
||||
.window
|
||||
@ -92,6 +107,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
self.window.text_style_stack.pop();
|
||||
}
|
||||
|
||||
pub fn text_style(&self) -> &Vec<TextStyle> {
|
||||
let style = TextStyleRefinement::default();
|
||||
for refinement in &self.window.text_style_stack {
|
||||
style.refine(refinement);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||
self.window.platform_window.mouse_position()
|
||||
}
|
||||
|
2919
crates/storybook2/Cargo.lock
generated
Normal file
2919
crates/storybook2/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
crates/storybook2/Cargo.toml
Normal file
23
crates/storybook2/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "storybook2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "storybook"
|
||||
path = "src/storybook2.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui3 = { path = "../gpui3" }
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui3 = { path = "../gpui3", features = ["test"] }
|
5
crates/storybook2/build.rs
Normal file
5
crates/storybook2/build.rs
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
|
||||
// TODO: We shouldn't depend on WebRTC in editor
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
}
|
72
crates/storybook2/docs/thoughts.md
Normal file
72
crates/storybook2/docs/thoughts.md
Normal file
@ -0,0 +1,72 @@
|
||||
Much of element styling is now handled by an external engine.
|
||||
|
||||
|
||||
How do I make an element hover.
|
||||
|
||||
There's a hover style.
|
||||
|
||||
Hoverable needs to wrap another element. That element can be styled.
|
||||
|
||||
```rs
|
||||
struct Hoverable<E: Element> {
|
||||
|
||||
}
|
||||
|
||||
impl<V> Element<V> for Hoverable {
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
```rs
|
||||
#[derive(Styled, Interactive)]
|
||||
pub struct Div {
|
||||
declared_style: StyleRefinement,
|
||||
interactions: Interactions
|
||||
}
|
||||
|
||||
pub trait Styled {
|
||||
fn declared_style(&mut self) -> &mut StyleRefinement;
|
||||
fn compute_style(&mut self) -> Style {
|
||||
Style::default().refine(self.declared_style())
|
||||
}
|
||||
|
||||
// All the tailwind classes, modifying self.declared_style()
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
}
|
||||
|
||||
pub trait Interactive<V> {
|
||||
fn interactions(&mut self) -> &mut Interactions<V>;
|
||||
|
||||
fn on_click(self, )
|
||||
}
|
||||
|
||||
struct Interactions<V> {
|
||||
click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
```rs
|
||||
|
||||
|
||||
trait Stylable {
|
||||
type Style;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
177
crates/storybook2/src/collab_panel.rs
Normal file
177
crates/storybook2/src/collab_panel.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use crate::theme::{theme, Theme};
|
||||
use gpui2::{
|
||||
elements::{div, div::ScrollState, img, svg},
|
||||
style::{StyleHelpers, Styleable},
|
||||
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct CollabPanelElement<V: 'static> {
|
||||
view_type: PhantomData<V>,
|
||||
scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
// When I improve child view rendering, I'd like to have V implement a trait that
|
||||
// provides the scroll state, among other things.
|
||||
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
|
||||
CollabPanelElement {
|
||||
view_type: PhantomData,
|
||||
scroll_state,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> CollabPanelElement<V> {
|
||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
// Panel
|
||||
div()
|
||||
.w_64()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.border_color(theme.middle.base.default.border)
|
||||
.border()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
// List Container
|
||||
.child(
|
||||
div()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.border_b()
|
||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||
// .group()
|
||||
// List Section Header
|
||||
.child(self.list_section_header("#CRDB", true, theme))
|
||||
// List Item Large
|
||||
.child(self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CHANNELS", true, theme)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CONTACTS", true, theme))
|
||||
.children(
|
||||
std::iter::repeat_with(|| {
|
||||
vec![
|
||||
self.list_item(
|
||||
"http://github.com/as-cii.png?s=50",
|
||||
"as-cii",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/nathansobo.png?s=50",
|
||||
"nathansobo",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
),
|
||||
]
|
||||
})
|
||||
.take(10)
|
||||
.flatten(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.border_t()
|
||||
.border_color(theme.middle.variant.default.border)
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.middle.variant.default.foreground)
|
||||
.child("Find..."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_section_header(
|
||||
&self,
|
||||
label: impl IntoElement<V>,
|
||||
expanded: bool,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
svg()
|
||||
.path(if expanded {
|
||||
"icons/radix/caret-down.svg"
|
||||
} else {
|
||||
"icons/radix/caret-up.svg"
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_item(
|
||||
&self,
|
||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
||||
label: impl IntoElement<V>,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.variant.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.variant.pressed.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
img()
|
||||
.uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
}
|
97
crates/storybook2/src/components.rs
Normal file
97
crates/storybook2/src/components.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use gpui2::{
|
||||
elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
|
||||
Element, EventContext, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
|
||||
struct ButtonHandlers<V, D> {
|
||||
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
||||
}
|
||||
|
||||
impl<V, D> Default for ButtonHandlers<V, D> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct Button<V: 'static, D: 'static> {
|
||||
handlers: ButtonHandlers<V, D>,
|
||||
label: Option<ArcCow<'static, str>>,
|
||||
icon: Option<ArcCow<'static, str>>,
|
||||
data: Rc<D>,
|
||||
view_type: PhantomData<V>,
|
||||
}
|
||||
|
||||
// Impl block for buttons without data.
|
||||
// See below for an impl block for any button.
|
||||
impl<V: 'static> Button<V, ()> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
handlers: ButtonHandlers::default(),
|
||||
label: None,
|
||||
icon: None,
|
||||
data: Rc::new(()),
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
|
||||
Button {
|
||||
handlers: ButtonHandlers::default(),
|
||||
label: self.label,
|
||||
icon: self.icon,
|
||||
data: Rc::new(data),
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Impl block for button regardless of its data type.
|
||||
impl<V: 'static, D: 'static> Button<V, D> {
|
||||
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.label = Some(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.icon = Some(icon.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers.click = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button<V>() -> Button<V, ()> {
|
||||
Button::new()
|
||||
}
|
||||
|
||||
impl<V: 'static, D: 'static> Button<V, D> {
|
||||
fn render(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> impl IntoElement<V> + Interactive<V> {
|
||||
// let colors = &cx.theme::<Theme>().colors;
|
||||
|
||||
let button = div()
|
||||
// .fill(colors.error(0.5))
|
||||
.h_4()
|
||||
.children(self.label.clone());
|
||||
|
||||
if let Some(handler) = self.handlers.click.clone() {
|
||||
let data = self.data.clone();
|
||||
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
|
||||
handler(view, data.as_ref(), cx)
|
||||
})
|
||||
} else {
|
||||
button
|
||||
}
|
||||
}
|
||||
}
|
22
crates/storybook2/src/element_ext.rs
Normal file
22
crates/storybook2/src/element_ext.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::theme::{Theme, Themed};
|
||||
use gpui3::Element;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait ElementExt: Element {
|
||||
fn themed(self, theme: Theme) -> Themed<V, Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element> ElementExt for E {
|
||||
fn themed(self, theme: Theme) -> Themed<V, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Themed {
|
||||
child: self,
|
||||
theme,
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
108
crates/storybook2/src/storybook2.rs
Normal file
108
crates/storybook2/src/storybook2.rs
Normal file
@ -0,0 +1,108 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use crate::theme::Theme;
|
||||
use ::theme as legacy_theme;
|
||||
use element_ext::ElementExt;
|
||||
use gpui3::{Element, ViewContext};
|
||||
use legacy_theme::ThemeSettings;
|
||||
use log::LevelFilter;
|
||||
use simplelog::SimpleLogger;
|
||||
|
||||
mod collab_panel;
|
||||
// mod components;
|
||||
mod element_ext;
|
||||
mod theme;
|
||||
mod workspace;
|
||||
|
||||
// gpui2::actions! {
|
||||
// storybook,
|
||||
// [ToggleInspector]
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
gpui3::App::new().run(|cx| cx.open_window(Default::default(), |cx| todo!()));
|
||||
|
||||
// gpui3::App::new(Assets).unwrap().run(|cx| {
|
||||
// let mut store = SettingsStore::default();
|
||||
// store
|
||||
// .set_default_settings(default_settings().as_ref(), cx)
|
||||
// .unwrap();
|
||||
// cx.set_global(store);
|
||||
// legacy_theme::init(Assets, cx);
|
||||
// // load_embedded_fonts(cx.platform().as_ref());
|
||||
|
||||
// cx.add_window(
|
||||
// gpui2::WindowOptions {
|
||||
// bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
|
||||
// center: true,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// |cx| {
|
||||
// view(|cx| {
|
||||
// cx.enable_inspector();
|
||||
// storybook(&mut ViewContext::new(cx))
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
// cx.platform().activate(true);
|
||||
// });
|
||||
}
|
||||
|
||||
fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element {
|
||||
workspace().themed(current_theme(cx))
|
||||
}
|
||||
|
||||
// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
|
||||
fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
|
||||
settings::get::<ThemeSettings>(cx)
|
||||
.theme
|
||||
.deserialized_base_theme
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let theme: Theme =
|
||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
.unwrap();
|
||||
Box::new(theme)
|
||||
})
|
||||
.downcast_ref::<Theme>()
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
use workspace::workspace;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "themes/**/*"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
||||
// impl AssetSource for Assets {
|
||||
// fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||
// Self::get(path)
|
||||
// .map(|f| f.data)
|
||||
// .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
// }
|
||||
|
||||
// fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
|
||||
// Self::iter().filter(|p| p.starts_with(path)).collect()
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
|
||||
// let font_paths = Assets.list("fonts");
|
||||
// let mut embedded_fonts = Vec::new();
|
||||
// for font_path in &font_paths {
|
||||
// if font_path.ends_with(".ttf") {
|
||||
// let font_path = &*font_path;
|
||||
// let font_bytes = Assets.load(font_path).unwrap().to_vec();
|
||||
// embedded_fonts.push(Arc::from(font_bytes));
|
||||
// }
|
||||
// }
|
||||
// platform.fonts().add_fonts(&embedded_fonts).unwrap();
|
||||
// }
|
190
crates/storybook2/src/theme.rs
Normal file
190
crates/storybook2/src/theme.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use gpui3::{
|
||||
serde_json, AppContext, Element, Hsla, IntoAnyElement, Layout, Vector2F, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use std::{collections::HashMap, fmt, marker::PhantomData};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Theme {
|
||||
pub name: String,
|
||||
pub is_light: bool,
|
||||
pub lowest: Layer,
|
||||
pub middle: Layer,
|
||||
pub highest: Layer,
|
||||
pub popover_shadow: Shadow,
|
||||
pub modal_shadow: Shadow,
|
||||
#[serde(deserialize_with = "deserialize_player_colors")]
|
||||
pub players: Vec<PlayerColors>,
|
||||
#[serde(deserialize_with = "deserialize_syntax_colors")]
|
||||
pub syntax: HashMap<String, Hsla>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Layer {
|
||||
pub base: StyleSet,
|
||||
pub variant: StyleSet,
|
||||
pub on: StyleSet,
|
||||
pub accent: StyleSet,
|
||||
pub positive: StyleSet,
|
||||
pub warning: StyleSet,
|
||||
pub negative: StyleSet,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct StyleSet {
|
||||
#[serde(rename = "default")]
|
||||
pub default: ContainerColors,
|
||||
pub hovered: ContainerColors,
|
||||
pub pressed: ContainerColors,
|
||||
pub active: ContainerColors,
|
||||
pub disabled: ContainerColors,
|
||||
pub inverted: ContainerColors,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct ContainerColors {
|
||||
pub background: Hsla,
|
||||
pub foreground: Hsla,
|
||||
pub border: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct PlayerColors {
|
||||
pub selection: Hsla,
|
||||
pub cursor: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Shadow {
|
||||
pub blur: u8,
|
||||
pub color: Hsla,
|
||||
pub offset: Vec<u8>,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct ColorWrapper {
|
||||
color: Hsla,
|
||||
}
|
||||
|
||||
struct SyntaxVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SyntaxVisitor {
|
||||
type Value = HashMap<String, Hsla>;
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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 wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
|
||||
result.insert(key, wrapper.color);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(SyntaxVisitor)
|
||||
}
|
||||
|
||||
pub struct Themed<V: 'static, E: Element<V>> {
|
||||
pub(crate) theme: Theme,
|
||||
pub(crate) child: E,
|
||||
pub(crate) view_type: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
|
||||
type FrameState = E::FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> anyhow::Result<(gpui2::LayoutId, Self::FrameState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.push_theme(self.theme.clone());
|
||||
let result = self.child.layout(view, cx);
|
||||
cx.pop_theme();
|
||||
result
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
layout: &Layout,
|
||||
state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.push_theme(self.theme.clone());
|
||||
self.child.paint(view, layout, state, cx);
|
||||
cx.pop_theme();
|
||||
}
|
||||
}
|
||||
|
||||
fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
|
||||
settings::get::<ThemeSettings>(cx)
|
||||
.theme
|
||||
.deserialized_base_theme
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let theme: Theme =
|
||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
.unwrap();
|
||||
Box::new(theme)
|
||||
})
|
||||
.downcast_ref::<Theme>()
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
|
||||
cx.theme::<Theme>()
|
||||
}
|
431
crates/storybook2/src/workspace.rs
Normal file
431
crates/storybook2/src/workspace.rs
Normal file
@ -0,0 +1,431 @@
|
||||
use crate::{collab_panel::collab_panel, theme::theme};
|
||||
use gpui3::{div, Element, IntoAnyElement, ParentElement, ScrollState, Styled, ViewContext};
|
||||
|
||||
#[derive(Element, Default)]
|
||||
struct WorkspaceElement {
|
||||
left_scroll_state: ScrollState,
|
||||
right_scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
pub fn workspace<V: 'static>() -> impl Element<V> {
|
||||
WorkspaceElement::default()
|
||||
}
|
||||
|
||||
impl WorkspaceElement {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.gap_0()
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(titlebar())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.overflow_hidden()
|
||||
.child(collab_panel(self.left_scroll_state.clone()))
|
||||
.child(div().h_full().flex_1())
|
||||
.child(collab_panel(self.right_scroll_state.clone())),
|
||||
)
|
||||
.child(statusbar())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element)]
|
||||
struct TitleBar;
|
||||
|
||||
pub fn titlebar<V: 'static>() -> impl Element<V> {
|
||||
TitleBar
|
||||
}
|
||||
|
||||
impl TitleBar {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Traffic Lights === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.positive.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.warning.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
),
|
||||
)
|
||||
// === Project Info === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("project")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("branch")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_3()
|
||||
.px_2()
|
||||
// === Actions === //
|
||||
.child(
|
||||
div().child(
|
||||
div().flex().items_center().gap_1().child(
|
||||
div().size_4().flex().items_center().justify_center().child(
|
||||
svg()
|
||||
.path("icons/exit.svg")
|
||||
.size_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// === Comms === //
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/microphone.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/speaker-loud.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/desktop.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// User Group
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.px_1()
|
||||
.py_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.gap_0p5()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
img()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size_4()
|
||||
.rounded_md()
|
||||
.fill(theme.middle.on.default.foreground),
|
||||
)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/caret_down_8.svg")
|
||||
.w_2()
|
||||
.h_2()
|
||||
.fill(theme.lowest.variant.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================ //
|
||||
|
||||
#[derive(Element)]
|
||||
struct StatusBar;
|
||||
|
||||
pub fn statusbar<V: 'static>() -> impl Element<V> {
|
||||
StatusBar
|
||||
}
|
||||
|
||||
impl StatusBar {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/project.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/conversations.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/file_icons/notebook.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
// === Diagnostics === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.gap_0p5()
|
||||
.px_1()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/error.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
)
|
||||
.child(div().text_sm().child("2")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.child("Something is wrong"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/check_circle.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/copilot.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user