Merge branch 'main' into editor2-rendering
This commit is contained in:
commit
c59817cd72
90 changed files with 4097 additions and 1476 deletions
|
@ -1,93 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{Icon, IconButton, Label, Panel, PanelSide};
|
||||
use gpui2::{rems, AbsoluteLength};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct AssistantPanel {
|
||||
id: ElementId,
|
||||
current_side: PanelSide,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
current_side: PanelSide::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn side(mut self, side: PanelSide) -> Self {
|
||||
self.current_side = side;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
Panel::new(self.id.clone(), cx)
|
||||
.children(vec![div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.h_full()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
// Header
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.child(IconButton::new("menu", Icon::Menu))
|
||||
.child(Label::new("New Conversation")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(IconButton::new("split_message", Icon::SplitMessage))
|
||||
.child(IconButton::new("quote", Icon::Quote))
|
||||
.child(IconButton::new("magic_wand", Icon::MagicWand))
|
||||
.child(IconButton::new("plus", Icon::Plus))
|
||||
.child(IconButton::new("maximize", Icon::Maximize)),
|
||||
),
|
||||
)
|
||||
// Chat Body
|
||||
.child(
|
||||
div()
|
||||
.id("chat-body")
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.overflow_y_scroll()
|
||||
.child(Label::new("Is this thing on?")),
|
||||
)
|
||||
.render()])
|
||||
.side(self.current_side)
|
||||
.width(AbsoluteLength::Rems(rems(32.)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
pub struct AssistantPanelStory;
|
||||
|
||||
impl Render for AssistantPanelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, AssistantPanel>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(AssistantPanel::new("assistant-panel"))
|
||||
}
|
||||
}
|
||||
}
|
83
crates/ui2/src/components/avatar.rs
Normal file
83
crates/ui2/src/components/avatar.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use gpui2::img;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Avatar {
|
||||
src: SharedString,
|
||||
shape: Shape,
|
||||
}
|
||||
|
||||
impl Avatar {
|
||||
pub fn new(src: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
src: src.into(),
|
||||
shape: Shape::Circle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shape(mut self, shape: Shape) -> Self {
|
||||
self.shape = shape;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let mut img = img();
|
||||
|
||||
if self.shape == Shape::Circle {
|
||||
img = img.rounded_full();
|
||||
} else {
|
||||
img = img.rounded_md();
|
||||
}
|
||||
|
||||
img.uri(self.src.clone())
|
||||
.size_4()
|
||||
// todo!(Pull the avatar fallback background from the theme.)
|
||||
.bg(gpui2::red())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct AvatarStory;
|
||||
|
||||
impl Render for AvatarStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Avatar>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Avatar::new(
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
))
|
||||
.child(Avatar::new(
|
||||
"https://avatars.githubusercontent.com/u/326587?v=4",
|
||||
))
|
||||
// .child(Avatar::new(
|
||||
// "https://avatars.githubusercontent.com/u/326587?v=4",
|
||||
// ))
|
||||
// .child(Avatar::new(
|
||||
// "https://avatars.githubusercontent.com/u/482957?v=4",
|
||||
// ))
|
||||
// .child(Avatar::new(
|
||||
// "https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
// ))
|
||||
// .child(Avatar::new(
|
||||
// "https://avatars.githubusercontent.com/u/1486634?v=4",
|
||||
// ))
|
||||
.child(Story::label(cx, "Rounded rectangle"))
|
||||
// .child(
|
||||
// Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
// .shape(Shape::RoundedRectangle),
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, HighlightedText};
|
||||
use gpui2::Div;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Symbol(pub Vec<HighlightedText>);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Breadcrumb {
|
||||
path: PathBuf,
|
||||
symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl Breadcrumb {
|
||||
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
|
||||
Self { path, symbols }
|
||||
}
|
||||
|
||||
fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
|
||||
div()
|
||||
.child(" › ")
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let symbols_len = self.symbols.len();
|
||||
|
||||
h_stack()
|
||||
.id("breadcrumb")
|
||||
.px_1()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.rounded_md()
|
||||
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
|
||||
.child(self.path.clone().to_str().unwrap().to_string())
|
||||
.child(if !self.symbols.is_empty() {
|
||||
self.render_separator(cx)
|
||||
} else {
|
||||
div()
|
||||
})
|
||||
.child(
|
||||
div().flex().children(
|
||||
self.symbols
|
||||
.iter()
|
||||
.enumerate()
|
||||
// TODO: Could use something like `intersperse` here instead.
|
||||
.flat_map(|(ix, symbol)| {
|
||||
let mut items =
|
||||
vec![div().flex().children(symbol.0.iter().map(|segment| {
|
||||
div().child(segment.text.clone()).text_color(segment.color)
|
||||
}))];
|
||||
|
||||
let is_last_segment = ix == symbols_len - 1;
|
||||
if !is_last_segment {
|
||||
items.push(self.render_separator(cx));
|
||||
}
|
||||
|
||||
items
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::Render;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct BreadcrumbStory;
|
||||
|
||||
impl Render for BreadcrumbStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Breadcrumb>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Breadcrumb::new(
|
||||
PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
|
||||
vec![
|
||||
Symbol(vec![
|
||||
HighlightedText {
|
||||
text: "impl ".to_string(),
|
||||
color: cx.theme().syntax_color("keyword"),
|
||||
},
|
||||
HighlightedText {
|
||||
text: "BreadcrumbStory".to_string(),
|
||||
color: cx.theme().syntax_color("function"),
|
||||
},
|
||||
]),
|
||||
Symbol(vec![
|
||||
HighlightedText {
|
||||
text: "fn ".to_string(),
|
||||
color: cx.theme().syntax_color("keyword"),
|
||||
},
|
||||
HighlightedText {
|
||||
text: "render".to_string(),
|
||||
color: cx.theme().syntax_color("function"),
|
||||
},
|
||||
]),
|
||||
],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
use gpui2::{Hsla, WindowContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, v_stack, Icon, IconElement};
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub struct PlayerCursor {
|
||||
color: Hsla,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct HighlightedText {
|
||||
pub text: String,
|
||||
pub color: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct HighlightedLine {
|
||||
pub highlighted_texts: Vec<HighlightedText>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct BufferRow {
|
||||
pub line_number: usize,
|
||||
pub code_action: bool,
|
||||
pub current: bool,
|
||||
pub line: Option<HighlightedLine>,
|
||||
pub cursors: Option<Vec<PlayerCursor>>,
|
||||
pub status: GitStatus,
|
||||
pub show_line_number: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BufferRows {
|
||||
pub show_line_numbers: bool,
|
||||
pub rows: Vec<BufferRow>,
|
||||
}
|
||||
|
||||
impl Default for BufferRows {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_line_numbers: true,
|
||||
rows: vec![BufferRow {
|
||||
line_number: 1,
|
||||
code_action: false,
|
||||
current: true,
|
||||
line: None,
|
||||
cursors: None,
|
||||
status: GitStatus::None,
|
||||
show_line_number: true,
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferRow {
|
||||
pub fn new(line_number: usize) -> Self {
|
||||
Self {
|
||||
line_number,
|
||||
code_action: false,
|
||||
current: false,
|
||||
line: None,
|
||||
cursors: None,
|
||||
status: GitStatus::None,
|
||||
show_line_number: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_line(mut self, line: Option<HighlightedLine>) -> Self {
|
||||
self.line = line;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_cursors(mut self, cursors: Option<Vec<PlayerCursor>>) -> Self {
|
||||
self.cursors = cursors;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self {
|
||||
if let Some(cursors) = &mut self.cursors {
|
||||
cursors.push(cursor);
|
||||
} else {
|
||||
self.cursors = Some(vec![cursor]);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_status(mut self, status: GitStatus) -> Self {
|
||||
self.status = status;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_show_line_number(mut self, show_line_number: bool) -> Self {
|
||||
self.show_line_number = show_line_number;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_code_action(mut self, code_action: bool) -> Self {
|
||||
self.code_action = code_action;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_current(mut self, current: bool) -> Self {
|
||||
self.current = current;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
pub struct Buffer {
|
||||
id: ElementId,
|
||||
rows: Option<BufferRows>,
|
||||
readonly: bool,
|
||||
language: Option<String>,
|
||||
title: Option<String>,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
rows: Some(BufferRows::default()),
|
||||
readonly: false,
|
||||
language: None,
|
||||
title: Some("untitled".to_string()),
|
||||
path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
|
||||
self.title = title.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_path<P: Into<Option<String>>>(mut self, path: P) -> Self {
|
||||
self.path = path.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_readonly(mut self, readonly: bool) -> Self {
|
||||
self.readonly = readonly;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_rows<R: Into<Option<BufferRows>>>(mut self, rows: R) -> Self {
|
||||
self.rows = rows.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_language<L: Into<Option<String>>>(mut self, language: L) -> Self {
|
||||
self.language = language.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> {
|
||||
let line_background = if row.current {
|
||||
cx.theme().colors().editor_active_line
|
||||
} else {
|
||||
cx.theme().styles.system.transparent
|
||||
};
|
||||
|
||||
let line_number_color = if row.current {
|
||||
cx.theme().colors().text
|
||||
} else {
|
||||
cx.theme().syntax_color("comment")
|
||||
};
|
||||
|
||||
h_stack()
|
||||
.bg(line_background)
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.px_1()
|
||||
.child(
|
||||
h_stack()
|
||||
.w_4()
|
||||
.h_full()
|
||||
.px_0p5()
|
||||
.when(row.code_action, |c| {
|
||||
div().child(IconElement::new(Icon::Bolt))
|
||||
}),
|
||||
)
|
||||
.when(row.show_line_number, |this| {
|
||||
this.child(
|
||||
h_stack().justify_end().px_0p5().w_3().child(
|
||||
div()
|
||||
.text_color(line_number_color)
|
||||
.child(row.line_number.to_string()),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
|
||||
.children(row.line.map(|line| {
|
||||
div()
|
||||
.flex()
|
||||
.children(line.highlighted_texts.iter().map(|highlighted_text| {
|
||||
div()
|
||||
.text_color(highlighted_text.color)
|
||||
.child(highlighted_text.text.clone())
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<V>> {
|
||||
match &self.rows {
|
||||
Some(rows) => rows
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| Self::render_row(row.clone(), cx))
|
||||
.collect(),
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let rows = self.render_rows(cx);
|
||||
|
||||
v_stack()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.bg(cx.theme().colors().editor)
|
||||
.children(rows)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{
|
||||
empty_buffer_example, hello_world_rust_buffer_example,
|
||||
hello_world_rust_buffer_with_status_example, Story,
|
||||
};
|
||||
use gpui2::{rems, Div, Render};
|
||||
|
||||
pub struct BufferStory;
|
||||
|
||||
impl Render for BufferStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Buffer>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
|
||||
.child(Story::label(cx, "Hello World (Rust)"))
|
||||
.child(
|
||||
div()
|
||||
.w(rems(64.))
|
||||
.h_96()
|
||||
.child(hello_world_rust_buffer_example(cx)),
|
||||
)
|
||||
.child(Story::label(cx, "Hello World (Rust) with Status"))
|
||||
.child(
|
||||
div()
|
||||
.w(rems(64.))
|
||||
.h_96()
|
||||
.child(hello_world_rust_buffer_with_status_example(cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
use gpui2::{Div, Render, View, VisualContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, Icon, IconButton, IconColor, Input};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BufferSearch {
|
||||
is_replace_open: bool,
|
||||
}
|
||||
|
||||
impl BufferSearch {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_replace_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_replace(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.is_replace_open = !self.is_replace_open;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BufferSearch {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
|
||||
h_stack().bg(cx.theme().colors().toolbar).p_2().child(
|
||||
h_stack().child(Input::new("Search")).child(
|
||||
IconButton::<Self>::new("replace", Icon::Replace)
|
||||
.when(self.is_replace_open, |this| this.color(IconColor::Accent))
|
||||
.on_click(|buffer_search, cx| {
|
||||
buffer_search.toggle_replace(cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
396
crates/ui2/src/components/button.rs
Normal file
396
crates/ui2/src/components/button.rs
Normal file
|
@ -0,0 +1,396 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext};
|
||||
|
||||
use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle};
|
||||
use crate::{prelude::*, IconButton};
|
||||
|
||||
/// Provides the flexibility to use either a standard
|
||||
/// button or an icon button in a given context.
|
||||
pub enum ButtonOrIconButton<V: 'static> {
|
||||
Button(Button<V>),
|
||||
IconButton(IconButton<V>),
|
||||
}
|
||||
|
||||
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: Button<V>) -> Self {
|
||||
Self::Button(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: IconButton<V>) -> Self {
|
||||
Self::IconButton(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum IconPosition {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub enum ButtonVariant {
|
||||
#[default]
|
||||
Ghost,
|
||||
Filled,
|
||||
}
|
||||
|
||||
impl ButtonVariant {
|
||||
pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_background,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_hover,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_active,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_active,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClickHandler<S> = Arc<dyn Fn(&mut S, &mut ViewContext<S>) + Send + Sync>;
|
||||
|
||||
struct ButtonHandlers<V: 'static> {
|
||||
click: Option<ClickHandler<V>>,
|
||||
}
|
||||
|
||||
unsafe impl<S> Send for ButtonHandlers<S> {}
|
||||
unsafe impl<S> Sync for ButtonHandlers<S> {}
|
||||
|
||||
impl<V: 'static> Default for ButtonHandlers<V> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Button<V: 'static> {
|
||||
disabled: bool,
|
||||
handlers: ButtonHandlers<V>,
|
||||
icon: Option<Icon>,
|
||||
icon_position: Option<IconPosition>,
|
||||
label: SharedString,
|
||||
variant: ButtonVariant,
|
||||
width: Option<DefiniteLength>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Button<V> {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
disabled: false,
|
||||
handlers: ButtonHandlers::default(),
|
||||
icon: None,
|
||||
icon_position: None,
|
||||
label: label.into(),
|
||||
variant: Default::default(),
|
||||
width: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ghost(label: impl Into<SharedString>) -> Self {
|
||||
Self::new(label).variant(ButtonVariant::Ghost)
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
|
||||
if self.icon.is_none() {
|
||||
panic!("An icon must be present if an icon_position is provided.");
|
||||
}
|
||||
self.icon_position = Some(icon_position);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
|
||||
self.handlers.click = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
fn label_color(&self) -> LabelColor {
|
||||
if self.disabled {
|
||||
LabelColor::Disabled
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> IconColor {
|
||||
if self.disabled {
|
||||
IconColor::Disabled
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_label(&self) -> Label {
|
||||
Label::new(self.label.clone())
|
||||
.color(self.label_color())
|
||||
.line_height_style(LineHeightStyle::UILabel)
|
||||
}
|
||||
|
||||
fn render_icon(&self, icon_color: IconColor) -> Option<IconElement> {
|
||||
self.icon.map(|i| IconElement::new(i).color(icon_color))
|
||||
}
|
||||
|
||||
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let icon_color = self.icon_color();
|
||||
|
||||
let mut button = h_stack()
|
||||
.relative()
|
||||
.id(SharedString::from(format!("{}", self.label)))
|
||||
.p_1()
|
||||
.text_size(rems(1.))
|
||||
.rounded_md()
|
||||
.bg(self.variant.bg_color(cx))
|
||||
.hover(|style| style.bg(self.variant.bg_color_hover(cx)))
|
||||
.active(|style| style.bg(self.variant.bg_color_active(cx)));
|
||||
|
||||
match (self.icon, self.icon_position) {
|
||||
(Some(_), Some(IconPosition::Left)) => {
|
||||
button = button
|
||||
.gap_1()
|
||||
.child(self.render_label())
|
||||
.children(self.render_icon(icon_color))
|
||||
}
|
||||
(Some(_), Some(IconPosition::Right)) => {
|
||||
button = button
|
||||
.gap_1()
|
||||
.children(self.render_icon(icon_color))
|
||||
.child(self.render_label())
|
||||
}
|
||||
(_, _) => button = button.child(self.render_label()),
|
||||
}
|
||||
|
||||
if let Some(width) = self.width {
|
||||
button = button.w(width).justify_center();
|
||||
}
|
||||
|
||||
if let Some(click_handler) = self.handlers.click.clone() {
|
||||
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
|
||||
click_handler(state, cx);
|
||||
});
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ButtonGroup<V: 'static> {
|
||||
buttons: Vec<Button<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> ButtonGroup<V> {
|
||||
pub fn new(buttons: Vec<Button<V>>) -> Self {
|
||||
Self { buttons }
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let mut el = h_stack().text_size(rems(1.));
|
||||
|
||||
for button in self.buttons {
|
||||
el = el.child(button.render(_view, cx));
|
||||
}
|
||||
|
||||
el
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{h_stack, v_stack, LabelColor, Story};
|
||||
use gpui2::{rems, Div, Render};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub struct ButtonStory;
|
||||
|
||||
impl Render for ButtonStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let states = InteractionState::iter();
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Button<Self>>(cx))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_8()
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Ghost (Default)"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Ghost – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Ghost – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right), // .state(state),
|
||||
)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Filled"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Filled – Left Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Filled – Right Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right), // .state(state),
|
||||
)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Fixed With"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Fixed With – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Fixed With – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string()).color(LabelColor::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
}))),
|
||||
),
|
||||
)
|
||||
.child(Story::label(cx, "Button with `on_click`"))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(Arc::new(|_view, _cx| println!("Button clicked."))),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
use chrono::NaiveDateTime;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Icon, IconButton, Input, Label, LabelColor};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ChatPanel {
|
||||
element_id: ElementId,
|
||||
messages: Vec<ChatMessage>,
|
||||
}
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(element_id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
element_id: element_id.into(),
|
||||
messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
|
||||
self.messages = messages;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.id(self.element_id.clone())
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_between()
|
||||
.h_full()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
// Header
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.py_2()
|
||||
.child(div().flex().child(Label::new("#design")))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(IconButton::new("file", Icon::File))
|
||||
.child(IconButton::new("audio_on", Icon::AudioOn)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
// Chat Body
|
||||
.child(
|
||||
div()
|
||||
.id("chat-body")
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.overflow_y_scroll()
|
||||
.children(self.messages),
|
||||
)
|
||||
// Composer
|
||||
.child(div().flex().my_2().child(Input::new("Message #design"))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ChatMessage {
|
||||
author: String,
|
||||
text: String,
|
||||
sent_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl ChatMessage {
|
||||
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
|
||||
Self {
|
||||
author,
|
||||
text,
|
||||
sent_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(Label::new(self.author.clone()))
|
||||
.child(
|
||||
Label::new(self.sent_at.format("%m/%d/%Y").to_string())
|
||||
.color(LabelColor::Muted),
|
||||
),
|
||||
)
|
||||
.child(div().child(Label::new(self.text.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use chrono::DateTime;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::{Panel, Story};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ChatPanelStory;
|
||||
|
||||
impl Render for ChatPanelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ChatPanel>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
Panel::new("chat-panel-1-outer", cx)
|
||||
.child(ChatPanel::new("chat-panel-1-inner")),
|
||||
)
|
||||
.child(Story::label(cx, "With Mesages"))
|
||||
.child(Panel::new("chat-panel-2-outer", cx).child(
|
||||
ChatPanel::new("chat-panel-2-inner").messages(vec![
|
||||
ChatMessage::new(
|
||||
"osiewicz".to_string(),
|
||||
"is this thing on?".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
ChatMessage::new(
|
||||
"maxdeviant".to_string(),
|
||||
"Reading you loud and clear!".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
]),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
220
crates/ui2/src/components/checkbox.rs
Normal file
220
crates/ui2/src/components/checkbox.rs
Normal file
|
@ -0,0 +1,220 @@
|
|||
///! # Checkbox
|
||||
///!
|
||||
///! Checkboxes are used for multiple choices, not for mutually exclusive choices.
|
||||
///! Each checkbox works independently from other checkboxes in the list,
|
||||
///! therefore checking an additional box does not affect any other selections.
|
||||
use gpui2::{
|
||||
div, Component, ParentElement, SharedString, StatelessInteractive, Styled, ViewContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
|
||||
use crate::{Icon, IconColor, IconElement, Selected};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Checkbox {
|
||||
id: SharedString,
|
||||
checked: Selected,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl Checkbox {
|
||||
pub fn new(id: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
checked: Selected::Unselected,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(mut self) -> Self {
|
||||
self.checked = match self.checked {
|
||||
Selected::Selected => Selected::Unselected,
|
||||
Selected::Unselected => Selected::Selected,
|
||||
Selected::Indeterminate => Selected::Selected,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_indeterminate(mut self) -> Self {
|
||||
self.checked = Selected::Indeterminate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let group_id = format!("checkbox_group_{}", self.id);
|
||||
|
||||
// The icon is different depending on the state of the checkbox.
|
||||
//
|
||||
// We need the match to return all the same type,
|
||||
// so we wrap the eatch result in a div.
|
||||
//
|
||||
// We are still exploring the best way to handle this.
|
||||
let icon = match self.checked {
|
||||
// When selected, we show a checkmark.
|
||||
Selected::Selected => {
|
||||
div().child(
|
||||
IconElement::new(Icon::Check)
|
||||
.size(crate::IconSize::Small)
|
||||
.color(
|
||||
// If the checkbox is disabled we change the color of the icon.
|
||||
if self.disabled {
|
||||
IconColor::Disabled
|
||||
} else {
|
||||
IconColor::Selected
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
// In an indeterminate state, we show a dash.
|
||||
Selected::Indeterminate => {
|
||||
div().child(
|
||||
IconElement::new(Icon::Dash)
|
||||
.size(crate::IconSize::Small)
|
||||
.color(
|
||||
// If the checkbox is disabled we change the color of the icon.
|
||||
if self.disabled {
|
||||
IconColor::Disabled
|
||||
} else {
|
||||
IconColor::Selected
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
// When unselected, we show nothing.
|
||||
Selected::Unselected => div(),
|
||||
};
|
||||
|
||||
// A checkbox could be in an indeterminate state,
|
||||
// for example the indeterminate state could represent:
|
||||
// - a group of options of which only some are selected
|
||||
// - an enabled option that is no longer available
|
||||
// - a previously agreed to license that has been updated
|
||||
//
|
||||
// For the sake of styles we treat the indeterminate state as selected,
|
||||
// but it's icon will be different.
|
||||
let selected =
|
||||
self.checked == Selected::Selected || self.checked == Selected::Indeterminate;
|
||||
|
||||
// We could use something like this to make the checkbox background when selected:
|
||||
//
|
||||
// ~~~rust
|
||||
// ...
|
||||
// .when(selected, |this| {
|
||||
// this.bg(cx.theme().colors().element_selected)
|
||||
// })
|
||||
// ~~~
|
||||
//
|
||||
// But we use a match instead here because the checkbox might be disabled,
|
||||
// and it could be disabled _while_ it is selected, as well as while it is not selected.
|
||||
let (bg_color, border_color) = match (self.disabled, selected) {
|
||||
(true, _) => (
|
||||
cx.theme().colors().ghost_element_disabled,
|
||||
cx.theme().colors().border_disabled,
|
||||
),
|
||||
(false, true) => (
|
||||
cx.theme().colors().element_selected,
|
||||
cx.theme().colors().border,
|
||||
),
|
||||
(false, false) => (
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().border,
|
||||
),
|
||||
};
|
||||
|
||||
div()
|
||||
// Rather than adding `px_1()` to add some space around the checkbox,
|
||||
// we use a larger parent element to create a slightly larger
|
||||
// click area for the checkbox.
|
||||
.size_5()
|
||||
// Because we've enlarged the click area, we need to create a
|
||||
// `group` to pass down interaction events to the checkbox.
|
||||
.group(group_id.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
// This prevent the flex element from growing
|
||||
// or shrinking in response to any size changes
|
||||
.flex_none()
|
||||
// The combo of `justify_center()` and `items_center()`
|
||||
// is used frequently to center elements in a flex container.
|
||||
//
|
||||
// We use this to center the icon in the checkbox.
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.m_1()
|
||||
.size_4()
|
||||
.rounded_sm()
|
||||
.bg(bg_color)
|
||||
.border()
|
||||
.border_color(border_color)
|
||||
// We only want the interaction states to fire when we
|
||||
// are in a checkbox that isn't disabled.
|
||||
.when(!self.disabled, |this| {
|
||||
// Here instead of `hover()` we use `group_hover()`
|
||||
// to pass it the group id.
|
||||
this.group_hover(group_id.clone(), |el| {
|
||||
el.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
})
|
||||
.child(icon),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{h_stack, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct CheckboxStory;
|
||||
|
||||
impl Render for CheckboxStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Checkbox>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
h_stack()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Checkbox::new("checkbox-enabled"))
|
||||
.child(Checkbox::new("checkbox-intermediate").set_indeterminate())
|
||||
.child(Checkbox::new("checkbox-selected").toggle()),
|
||||
)
|
||||
.child(Story::label(cx, "Disabled"))
|
||||
.child(
|
||||
h_stack()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Checkbox::new("checkbox-disabled").set_disabled(true))
|
||||
.child(
|
||||
Checkbox::new("checkbox-disabled-intermediate")
|
||||
.set_disabled(true)
|
||||
.set_indeterminate(),
|
||||
)
|
||||
.child(
|
||||
Checkbox::new("checkbox-disabled-selected")
|
||||
.set_disabled(true)
|
||||
.toggle(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{
|
||||
static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List,
|
||||
ListHeader, ToggleState,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CollabPanel {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl CollabPanel {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
v_stack()
|
||||
.id(self.id.clone())
|
||||
.h_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.child(
|
||||
v_stack()
|
||||
.id("crdb")
|
||||
.w_full()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
div()
|
||||
.pb_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.border_b()
|
||||
.child(
|
||||
List::new(static_collab_panel_current_call())
|
||||
.header(
|
||||
ListHeader::new("CRDB")
|
||||
.left_icon(Icon::Hash.into())
|
||||
.toggle(ToggleState::Toggled),
|
||||
)
|
||||
.toggle(ToggleState::Toggled),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_stack().id("channels").py_1().child(
|
||||
List::new(static_collab_panel_channels())
|
||||
.header(ListHeader::new("CHANNELS").toggle(ToggleState::Toggled))
|
||||
.empty_message("No channels yet. Add a channel to get started.")
|
||||
.toggle(ToggleState::Toggled),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_stack().id("contacts-online").py_1().child(
|
||||
List::new(static_collab_panel_current_call())
|
||||
.header(
|
||||
ListHeader::new("CONTACTS – ONLINE")
|
||||
.toggle(ToggleState::Toggled),
|
||||
)
|
||||
.toggle(ToggleState::Toggled),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_stack().id("contacts-offline").py_1().child(
|
||||
List::new(static_collab_panel_current_call())
|
||||
.header(
|
||||
ListHeader::new("CONTACTS – OFFLINE")
|
||||
.toggle(ToggleState::NotToggled),
|
||||
)
|
||||
.toggle(ToggleState::NotToggled),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.border_t()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_placeholder)
|
||||
.child("Find..."),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct CollabPanelStory;
|
||||
|
||||
impl Render for CollabPanelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, CollabPanel>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(CollabPanel::new("collab-panel"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{example_editor_actions, OrderMethod, Palette};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CommandPalette {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl CommandPalette {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().id(self.id.clone()).child(
|
||||
Palette::new("palette")
|
||||
.items(example_editor_actions())
|
||||
.placeholder("Execute a command...")
|
||||
.empty_string("No items found.")
|
||||
.default_order(OrderMethod::Ascending),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::Story;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct CommandPaletteStory;
|
||||
|
||||
impl Render for CommandPaletteStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, CommandPalette>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(CommandPalette::new("command-palette"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ pub enum ContextMenuItem {
|
|||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
fn to_list_item<V: 'static>(self) -> ListItem<V> {
|
||||
fn to_list_item<V: 'static>(self) -> ListItem {
|
||||
match self {
|
||||
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
|
||||
ContextMenuItem::Entry(label) => {
|
||||
|
@ -46,18 +46,15 @@ impl ContextMenu {
|
|||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
v_stack()
|
||||
.flex()
|
||||
.bg(cx.theme().colors().elevated_surface)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
List::new(
|
||||
self.items
|
||||
.into_iter()
|
||||
.map(ContextMenuItem::to_list_item)
|
||||
.collect(),
|
||||
)
|
||||
.toggle(ToggleState::Toggled),
|
||||
)
|
||||
.child(List::new(
|
||||
self.items
|
||||
.into_iter()
|
||||
.map(ContextMenuItem::to_list_item::<V>)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
use crate::{prelude::*, Button, Label, LabelColor, Modal};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CopilotModal {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl CopilotModal {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().id(self.id.clone()).child(
|
||||
Modal::new("some-id")
|
||||
.title("Connect Copilot to Zed")
|
||||
.child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(LabelColor::Muted))
|
||||
.primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::Story;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct CopilotModalStory;
|
||||
|
||||
impl Render for CopilotModalStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, CopilotModal>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(CopilotModal::new("copilot-modal"))
|
||||
}
|
||||
}
|
||||
}
|
78
crates/ui2/src/components/details.rs
Normal file
78
crates/ui2/src/components/details.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{v_stack, ButtonGroup};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Details<V: 'static> {
|
||||
text: &'static str,
|
||||
meta: Option<&'static str>,
|
||||
actions: Option<ButtonGroup<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Details<V> {
|
||||
pub fn new(text: &'static str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
meta: None,
|
||||
actions: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn meta_text(mut self, meta: &'static str) -> Self {
|
||||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn actions(mut self, actions: ButtonGroup<V>) -> Self {
|
||||
self.actions = Some(actions);
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
v_stack()
|
||||
.p_1()
|
||||
.gap_0p5()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.size_full()
|
||||
.child(self.text)
|
||||
.children(self.meta.map(|m| m))
|
||||
.children(self.actions.map(|a| a))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{Button, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct DetailsStory;
|
||||
|
||||
impl Render for DetailsStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Details<Self>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Details::new("The quick brown fox jumps over the lazy dog"))
|
||||
.child(Story::label(cx, "With meta"))
|
||||
.child(
|
||||
Details::new("The quick brown fox jumps over the lazy dog")
|
||||
.meta_text("Sphinx of black quartz, judge my vow."),
|
||||
)
|
||||
.child(Story::label(cx, "With meta and actions"))
|
||||
.child(
|
||||
Details::new("The quick brown fox jumps over the lazy dog")
|
||||
.meta_text("Sphinx of black quartz, judge my vow.")
|
||||
.actions(ButtonGroup::new(vec![
|
||||
Button::new("Decline"),
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
])),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use gpui2::{Div, Render, View, VisualContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
hello_world_rust_editor_with_status_example, v_stack, Breadcrumb, Buffer, BufferSearch, Icon,
|
||||
IconButton, IconColor, Symbol, Tab, TabBar, Toolbar,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EditorPane {
|
||||
tabs: Vec<Tab>,
|
||||
path: PathBuf,
|
||||
symbols: Vec<Symbol>,
|
||||
buffer: Buffer,
|
||||
buffer_search: View<BufferSearch>,
|
||||
is_buffer_search_open: bool,
|
||||
}
|
||||
|
||||
impl EditorPane {
|
||||
pub fn new(
|
||||
cx: &mut ViewContext<Self>,
|
||||
tabs: Vec<Tab>,
|
||||
path: PathBuf,
|
||||
symbols: Vec<Symbol>,
|
||||
buffer: Buffer,
|
||||
) -> Self {
|
||||
Self {
|
||||
tabs,
|
||||
path,
|
||||
symbols,
|
||||
buffer,
|
||||
buffer_search: BufferSearch::view(cx),
|
||||
is_buffer_search_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_buffer_search(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.is_buffer_search_open = !self.is_buffer_search_open;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| hello_world_rust_editor_with_status_example(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for EditorPane {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
|
||||
v_stack()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.child(TabBar::new("editor-pane-tabs", self.tabs.clone()).can_navigate((false, true)))
|
||||
.child(
|
||||
Toolbar::new()
|
||||
.left_item(Breadcrumb::new(self.path.clone(), self.symbols.clone()))
|
||||
.right_items(vec![
|
||||
IconButton::new("toggle_inlay_hints", Icon::InlayHint),
|
||||
IconButton::<Self>::new("buffer_search", Icon::MagnifyingGlass)
|
||||
.when(self.is_buffer_search_open, |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|editor, cx| {
|
||||
editor.toggle_buffer_search(cx);
|
||||
}),
|
||||
IconButton::new("inline_assist", Icon::MagicWand),
|
||||
]),
|
||||
)
|
||||
.children(Some(self.buffer_search.clone()).filter(|_| self.is_buffer_search_open))
|
||||
.child(self.buffer.clone())
|
||||
}
|
||||
}
|
228
crates/ui2/src/components/icon.rs
Normal file
228
crates/ui2/src/components/icon.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
use gpui2::{rems, svg, Hsla};
|
||||
use strum::EnumIter;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum IconSize {
|
||||
Small,
|
||||
#[default]
|
||||
Medium,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum IconColor {
|
||||
#[default]
|
||||
Default,
|
||||
Muted,
|
||||
Disabled,
|
||||
Placeholder,
|
||||
Accent,
|
||||
Error,
|
||||
Warning,
|
||||
Success,
|
||||
Info,
|
||||
Selected,
|
||||
}
|
||||
|
||||
impl IconColor {
|
||||
pub fn color(self, cx: &WindowContext) -> Hsla {
|
||||
match self {
|
||||
IconColor::Default => cx.theme().colors().icon,
|
||||
IconColor::Muted => cx.theme().colors().icon_muted,
|
||||
IconColor::Disabled => cx.theme().colors().icon_disabled,
|
||||
IconColor::Placeholder => cx.theme().colors().icon_placeholder,
|
||||
IconColor::Accent => cx.theme().colors().icon_accent,
|
||||
IconColor::Error => cx.theme().status().error,
|
||||
IconColor::Warning => cx.theme().status().warning,
|
||||
IconColor::Success => cx.theme().status().success,
|
||||
IconColor::Info => cx.theme().status().info,
|
||||
IconColor::Selected => cx.theme().colors().icon_accent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
||||
pub enum Icon {
|
||||
Ai,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUpRight,
|
||||
AudioOff,
|
||||
AudioOn,
|
||||
Bolt,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
Close,
|
||||
Dash,
|
||||
Exit,
|
||||
ExclamationTriangle,
|
||||
File,
|
||||
FileGeneric,
|
||||
FileDoc,
|
||||
FileGit,
|
||||
FileLock,
|
||||
FileRust,
|
||||
FileToml,
|
||||
FileTree,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
FolderX,
|
||||
Hash,
|
||||
InlayHint,
|
||||
MagicWand,
|
||||
MagnifyingGlass,
|
||||
Maximize,
|
||||
Menu,
|
||||
MessageBubbles,
|
||||
Mic,
|
||||
MicMute,
|
||||
Plus,
|
||||
Quote,
|
||||
Replace,
|
||||
ReplaceAll,
|
||||
Screen,
|
||||
SelectAll,
|
||||
Split,
|
||||
SplitMessage,
|
||||
Terminal,
|
||||
XCircle,
|
||||
Copilot,
|
||||
Envelope,
|
||||
Bell,
|
||||
BellOff,
|
||||
BellRing,
|
||||
MailOpen,
|
||||
AtSign,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
pub fn path(self) -> &'static str {
|
||||
match self {
|
||||
Icon::Ai => "icons/ai.svg",
|
||||
Icon::ArrowLeft => "icons/arrow_left.svg",
|
||||
Icon::ArrowRight => "icons/arrow_right.svg",
|
||||
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
||||
Icon::AudioOff => "icons/speaker-off.svg",
|
||||
Icon::AudioOn => "icons/speaker-loud.svg",
|
||||
Icon::Bolt => "icons/bolt.svg",
|
||||
Icon::Check => "icons/check.svg",
|
||||
Icon::ChevronDown => "icons/chevron_down.svg",
|
||||
Icon::ChevronLeft => "icons/chevron_left.svg",
|
||||
Icon::ChevronRight => "icons/chevron_right.svg",
|
||||
Icon::ChevronUp => "icons/chevron_up.svg",
|
||||
Icon::Close => "icons/x.svg",
|
||||
Icon::Dash => "icons/dash.svg",
|
||||
Icon::Exit => "icons/exit.svg",
|
||||
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||
Icon::File => "icons/file.svg",
|
||||
Icon::FileGeneric => "icons/file_icons/file.svg",
|
||||
Icon::FileDoc => "icons/file_icons/book.svg",
|
||||
Icon::FileGit => "icons/file_icons/git.svg",
|
||||
Icon::FileLock => "icons/file_icons/lock.svg",
|
||||
Icon::FileRust => "icons/file_icons/rust.svg",
|
||||
Icon::FileToml => "icons/file_icons/toml.svg",
|
||||
Icon::FileTree => "icons/project.svg",
|
||||
Icon::Folder => "icons/file_icons/folder.svg",
|
||||
Icon::FolderOpen => "icons/file_icons/folder_open.svg",
|
||||
Icon::FolderX => "icons/stop_sharing.svg",
|
||||
Icon::Hash => "icons/hash.svg",
|
||||
Icon::InlayHint => "icons/inlay_hint.svg",
|
||||
Icon::MagicWand => "icons/magic-wand.svg",
|
||||
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
|
||||
Icon::Maximize => "icons/maximize.svg",
|
||||
Icon::Menu => "icons/menu.svg",
|
||||
Icon::MessageBubbles => "icons/conversations.svg",
|
||||
Icon::Mic => "icons/mic.svg",
|
||||
Icon::MicMute => "icons/mic-mute.svg",
|
||||
Icon::Plus => "icons/plus.svg",
|
||||
Icon::Quote => "icons/quote.svg",
|
||||
Icon::Replace => "icons/replace.svg",
|
||||
Icon::ReplaceAll => "icons/replace_all.svg",
|
||||
Icon::Screen => "icons/desktop.svg",
|
||||
Icon::SelectAll => "icons/select-all.svg",
|
||||
Icon::Split => "icons/split.svg",
|
||||
Icon::SplitMessage => "icons/split_message.svg",
|
||||
Icon::Terminal => "icons/terminal.svg",
|
||||
Icon::XCircle => "icons/error.svg",
|
||||
Icon::Copilot => "icons/copilot.svg",
|
||||
Icon::Envelope => "icons/feedback.svg",
|
||||
Icon::Bell => "icons/bell.svg",
|
||||
Icon::BellOff => "icons/bell-off.svg",
|
||||
Icon::BellRing => "icons/bell-ring.svg",
|
||||
Icon::MailOpen => "icons/mail-open.svg",
|
||||
Icon::AtSign => "icons/at-sign.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct IconElement {
|
||||
icon: Icon,
|
||||
color: IconColor,
|
||||
size: IconSize,
|
||||
}
|
||||
|
||||
impl IconElement {
|
||||
pub fn new(icon: Icon) -> Self {
|
||||
Self {
|
||||
icon,
|
||||
color: IconColor::default(),
|
||||
size: IconSize::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: IconColor) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: IconSize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let svg_size = match self.size {
|
||||
IconSize::Small => rems(0.75),
|
||||
IconSize::Medium => rems(0.9375),
|
||||
};
|
||||
|
||||
svg()
|
||||
.size(svg_size)
|
||||
.flex_none()
|
||||
.path(self.icon.path())
|
||||
.text_color(self.color.color(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use gpui2::{Div, Render};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::Story;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct IconStory;
|
||||
|
||||
impl Render for IconStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let icons = Icon::iter();
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, IconElement>(cx))
|
||||
.child(Story::label(cx, "All Icons"))
|
||||
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,12 +73,12 @@ impl<V: 'static> IconButton<V> {
|
|||
|
||||
let (bg_color, bg_hover_color, bg_active_color) = match self.variant {
|
||||
ButtonVariant::Filled => (
|
||||
cx.theme().colors().element,
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
ButtonVariant::Ghost => (
|
||||
cx.theme().colors().ghost_element,
|
||||
cx.theme().colors().ghost_element_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
|
|
23
crates/ui2/src/components/indicator.rs
Normal file
23
crates/ui2/src/components/indicator.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use gpui2::px;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct UnreadIndicator;
|
||||
|
||||
impl UnreadIndicator {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.rounded_full()
|
||||
.border_2()
|
||||
.border_color(cx.theme().colors().surface_background)
|
||||
.w(px(9.0))
|
||||
.h(px(9.0))
|
||||
.z_index(2)
|
||||
.bg(cx.theme().status().info)
|
||||
}
|
||||
}
|
128
crates/ui2/src/components/input.rs
Normal file
128
crates/ui2/src/components/input.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use crate::prelude::*;
|
||||
use crate::Label;
|
||||
use crate::LabelColor;
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum InputVariant {
|
||||
#[default]
|
||||
Ghost,
|
||||
Filled,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Input {
|
||||
placeholder: SharedString,
|
||||
value: String,
|
||||
state: InteractionState,
|
||||
variant: InputVariant,
|
||||
disabled: bool,
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn new(placeholder: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
placeholder: placeholder.into(),
|
||||
value: "".to_string(),
|
||||
state: InteractionState::default(),
|
||||
variant: InputVariant::default(),
|
||||
disabled: false,
|
||||
is_active: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(mut self, value: String) -> Self {
|
||||
self.value = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: InputVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_active(mut self, is_active: bool) -> Self {
|
||||
self.is_active = is_active;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
|
||||
InputVariant::Ghost => (
|
||||
cx.theme().colors().ghost_element_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
InputVariant::Filled => (
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
};
|
||||
|
||||
let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
|
||||
LabelColor::Disabled
|
||||
} else {
|
||||
LabelColor::Placeholder
|
||||
});
|
||||
|
||||
let label = Label::new(self.value.clone()).color(if self.disabled {
|
||||
LabelColor::Disabled
|
||||
} else {
|
||||
LabelColor::Default
|
||||
});
|
||||
|
||||
div()
|
||||
.id("input")
|
||||
.h_7()
|
||||
.w_full()
|
||||
.px_2()
|
||||
.border()
|
||||
.border_color(cx.theme().styles.system.transparent)
|
||||
.bg(input_bg)
|
||||
.hover(|style| style.bg(input_hover_bg))
|
||||
.active(|style| style.bg(input_active_bg))
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(div().flex().items_center().text_sm().map(|this| {
|
||||
if self.value.is_empty() {
|
||||
this.child(placeholder_label)
|
||||
} else {
|
||||
this.child(label)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct InputStory;
|
||||
|
||||
impl Render for InputStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Input>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(div().flex().child(Input::new("Search")))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ impl Key {
|
|||
.rounded_md()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.bg(cx.theme().colors().element)
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.child(self.key.clone())
|
||||
}
|
||||
}
|
||||
|
|
216
crates/ui2/src/components/label.rs
Normal file
216
crates/ui2/src/components/label.rs
Normal file
|
@ -0,0 +1,216 @@
|
|||
use gpui2::{relative, rems, Hsla, WindowContext};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum LabelColor {
|
||||
#[default]
|
||||
Default,
|
||||
Muted,
|
||||
Created,
|
||||
Modified,
|
||||
Deleted,
|
||||
Disabled,
|
||||
Hidden,
|
||||
Placeholder,
|
||||
Accent,
|
||||
}
|
||||
|
||||
impl LabelColor {
|
||||
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
|
||||
match self {
|
||||
Self::Default => cx.theme().colors().text,
|
||||
Self::Muted => cx.theme().colors().text_muted,
|
||||
Self::Created => cx.theme().status().created,
|
||||
Self::Modified => cx.theme().status().modified,
|
||||
Self::Deleted => cx.theme().status().deleted,
|
||||
Self::Disabled => cx.theme().colors().text_disabled,
|
||||
Self::Hidden => cx.theme().status().hidden,
|
||||
Self::Placeholder => cx.theme().colors().text_placeholder,
|
||||
Self::Accent => cx.theme().colors().text_accent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum LineHeightStyle {
|
||||
#[default]
|
||||
TextLabel,
|
||||
/// Sets the line height to 1
|
||||
UILabel,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Label {
|
||||
label: SharedString,
|
||||
line_height_style: LineHeightStyle,
|
||||
color: LabelColor,
|
||||
strikethrough: bool,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
line_height_style: LineHeightStyle::default(),
|
||||
color: LabelColor::Default,
|
||||
strikethrough: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: LabelColor) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
|
||||
self.line_height_style = line_height_style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
|
||||
self.strikethrough = strikethrough;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.when(self.strikethrough, |this| {
|
||||
this.relative().child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_1_2()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.bg(LabelColor::Hidden.hsla(cx)),
|
||||
)
|
||||
})
|
||||
.text_size(rems(1.))
|
||||
.when(self.line_height_style == LineHeightStyle::UILabel, |this| {
|
||||
this.line_height(relative(1.))
|
||||
})
|
||||
.text_color(self.color.hsla(cx))
|
||||
.child(self.label.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct HighlightedLabel {
|
||||
label: SharedString,
|
||||
color: LabelColor,
|
||||
highlight_indices: Vec<usize>,
|
||||
strikethrough: bool,
|
||||
}
|
||||
|
||||
impl HighlightedLabel {
|
||||
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
color: LabelColor::Default,
|
||||
highlight_indices,
|
||||
strikethrough: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: LabelColor) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
|
||||
self.strikethrough = strikethrough;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let highlight_color = cx.theme().colors().text_accent;
|
||||
|
||||
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
|
||||
|
||||
let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
|
||||
|
||||
for (char_ix, char) in self.label.char_indices() {
|
||||
let mut color = self.color.hsla(cx);
|
||||
|
||||
if let Some(highlight_ix) = highlight_indices.peek() {
|
||||
if char_ix == *highlight_ix {
|
||||
color = highlight_color;
|
||||
|
||||
highlight_indices.next();
|
||||
}
|
||||
}
|
||||
|
||||
let last_run = runs.last_mut();
|
||||
|
||||
let start_new_run = if let Some(last_run) = last_run {
|
||||
if color == last_run.color {
|
||||
last_run.text.push(char);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if start_new_run {
|
||||
runs.push(Run {
|
||||
text: char.to_string(),
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.when(self.strikethrough, |this| {
|
||||
this.relative().child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_px()
|
||||
.my_auto()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.bg(LabelColor::Hidden.hsla(cx)),
|
||||
)
|
||||
})
|
||||
.children(
|
||||
runs.into_iter()
|
||||
.map(|run| div().text_color(run.color).child(run.text)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A run of text that receives the same style.
|
||||
struct Run {
|
||||
pub text: String,
|
||||
pub color: Hsla,
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct LabelStory;
|
||||
|
||||
impl Render for LabelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Label>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Label::new("Hello, world!"))
|
||||
.child(Story::label(cx, "Highlighted"))
|
||||
.child(HighlightedLabel::new(
|
||||
"Hello, world!",
|
||||
vec![0, 1, 2, 7, 8, 12],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{OrderMethod, Palette, PaletteItem};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct LanguageSelector {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl LanguageSelector {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().id(self.id.clone()).child(
|
||||
Palette::new("palette")
|
||||
.items(vec![
|
||||
PaletteItem::new("C"),
|
||||
PaletteItem::new("C++"),
|
||||
PaletteItem::new("CSS"),
|
||||
PaletteItem::new("Elixir"),
|
||||
PaletteItem::new("Elm"),
|
||||
PaletteItem::new("ERB"),
|
||||
PaletteItem::new("Rust (current)"),
|
||||
PaletteItem::new("Scheme"),
|
||||
PaletteItem::new("TOML"),
|
||||
PaletteItem::new("TypeScript"),
|
||||
])
|
||||
.placeholder("Select a language...")
|
||||
.empty_string("No matches")
|
||||
.default_order(OrderMethod::Ascending),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct LanguageSelectorStory;
|
||||
|
||||
impl Render for LanguageSelectorStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, LanguageSelector>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(LanguageSelector::new("language-selector"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use gpui2::{div, px, relative, Div};
|
||||
use gpui2::div;
|
||||
|
||||
use crate::settings::user_settings;
|
||||
use crate::{
|
||||
h_stack, v_stack, Avatar, ClickHandler, Icon, IconColor, IconElement, IconSize, Label,
|
||||
LabelColor,
|
||||
disclosure_control, h_stack, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label,
|
||||
LabelColor, Toggle,
|
||||
};
|
||||
use crate::{prelude::*, Button};
|
||||
use crate::{prelude::*, GraphicSlot};
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
||||
pub enum ListItemVariant {
|
||||
|
@ -29,7 +29,7 @@ pub struct ListHeader {
|
|||
left_icon: Option<Icon>,
|
||||
meta: Option<ListHeaderMeta>,
|
||||
variant: ListItemVariant,
|
||||
toggleable: Toggleable,
|
||||
toggle: Toggle,
|
||||
}
|
||||
|
||||
impl ListHeader {
|
||||
|
@ -39,17 +39,12 @@ impl ListHeader {
|
|||
left_icon: None,
|
||||
meta: None,
|
||||
variant: ListItemVariant::default(),
|
||||
toggleable: Toggleable::NotToggleable,
|
||||
toggle: Toggle::NotToggleable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggleable = toggle.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn toggleable(mut self, toggleable: Toggleable) -> Self {
|
||||
self.toggleable = toggleable;
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -63,30 +58,8 @@ impl ListHeader {
|
|||
self
|
||||
}
|
||||
|
||||
fn disclosure_control<V: 'static>(&self) -> Div<V> {
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
match (is_toggleable, is_toggled) {
|
||||
(false, _) => div(),
|
||||
(_, true) => div().child(
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small),
|
||||
),
|
||||
(_, false) => div().child(
|
||||
IconElement::new(Icon::ChevronRight)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = self.toggleable.is_toggled();
|
||||
|
||||
let disclosure_control = self.disclosure_control();
|
||||
let disclosure_control = disclosure_control(self.toggle);
|
||||
|
||||
let meta = match self.meta {
|
||||
Some(ListHeaderMeta::Tools(icons)) => div().child(
|
||||
|
@ -106,7 +79,7 @@ impl ListHeader {
|
|||
|
||||
h_stack()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
// TODO: Add focus state
|
||||
// .when(self.state == InteractionState::Focused, |this| {
|
||||
// this.border()
|
||||
|
@ -193,12 +166,6 @@ impl ListSubHeader {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LeftContent {
|
||||
Icon(Icon),
|
||||
Avatar(SharedString),
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum ListEntrySize {
|
||||
#[default]
|
||||
|
@ -207,44 +174,36 @@ pub enum ListEntrySize {
|
|||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum ListItem<V: 'static> {
|
||||
pub enum ListItem {
|
||||
Entry(ListEntry),
|
||||
Details(ListDetailsEntry<V>),
|
||||
Separator(ListSeparator),
|
||||
Header(ListSubHeader),
|
||||
}
|
||||
|
||||
impl<V: 'static> From<ListEntry> for ListItem<V> {
|
||||
impl From<ListEntry> for ListItem {
|
||||
fn from(entry: ListEntry) -> Self {
|
||||
Self::Entry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<ListDetailsEntry<V>> for ListItem<V> {
|
||||
fn from(entry: ListDetailsEntry<V>) -> Self {
|
||||
Self::Details(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<ListSeparator> for ListItem<V> {
|
||||
impl From<ListSeparator> for ListItem {
|
||||
fn from(entry: ListSeparator) -> Self {
|
||||
Self::Separator(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<ListSubHeader> for ListItem<V> {
|
||||
impl From<ListSubHeader> for ListItem {
|
||||
fn from(entry: ListSubHeader) -> Self {
|
||||
Self::Header(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> ListItem<V> {
|
||||
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
impl ListItem {
|
||||
fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
match self {
|
||||
ListItem::Entry(entry) => div().child(entry.render(view, cx)),
|
||||
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
|
||||
ListItem::Header(header) => div().child(header.render(view, cx)),
|
||||
ListItem::Details(details) => div().child(details.render(view, cx)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,31 +222,29 @@ impl<V: 'static> ListItem<V> {
|
|||
|
||||
#[derive(Component)]
|
||||
pub struct ListEntry {
|
||||
disclosure_control_style: DisclosureControlVisibility,
|
||||
disabled: bool,
|
||||
// TODO: Reintroduce this
|
||||
// disclosure_control_style: DisclosureControlVisibility,
|
||||
indent_level: u32,
|
||||
label: Label,
|
||||
left_content: Option<LeftContent>,
|
||||
variant: ListItemVariant,
|
||||
size: ListEntrySize,
|
||||
state: InteractionState,
|
||||
toggle: Option<ToggleState>,
|
||||
left_slot: Option<GraphicSlot>,
|
||||
overflow: OverflowStyle,
|
||||
size: ListEntrySize,
|
||||
toggle: Toggle,
|
||||
variant: ListItemVariant,
|
||||
}
|
||||
|
||||
impl ListEntry {
|
||||
pub fn new(label: Label) -> Self {
|
||||
Self {
|
||||
disclosure_control_style: DisclosureControlVisibility::default(),
|
||||
disabled: false,
|
||||
indent_level: 0,
|
||||
label,
|
||||
variant: ListItemVariant::default(),
|
||||
left_content: None,
|
||||
size: ListEntrySize::default(),
|
||||
state: InteractionState::default(),
|
||||
// TODO: Should use Toggleable::NotToggleable
|
||||
// or remove Toggleable::NotToggleable from the system
|
||||
toggle: None,
|
||||
left_slot: None,
|
||||
overflow: OverflowStyle::Hidden,
|
||||
size: ListEntrySize::default(),
|
||||
toggle: Toggle::NotToggleable,
|
||||
variant: ListItemVariant::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,28 +258,23 @@ impl ListEntry {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggle = Some(toggle);
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_content(mut self, left_content: LeftContent) -> Self {
|
||||
self.left_content = Some(left_content);
|
||||
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
|
||||
self.left_slot = Some(left_content);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Icon) -> Self {
|
||||
self.left_content = Some(LeftContent::Icon(left_icon));
|
||||
self.left_slot = Some(GraphicSlot::Icon(left_icon));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
|
||||
self.left_content = Some(LeftContent::Avatar(left_avatar.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -331,63 +283,19 @@ impl ListEntry {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn disclosure_control_style(
|
||||
mut self,
|
||||
disclosure_control_style: DisclosureControlVisibility,
|
||||
) -> Self {
|
||||
self.disclosure_control_style = disclosure_control_style;
|
||||
self
|
||||
}
|
||||
|
||||
fn label_color(&self) -> LabelColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => LabelColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> IconColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => IconColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn disclosure_control<V: 'static>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Option<impl Component<V>> {
|
||||
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
} else {
|
||||
IconElement::new(Icon::ChevronRight)
|
||||
}
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small);
|
||||
|
||||
match (self.toggle, self.disclosure_control_style) {
|
||||
(Some(_), DisclosureControlVisibility::OnHover) => {
|
||||
Some(div().absolute().neg_left_5().child(disclosure_control_icon))
|
||||
}
|
||||
(Some(_), DisclosureControlVisibility::Always) => {
|
||||
Some(div().child(disclosure_control_icon))
|
||||
}
|
||||
(None, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let settings = user_settings(cx);
|
||||
|
||||
let left_content = match self.left_content.clone() {
|
||||
Some(LeftContent::Icon(i)) => Some(
|
||||
let left_content = match self.left_slot.clone() {
|
||||
Some(GraphicSlot::Icon(i)) => Some(
|
||||
h_stack().child(
|
||||
IconElement::new(i)
|
||||
.size(IconSize::Small)
|
||||
.color(IconColor::Muted),
|
||||
),
|
||||
),
|
||||
Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
|
||||
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
|
||||
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::new(src))),
|
||||
None => None,
|
||||
};
|
||||
|
||||
|
@ -399,11 +307,8 @@ impl ListEntry {
|
|||
div()
|
||||
.relative()
|
||||
.group("")
|
||||
.bg(cx.theme().colors().surface)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border()
|
||||
.border_color(cx.theme().colors().border_focused)
|
||||
})
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
// TODO: Add focus state
|
||||
.child(
|
||||
sized_item
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
|
@ -425,131 +330,13 @@ impl ListEntry {
|
|||
.gap_1()
|
||||
.items_center()
|
||||
.relative()
|
||||
.children(self.disclosure_control(cx))
|
||||
.child(disclosure_control(self.toggle))
|
||||
.children(left_content)
|
||||
.child(self.label),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ListDetailsEntryHandlers<V: 'static> {
|
||||
click: Option<ClickHandler<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Default for ListDetailsEntryHandlers<V> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ListDetailsEntry<V: 'static> {
|
||||
label: SharedString,
|
||||
meta: Option<SharedString>,
|
||||
left_content: Option<LeftContent>,
|
||||
handlers: ListDetailsEntryHandlers<V>,
|
||||
actions: Option<Vec<Button<V>>>,
|
||||
// TODO: make this more generic instead of
|
||||
// specifically for notifications
|
||||
seen: bool,
|
||||
}
|
||||
|
||||
impl<V: 'static> ListDetailsEntry<V> {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
meta: None,
|
||||
left_content: None,
|
||||
handlers: ListDetailsEntryHandlers::default(),
|
||||
actions: None,
|
||||
seen: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
|
||||
self.meta = Some(meta.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn seen(mut self, seen: bool) -> Self {
|
||||
self.seen = seen;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
|
||||
self.handlers.click = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn actions(mut self, actions: Vec<Button<V>>) -> Self {
|
||||
self.actions = Some(actions);
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let settings = user_settings(cx);
|
||||
|
||||
let (item_bg, item_bg_hover, item_bg_active) = (
|
||||
cx.theme().colors().ghost_element,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
);
|
||||
|
||||
let label_color = match self.seen {
|
||||
true => LabelColor::Muted,
|
||||
false => LabelColor::Default,
|
||||
};
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.group("")
|
||||
.bg(item_bg)
|
||||
.px_2()
|
||||
.py_1p5()
|
||||
.w_full()
|
||||
.z_index(1)
|
||||
.when(!self.seen, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(3.0))
|
||||
.top_3()
|
||||
.rounded_full()
|
||||
.border_2()
|
||||
.border_color(cx.theme().colors().surface)
|
||||
.w(px(9.0))
|
||||
.h(px(9.0))
|
||||
.z_index(2)
|
||||
.bg(cx.theme().status().info),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
v_stack()
|
||||
.w_full()
|
||||
.line_height(relative(1.2))
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_5()
|
||||
.h_5()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().icon_accent),
|
||||
)
|
||||
.child(Label::new(self.label.clone()).color(label_color))
|
||||
.children(
|
||||
self.meta
|
||||
.map(|meta| Label::new(meta).color(LabelColor::Muted)),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.children(self.actions.unwrap_or_default()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
pub struct ListSeparator;
|
||||
|
||||
|
@ -564,20 +351,22 @@ impl ListSeparator {
|
|||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct List<V: 'static> {
|
||||
items: Vec<ListItem<V>>,
|
||||
pub struct List {
|
||||
items: Vec<ListItem>,
|
||||
/// Message to display when the list is empty
|
||||
/// Defaults to "No items"
|
||||
empty_message: SharedString,
|
||||
header: Option<ListHeader>,
|
||||
toggleable: Toggleable,
|
||||
toggle: Toggle,
|
||||
}
|
||||
|
||||
impl<V: 'static> List<V> {
|
||||
pub fn new(items: Vec<ListItem<V>>) -> Self {
|
||||
impl List {
|
||||
pub fn new(items: Vec<ListItem>) -> Self {
|
||||
Self {
|
||||
items,
|
||||
empty_message: "No items".into(),
|
||||
header: None,
|
||||
toggleable: Toggleable::default(),
|
||||
toggle: Toggle::NotToggleable,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -591,19 +380,16 @@ impl<V: 'static> List<V> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: ToggleState) -> Self {
|
||||
self.toggleable = toggle.into();
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
let list_content = match (self.items.is_empty(), is_toggled) {
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let list_content = match (self.items.is_empty(), self.toggle) {
|
||||
(false, _) => div().children(self.items),
|
||||
(true, false) => div(),
|
||||
(true, true) => {
|
||||
(true, Toggle::Toggled(false)) => div(),
|
||||
(true, _) => {
|
||||
div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted))
|
||||
}
|
||||
};
|
||||
|
@ -611,7 +397,7 @@ impl<V: 'static> List<V> {
|
|||
v_stack()
|
||||
.w_full()
|
||||
.py_1()
|
||||
.children(self.header.map(|header| header.toggleable(self.toggleable)))
|
||||
.children(self.header.map(|header| header))
|
||||
.child(list_content)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{v_stack, Buffer, Icon, IconButton, Label};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct MultiBuffer {
|
||||
buffers: Vec<Buffer>,
|
||||
}
|
||||
|
||||
impl MultiBuffer {
|
||||
pub fn new(buffers: Vec<Buffer>) -> Self {
|
||||
Self { buffers }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
v_stack()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.children(self.buffers.clone().into_iter().map(|buffer| {
|
||||
v_stack()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.p_4()
|
||||
.bg(cx.theme().colors().editor_subheader)
|
||||
.child(Label::new("main.rs"))
|
||||
.child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)),
|
||||
)
|
||||
.child(buffer)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{hello_world_rust_buffer_example, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct MultiBufferStory;
|
||||
|
||||
impl Render for MultiBufferStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, MultiBuffer>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(MultiBuffer::new(vec![
|
||||
hello_world_rust_buffer_example(cx),
|
||||
hello_world_rust_buffer_example(cx),
|
||||
hello_world_rust_buffer_example(cx),
|
||||
hello_world_rust_buffer_example(cx),
|
||||
hello_world_rust_buffer_example(cx),
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ impl NotificationToast {
|
|||
.px_1p5()
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.bg(cx.theme().colors().elevated_surface)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.child(div().size_full().child(self.label.clone()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,389 +0,0 @@
|
|||
use crate::utils::naive_format_distance_from_now;
|
||||
use crate::{
|
||||
h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon,
|
||||
IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator,
|
||||
UnreadIndicator,
|
||||
};
|
||||
use crate::{ClickHandler, ListHeader};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct NotificationsPanel {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl NotificationsPanel {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.child(
|
||||
ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![
|
||||
Icon::AtSign,
|
||||
Icon::BellOff,
|
||||
Icon::MailOpen,
|
||||
]))),
|
||||
)
|
||||
.child(ListSeparator::new())
|
||||
.child(
|
||||
v_stack()
|
||||
.id("notifications-panel-scroll-view")
|
||||
.py_1()
|
||||
.overflow_y_scroll()
|
||||
.flex_1()
|
||||
.child(
|
||||
div()
|
||||
.mx_2()
|
||||
.p_1()
|
||||
// TODO: Add cursor style
|
||||
// .cursor(Cursor::IBeam)
|
||||
.bg(cx.theme().colors().element)
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Label::new("Search...")
|
||||
.color(LabelColor::Placeholder)
|
||||
.line_height_style(LineHeightStyle::UILabel),
|
||||
),
|
||||
)
|
||||
.child(v_stack().px_1().children(static_new_notification_items_2())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonOrIconButton<V: 'static> {
|
||||
Button(Button<V>),
|
||||
IconButton(IconButton<V>),
|
||||
}
|
||||
|
||||
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: Button<V>) -> Self {
|
||||
Self::Button(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: IconButton<V>) -> Self {
|
||||
Self::IconButton(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotificationAction<V: 'static> {
|
||||
button: ButtonOrIconButton<V>,
|
||||
tooltip: SharedString,
|
||||
/// Shows after action is chosen
|
||||
///
|
||||
/// For example, if the action is "Accept" the taken message could be:
|
||||
///
|
||||
/// - `(None,"Accepted")` - "Accepted"
|
||||
///
|
||||
/// - `(Some(Icon::Check),"Accepted")` - ✓ "Accepted"
|
||||
taken_message: (Option<Icon>, SharedString),
|
||||
}
|
||||
|
||||
impl<V: 'static> NotificationAction<V> {
|
||||
pub fn new(
|
||||
button: impl Into<ButtonOrIconButton<V>>,
|
||||
tooltip: impl Into<SharedString>,
|
||||
(icon, taken_message): (Option<Icon>, impl Into<SharedString>),
|
||||
) -> Self {
|
||||
Self {
|
||||
button: button.into(),
|
||||
tooltip: tooltip.into(),
|
||||
taken_message: (icon, taken_message.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ActorOrIcon {
|
||||
Actor(PublicActor),
|
||||
Icon(Icon),
|
||||
}
|
||||
|
||||
pub struct NotificationMeta<V: 'static> {
|
||||
items: Vec<(Option<Icon>, SharedString, Option<ClickHandler<V>>)>,
|
||||
}
|
||||
|
||||
struct NotificationHandlers<V: 'static> {
|
||||
click: Option<ClickHandler<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Default for NotificationHandlers<V> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Notification<V: 'static> {
|
||||
id: ElementId,
|
||||
slot: ActorOrIcon,
|
||||
message: SharedString,
|
||||
date_received: NaiveDateTime,
|
||||
meta: Option<NotificationMeta<V>>,
|
||||
actions: Option<[NotificationAction<V>; 2]>,
|
||||
unread: bool,
|
||||
new: bool,
|
||||
action_taken: Option<NotificationAction<V>>,
|
||||
handlers: NotificationHandlers<V>,
|
||||
}
|
||||
|
||||
impl<V> Notification<V> {
|
||||
fn new(
|
||||
id: ElementId,
|
||||
message: SharedString,
|
||||
date_received: NaiveDateTime,
|
||||
slot: ActorOrIcon,
|
||||
click_action: Option<ClickHandler<V>>,
|
||||
) -> Self {
|
||||
let handlers = if click_action.is_some() {
|
||||
NotificationHandlers {
|
||||
click: click_action,
|
||||
}
|
||||
} else {
|
||||
NotificationHandlers::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
id,
|
||||
date_received,
|
||||
message,
|
||||
meta: None,
|
||||
slot,
|
||||
actions: None,
|
||||
unread: true,
|
||||
new: false,
|
||||
action_taken: None,
|
||||
handlers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new notification with an actor slot.
|
||||
///
|
||||
/// Requires a click action.
|
||||
pub fn new_actor_message(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
actor: PublicActor,
|
||||
click_action: ClickHandler<V>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Actor(actor),
|
||||
Some(click_action),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an icon slot.
|
||||
///
|
||||
/// Requires a click action.
|
||||
pub fn new_icon_message(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
icon: Icon,
|
||||
click_action: ClickHandler<V>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Icon(icon),
|
||||
Some(click_action),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an actor slot
|
||||
/// and a Call To Action row.
|
||||
///
|
||||
/// Cannot take a click action due to required actions.
|
||||
pub fn new_actor_with_actions(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
actor: PublicActor,
|
||||
actions: [NotificationAction<V>; 2],
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Actor(actor),
|
||||
None,
|
||||
)
|
||||
.actions(actions)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an icon slot
|
||||
/// and a Call To Action row.
|
||||
///
|
||||
/// Cannot take a click action due to required actions.
|
||||
pub fn new_icon_with_actions(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
icon: Icon,
|
||||
actions: [NotificationAction<V>; 2],
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Icon(icon),
|
||||
None,
|
||||
)
|
||||
.actions(actions)
|
||||
}
|
||||
|
||||
fn on_click(mut self, handler: ClickHandler<V>) -> Self {
|
||||
self.handlers.click = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn actions(mut self, actions: [NotificationAction<V>; 2]) -> Self {
|
||||
self.actions = Some(actions);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: NotificationMeta<V>) -> Self {
|
||||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
|
||||
fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
if let Some(meta) = &self.meta {
|
||||
h_stack().children(
|
||||
meta.items
|
||||
.iter()
|
||||
.map(|(icon, text, _)| {
|
||||
let mut meta_el = div();
|
||||
if let Some(icon) = icon {
|
||||
meta_el = meta_el.child(IconElement::new(icon.clone()));
|
||||
}
|
||||
meta_el.child(Label::new(text.clone()).color(LabelColor::Muted))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
div()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
match &self.slot {
|
||||
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
|
||||
ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.relative()
|
||||
.id(self.id.clone())
|
||||
.p_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.children(
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(3.0))
|
||||
.top_3()
|
||||
.z_index(2)
|
||||
.child(UnreadIndicator::new()),
|
||||
)
|
||||
.filter(|_| self.unread),
|
||||
)
|
||||
.child(
|
||||
v_stack()
|
||||
.z_index(1)
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.child(
|
||||
h_stack()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(self.render_slot(cx))
|
||||
.child(div().flex_1().child(Label::new(self.message.clone()))),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(naive_format_distance_from_now(
|
||||
self.date_received,
|
||||
true,
|
||||
true,
|
||||
))
|
||||
.color(LabelColor::Muted),
|
||||
)
|
||||
.child(self.render_meta_items(cx)),
|
||||
)
|
||||
.child(match (self.actions, self.action_taken) {
|
||||
// Show nothing
|
||||
(None, _) => div(),
|
||||
// Show the taken_message
|
||||
(Some(_), Some(action_taken)) => h_stack()
|
||||
.children(action_taken.taken_message.0.map(|icon| {
|
||||
IconElement::new(icon).color(crate::IconColor::Muted)
|
||||
}))
|
||||
.child(
|
||||
Label::new(action_taken.taken_message.1.clone())
|
||||
.color(LabelColor::Muted),
|
||||
),
|
||||
// Show the actions
|
||||
(Some(actions), None) => {
|
||||
h_stack().children(actions.map(|action| match action.button {
|
||||
ButtonOrIconButton::Button(button) => {
|
||||
Component::render(button)
|
||||
}
|
||||
ButtonOrIconButton::IconButton(icon_button) => {
|
||||
Component::render(icon_button)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use gpui2::{px, Styled};
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{Panel, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct NotificationsPanelStory;
|
||||
|
||||
impl Render for NotificationsPanelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, NotificationsPanel>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
Panel::new("panel", cx).child(NotificationsPanel::new("notifications_panel")),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ impl Palette {
|
|||
.id(self.id.clone())
|
||||
.w_96()
|
||||
.rounded_lg()
|
||||
.bg(cx.theme().colors().elevated_surface)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
|
@ -56,7 +56,12 @@ impl Palette {
|
|||
.child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
|
||||
Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder),
|
||||
)))
|
||||
.child(div().h_px().w_full().bg(cx.theme().colors().element))
|
||||
.child(
|
||||
div()
|
||||
.h_px()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().element_background),
|
||||
)
|
||||
.child(
|
||||
v_stack()
|
||||
.id("items")
|
||||
|
|
|
@ -107,7 +107,7 @@ impl<V: 'static> Panel<V> {
|
|||
PanelSide::Right => this.border_l(),
|
||||
PanelSide::Bottom => this.border_b().w_full().h(current_size),
|
||||
})
|
||||
.bg(cx.theme().colors().surface)
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.children(self.children)
|
||||
}
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum SplitDirection {
|
||||
#[default]
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Pane<V: 'static> {
|
||||
id: ElementId,
|
||||
size: Size<Length>,
|
||||
fill: Hsla,
|
||||
children: SmallVec<[AnyElement<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Pane<V> {
|
||||
pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
|
||||
// Fill is only here for debugging purposes, remove before release
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
size,
|
||||
fill: hsla(0.3, 0.3, 0.3, 1.),
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill(mut self, fill: Hsla) -> Self {
|
||||
self.fill = fill;
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_initial()
|
||||
.bg(self.fill)
|
||||
.w(self.size.width)
|
||||
.h(self.size.height)
|
||||
.relative()
|
||||
.child(div().z_index(0).size_full().children(self.children))
|
||||
.child(
|
||||
div()
|
||||
.z_index(1)
|
||||
.id("drag-target")
|
||||
.drag_over::<ExternalPaths>(|d| d.bg(red()))
|
||||
.on_drop(|_, files: View<ExternalPaths>, cx| {
|
||||
eprintln!("dropped files! {:?}", files.read(cx));
|
||||
})
|
||||
.absolute()
|
||||
.inset_0(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> ParentElement<V> for Pane<V> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PaneGroup<V: 'static> {
|
||||
groups: Vec<PaneGroup<V>>,
|
||||
panes: Vec<Pane<V>>,
|
||||
split_direction: SplitDirection,
|
||||
}
|
||||
|
||||
impl<V: 'static> PaneGroup<V> {
|
||||
pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
|
||||
Self {
|
||||
groups,
|
||||
panes: Vec::new(),
|
||||
split_direction,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
|
||||
Self {
|
||||
groups: Vec::new(),
|
||||
panes,
|
||||
split_direction,
|
||||
}
|
||||
}
|
||||
|
||||
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
if !self.panes.is_empty() {
|
||||
let el = div()
|
||||
.flex()
|
||||
.flex_1()
|
||||
.gap_px()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.children(self.panes.into_iter().map(|pane| pane.render(view, cx)));
|
||||
|
||||
if self.split_direction == SplitDirection::Horizontal {
|
||||
return el;
|
||||
} else {
|
||||
return el.flex_col();
|
||||
}
|
||||
}
|
||||
|
||||
if !self.groups.is_empty() {
|
||||
let el = div()
|
||||
.flex()
|
||||
.flex_1()
|
||||
.gap_px()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.bg(cx.theme().colors().editor)
|
||||
.children(self.groups.into_iter().map(|group| group.render(view, cx)));
|
||||
|
||||
if self.split_direction == SplitDirection::Horizontal {
|
||||
return el;
|
||||
} else {
|
||||
return el.flex_col();
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
}
|
174
crates/ui2/src/components/player.rs
Normal file
174
crates/ui2/src/components/player.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
use gpui2::{Hsla, ViewContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Represents a person with a Zed account's public profile.
|
||||
/// All data in this struct should be considered public.
|
||||
pub struct PublicPlayer {
|
||||
pub username: SharedString,
|
||||
pub avatar: SharedString,
|
||||
pub is_contact: bool,
|
||||
}
|
||||
|
||||
impl PublicPlayer {
|
||||
pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
username: username.into(),
|
||||
avatar: avatar.into(),
|
||||
is_contact: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum PlayerStatus {
|
||||
#[default]
|
||||
Offline,
|
||||
Online,
|
||||
InCall,
|
||||
Away,
|
||||
DoNotDisturb,
|
||||
Invisible,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum MicStatus {
|
||||
Muted,
|
||||
#[default]
|
||||
Unmuted,
|
||||
}
|
||||
|
||||
impl MicStatus {
|
||||
pub fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::Muted => Self::Unmuted,
|
||||
Self::Unmuted => Self::Muted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum VideoStatus {
|
||||
On,
|
||||
#[default]
|
||||
Off,
|
||||
}
|
||||
|
||||
impl VideoStatus {
|
||||
pub fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::On => Self::Off,
|
||||
Self::Off => Self::On,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum ScreenShareStatus {
|
||||
Shared,
|
||||
#[default]
|
||||
NotShared,
|
||||
}
|
||||
|
||||
impl ScreenShareStatus {
|
||||
pub fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::Shared => Self::NotShared,
|
||||
Self::NotShared => Self::Shared,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlayerCallStatus {
|
||||
pub mic_status: MicStatus,
|
||||
/// Indicates if the player is currently speaking
|
||||
/// And the intensity of the volume coming through
|
||||
///
|
||||
/// 0.0 - 1.0
|
||||
pub voice_activity: f32,
|
||||
pub video_status: VideoStatus,
|
||||
pub screen_share_status: ScreenShareStatus,
|
||||
pub in_current_project: bool,
|
||||
pub disconnected: bool,
|
||||
pub following: Option<Vec<Player>>,
|
||||
pub followers: Option<Vec<Player>>,
|
||||
}
|
||||
|
||||
impl PlayerCallStatus {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
mic_status: MicStatus::default(),
|
||||
voice_activity: 0.,
|
||||
video_status: VideoStatus::default(),
|
||||
screen_share_status: ScreenShareStatus::default(),
|
||||
in_current_project: true,
|
||||
disconnected: false,
|
||||
following: None,
|
||||
followers: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct Player {
|
||||
index: usize,
|
||||
avatar_src: String,
|
||||
username: String,
|
||||
status: PlayerStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlayerWithCallStatus {
|
||||
player: Player,
|
||||
call_status: PlayerCallStatus,
|
||||
}
|
||||
|
||||
impl PlayerWithCallStatus {
|
||||
pub fn new(player: Player, call_status: PlayerCallStatus) -> Self {
|
||||
Self {
|
||||
player,
|
||||
call_status,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_player(&self) -> &Player {
|
||||
&self.player
|
||||
}
|
||||
|
||||
pub fn get_call_status(&self) -> &PlayerCallStatus {
|
||||
&self.call_status
|
||||
}
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(index: usize, avatar_src: String, username: String) -> Self {
|
||||
Self {
|
||||
index,
|
||||
avatar_src,
|
||||
username,
|
||||
status: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status(mut self, status: PlayerStatus) -> Self {
|
||||
self.status = status;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cursor_color<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
|
||||
cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].cursor
|
||||
}
|
||||
|
||||
pub fn selection_color<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
|
||||
cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].selection
|
||||
}
|
||||
|
||||
pub fn avatar_src(&self) -> &str {
|
||||
&self.avatar_src
|
||||
}
|
||||
|
||||
pub fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{
|
||||
static_project_panel_project_items, static_project_panel_single_items, Input, List, ListHeader,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ProjectPanel {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl ProjectPanel {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.child(
|
||||
div()
|
||||
.id("project-panel-contents")
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
List::new(static_project_panel_single_items())
|
||||
.header(ListHeader::new("FILES").toggle(ToggleState::Toggled))
|
||||
.empty_message("No files in directory")
|
||||
.toggle(ToggleState::Toggled),
|
||||
)
|
||||
.child(
|
||||
List::new(static_project_panel_project_items())
|
||||
.header(ListHeader::new("PROJECT").toggle(ToggleState::Toggled))
|
||||
.empty_message("No folders in directory")
|
||||
.toggle(ToggleState::Toggled),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Input::new("Find something...")
|
||||
.value("buffe".to_string())
|
||||
.state(InteractionState::Focused),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use gpui2::ElementId;
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{Panel, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct ProjectPanelStory;
|
||||
|
||||
impl Render for ProjectPanelStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ProjectPanel>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
Panel::new("project-panel-outer", cx)
|
||||
.child(ProjectPanel::new("project-panel-inner")),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{OrderMethod, Palette, PaletteItem};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RecentProjects {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl RecentProjects {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().id(self.id.clone()).child(
|
||||
Palette::new("palette")
|
||||
.items(vec![
|
||||
PaletteItem::new("zed").sublabel(SharedString::from("~/projects/zed")),
|
||||
PaletteItem::new("saga").sublabel(SharedString::from("~/projects/saga")),
|
||||
PaletteItem::new("journal").sublabel(SharedString::from("~/journal")),
|
||||
PaletteItem::new("dotfiles").sublabel(SharedString::from("~/dotfiles")),
|
||||
PaletteItem::new("zed.dev").sublabel(SharedString::from("~/projects/zed.dev")),
|
||||
PaletteItem::new("laminar").sublabel(SharedString::from("~/projects/laminar")),
|
||||
])
|
||||
.placeholder("Recent Projects...")
|
||||
.empty_string("No matches")
|
||||
.default_order(OrderMethod::Ascending),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct RecentProjectsStory;
|
||||
|
||||
impl Render for RecentProjectsStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, RecentProjects>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(RecentProjects::new("recent-projects"))
|
||||
}
|
||||
}
|
||||
}
|
14
crates/ui2/src/components/slot.rs
Normal file
14
crates/ui2/src/components/slot.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use gpui2::SharedString;
|
||||
|
||||
use crate::Icon;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A slot utility that provides a way to to pass either
|
||||
/// an icon or an image to a component.
|
||||
///
|
||||
/// Can be filled with a []
|
||||
pub enum GraphicSlot {
|
||||
Icon(Icon),
|
||||
Avatar(SharedString),
|
||||
PublicActor(SharedString),
|
||||
}
|
31
crates/ui2/src/components/stack.rs
Normal file
31
crates/ui2/src/components/stack.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use gpui2::{div, Div};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub trait Stack: Styled + Sized {
|
||||
/// Horizontally stacks elements.
|
||||
fn h_stack(self) -> Self {
|
||||
self.flex().flex_row().items_center()
|
||||
}
|
||||
|
||||
/// Vertically stacks elements.
|
||||
fn v_stack(self) -> Self {
|
||||
self.flex().flex_col()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Stack for Div<V> {}
|
||||
|
||||
/// Horizontally stacks elements.
|
||||
///
|
||||
/// Sets `flex()`, `flex_row()`, `items_center()`
|
||||
pub fn h_stack<V: 'static>() -> Div<V> {
|
||||
div().h_stack()
|
||||
}
|
||||
|
||||
/// Vertically stacks elements.
|
||||
///
|
||||
/// Sets `flex()`, `flex_col()`
|
||||
pub fn v_stack<V: 'static>() -> Div<V> {
|
||||
div().v_stack()
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Button, Icon, IconButton, IconColor, ToolDivider, Workspace};
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum Tool {
|
||||
#[default]
|
||||
ProjectPanel,
|
||||
CollaborationPanel,
|
||||
Terminal,
|
||||
Assistant,
|
||||
Feedback,
|
||||
Diagnostics,
|
||||
}
|
||||
|
||||
struct ToolGroup {
|
||||
active_index: Option<usize>,
|
||||
tools: Vec<Tool>,
|
||||
}
|
||||
|
||||
impl Default for ToolGroup {
|
||||
fn default() -> Self {
|
||||
ToolGroup {
|
||||
active_index: None,
|
||||
tools: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(view_type = "Workspace")]
|
||||
pub struct StatusBar {
|
||||
left_tools: Option<ToolGroup>,
|
||||
right_tools: Option<ToolGroup>,
|
||||
bottom_tools: Option<ToolGroup>,
|
||||
}
|
||||
|
||||
impl StatusBar {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
left_tools: None,
|
||||
right_tools: None,
|
||||
bottom_tools: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||
self.left_tools = {
|
||||
let mut tools = vec![tool];
|
||||
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||
Some(ToolGroup {
|
||||
active_index,
|
||||
tools,
|
||||
})
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||
self.right_tools = {
|
||||
let mut tools = vec![tool];
|
||||
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||
Some(ToolGroup {
|
||||
active_index,
|
||||
tools,
|
||||
})
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bottom_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||
self.bottom_tools = {
|
||||
let mut tools = vec![tool];
|
||||
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||
Some(ToolGroup {
|
||||
active_index,
|
||||
tools,
|
||||
})
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
fn render(
|
||||
self,
|
||||
view: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> impl Component<Workspace> {
|
||||
div()
|
||||
.py_0p5()
|
||||
.px_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().status_bar)
|
||||
.child(self.left_tools(view, cx))
|
||||
.child(self.right_tools(view, cx))
|
||||
}
|
||||
|
||||
fn left_tools(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &WindowContext,
|
||||
) -> impl Component<Workspace> {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::<Workspace>::new("project_panel", Icon::FileTree)
|
||||
.when(workspace.is_project_panel_open(), |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|workspace, cx| {
|
||||
workspace.toggle_project_panel(cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::<Workspace>::new("collab_panel", Icon::Hash)
|
||||
.when(workspace.is_collab_panel_open(), |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|workspace, cx| {
|
||||
workspace.toggle_collab_panel();
|
||||
}),
|
||||
)
|
||||
.child(ToolDivider::new())
|
||||
.child(IconButton::new("diagnostics", Icon::XCircle))
|
||||
}
|
||||
|
||||
fn right_tools(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &WindowContext,
|
||||
) -> impl Component<Workspace> {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(Button::new("116:25"))
|
||||
.child(
|
||||
Button::<Workspace>::new("Rust").on_click(Arc::new(|workspace, cx| {
|
||||
workspace.toggle_language_selector(cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(ToolDivider::new())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new("copilot", Icon::Copilot)
|
||||
.on_click(|_, _| println!("Copilot clicked.")),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("envelope", Icon::Envelope)
|
||||
.on_click(|_, _| println!("Send Feedback clicked.")),
|
||||
),
|
||||
)
|
||||
.child(ToolDivider::new())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::<Workspace>::new("terminal", Icon::Terminal)
|
||||
.when(workspace.is_terminal_open(), |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|workspace, cx| {
|
||||
workspace.toggle_terminal(cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::<Workspace>::new("chat_panel", Icon::MessageBubbles)
|
||||
.when(workspace.is_chat_panel_open(), |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|workspace, cx| {
|
||||
workspace.toggle_chat_panel(cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::<Workspace>::new("assistant_panel", Icon::Ai)
|
||||
.when(workspace.is_assistant_panel_open(), |this| {
|
||||
this.color(IconColor::Accent)
|
||||
})
|
||||
.on_click(|workspace, cx| {
|
||||
workspace.toggle_assistant_panel(cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -109,12 +109,12 @@ impl Tab {
|
|||
|
||||
let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
|
||||
false => (
|
||||
cx.theme().colors().tab_inactive,
|
||||
cx.theme().colors().tab_inactive_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
true => (
|
||||
cx.theme().colors().tab_active,
|
||||
cx.theme().colors().tab_active_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{Icon, IconButton, Tab};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct TabBar {
|
||||
id: ElementId,
|
||||
/// Backwards, Forwards
|
||||
can_navigate: (bool, bool),
|
||||
tabs: Vec<Tab>,
|
||||
}
|
||||
|
||||
impl TabBar {
|
||||
pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
can_navigate: (false, false),
|
||||
tabs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
|
||||
self.can_navigate = can_navigate;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let (can_navigate_back, can_navigate_forward) = self.can_navigate;
|
||||
|
||||
div()
|
||||
.group("tab_bar")
|
||||
.id(self.id.clone())
|
||||
.w_full()
|
||||
.flex()
|
||||
.bg(cx.theme().colors().tab_bar)
|
||||
// Left Side
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.right_0()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
IconButton::new("arrow_left", Icon::ArrowLeft)
|
||||
.state(InteractionState::Enabled.if_enabled(can_navigate_back)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("arrow_right", Icon::ArrowRight).state(
|
||||
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().w_0().flex_1().h_full().child(
|
||||
div()
|
||||
.id("tabs")
|
||||
.flex()
|
||||
.overflow_x_scroll()
|
||||
.children(self.tabs.clone()),
|
||||
),
|
||||
)
|
||||
// Right Side
|
||||
.child(
|
||||
div()
|
||||
// We only use absolute here since we don't
|
||||
// have opacity or `hidden()` yet
|
||||
.absolute()
|
||||
.neg_top_7()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
.group_hover("tab_bar", |this| this.top_0())
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(IconButton::new("plus", Icon::Plus))
|
||||
.child(IconButton::new("split", Icon::Split)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use gpui2::ElementId;
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct TabBarStory;
|
||||
|
||||
impl Render for TabBarStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TabBar>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(TabBar::new(
|
||||
"tab-bar",
|
||||
vec![
|
||||
Tab::new(1)
|
||||
.title("Cargo.toml".to_string())
|
||||
.current(false)
|
||||
.git_status(GitStatus::Modified),
|
||||
Tab::new(2)
|
||||
.title("Channels Panel".to_string())
|
||||
.current(false),
|
||||
Tab::new(3)
|
||||
.title("channels_panel.rs".to_string())
|
||||
.current(true)
|
||||
.git_status(GitStatus::Modified),
|
||||
Tab::new(4)
|
||||
.title("workspace.rs".to_string())
|
||||
.current(false)
|
||||
.git_status(GitStatus::Modified),
|
||||
Tab::new(5)
|
||||
.title("icon_button.rs".to_string())
|
||||
.current(false),
|
||||
Tab::new(6)
|
||||
.title("storybook.rs".to_string())
|
||||
.current(false)
|
||||
.git_status(GitStatus::Created),
|
||||
Tab::new(7).title("theme.rs".to_string()).current(false),
|
||||
Tab::new(8)
|
||||
.title("theme_registry.rs".to_string())
|
||||
.current(false),
|
||||
Tab::new(9)
|
||||
.title("styleable_helpers.rs".to_string())
|
||||
.current(false),
|
||||
],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
use gpui2::{relative, rems, Size};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Icon, IconButton, Pane, Tab};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Terminal;
|
||||
|
||||
impl Terminal {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let can_navigate_back = true;
|
||||
let can_navigate_forward = false;
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.child(
|
||||
// Terminal Tabs.
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.child(
|
||||
div().px_1().flex().flex_none().gap_2().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
IconButton::new("arrow_left", Icon::ArrowLeft).state(
|
||||
InteractionState::Enabled.if_enabled(can_navigate_back),
|
||||
),
|
||||
)
|
||||
.child(IconButton::new("arrow_right", Icon::ArrowRight).state(
|
||||
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().w_0().flex_1().h_full().child(
|
||||
div()
|
||||
.flex()
|
||||
.child(
|
||||
Tab::new(1)
|
||||
.title("zed — fish".to_string())
|
||||
.icon(Icon::Terminal)
|
||||
.close_side(IconSide::Right)
|
||||
.current(true),
|
||||
)
|
||||
.child(
|
||||
Tab::new(2)
|
||||
.title("zed — fish".to_string())
|
||||
.icon(Icon::Terminal)
|
||||
.close_side(IconSide::Right)
|
||||
.current(false),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
// Terminal Pane.
|
||||
.child(
|
||||
Pane::new(
|
||||
"terminal",
|
||||
Size {
|
||||
width: relative(1.).into(),
|
||||
height: rems(36.).into(),
|
||||
},
|
||||
)
|
||||
.child(crate::static_data::terminal_buffer(cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
use gpui2::{Div, Render};
|
||||
pub struct TerminalStory;
|
||||
|
||||
impl Render for TerminalStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Terminal>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Terminal::new())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{OrderMethod, Palette, PaletteItem};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ThemeSelector {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
impl ThemeSelector {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().child(
|
||||
Palette::new(self.id.clone())
|
||||
.items(vec![
|
||||
PaletteItem::new("One Dark"),
|
||||
PaletteItem::new("Rosé Pine"),
|
||||
PaletteItem::new("Rosé Pine Moon"),
|
||||
PaletteItem::new("Sandcastle"),
|
||||
PaletteItem::new("Solarized Dark"),
|
||||
PaletteItem::new("Summercamp"),
|
||||
PaletteItem::new("Atelier Cave Light"),
|
||||
PaletteItem::new("Atelier Dune Light"),
|
||||
PaletteItem::new("Atelier Estuary Light"),
|
||||
PaletteItem::new("Atelier Forest Light"),
|
||||
PaletteItem::new("Atelier Heath Light"),
|
||||
])
|
||||
.placeholder("Select Theme...")
|
||||
.empty_string("No matches")
|
||||
.default_order(OrderMethod::Ascending),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::Story;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ThemeSelectorStory;
|
||||
|
||||
impl Render for ThemeSelectorStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ThemeSelector>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(ThemeSelector::new("theme-selector"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui2::{Div, Render, View, VisualContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::settings::user_settings;
|
||||
use crate::{
|
||||
Avatar, Button, Icon, IconButton, IconColor, MicStatus, PlayerStack, PlayerWithCallStatus,
|
||||
ScreenShareStatus, ToolDivider, TrafficLights,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Livestream {
|
||||
pub players: Vec<PlayerWithCallStatus>,
|
||||
pub channel: Option<String>, // projects
|
||||
// windows
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TitleBar {
|
||||
/// If the window is active from the OS's perspective.
|
||||
is_active: Arc<AtomicBool>,
|
||||
livestream: Option<Livestream>,
|
||||
mic_status: MicStatus,
|
||||
is_deafened: bool,
|
||||
screen_share_status: ScreenShareStatus,
|
||||
}
|
||||
|
||||
impl TitleBar {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let is_active = Arc::new(AtomicBool::new(true));
|
||||
let active = is_active.clone();
|
||||
|
||||
// cx.observe_window_activation(move |_, is_active, cx| {
|
||||
// active.store(is_active, std::sync::atomic::Ordering::SeqCst);
|
||||
// cx.notify();
|
||||
// })
|
||||
// .detach();
|
||||
|
||||
Self {
|
||||
is_active,
|
||||
livestream: None,
|
||||
mic_status: MicStatus::Unmuted,
|
||||
is_deafened: false,
|
||||
screen_share_status: ScreenShareStatus::NotShared,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_livestream(mut self, livestream: Option<Livestream>) -> Self {
|
||||
self.livestream = livestream;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_mic_muted(&self) -> bool {
|
||||
self.mic_status == MicStatus::Muted
|
||||
}
|
||||
|
||||
pub fn toggle_mic_status(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.mic_status = self.mic_status.inverse();
|
||||
|
||||
// Undeafen yourself when unmuting the mic while deafened.
|
||||
if self.is_deafened && self.mic_status == MicStatus::Unmuted {
|
||||
self.is_deafened = false;
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_deafened(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.is_deafened = !self.is_deafened;
|
||||
self.mic_status = MicStatus::Muted;
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn toggle_screen_share_status(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.screen_share_status = self.screen_share_status.inverse();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn view(cx: &mut WindowContext, livestream: Option<Livestream>) -> View<Self> {
|
||||
cx.build_view(|cx| Self::new(cx).set_livestream(livestream))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TitleBar {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
|
||||
let settings = user_settings(cx);
|
||||
|
||||
// let has_focus = cx.window_is_active();
|
||||
let has_focus = true;
|
||||
|
||||
let player_list = if let Some(livestream) = &self.livestream {
|
||||
livestream.players.clone().into_iter()
|
||||
} else {
|
||||
vec![].into_iter()
|
||||
};
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().background)
|
||||
.py_1()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
.child(TrafficLights::new().window_has_focus(has_focus))
|
||||
// === Project Info === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.when(*settings.titlebar.show_project_owner, |this| {
|
||||
this.child(Button::new("iamnbutler"))
|
||||
})
|
||||
.child(Button::new("zed"))
|
||||
.child(Button::new("nate/gpui2-ui-components")),
|
||||
)
|
||||
.children(player_list.map(|p| PlayerStack::new(p)))
|
||||
.child(IconButton::new("plus", Icon::Plus)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(IconButton::new("folder_x", Icon::FolderX))
|
||||
.child(IconButton::new("exit", Icon::Exit)),
|
||||
)
|
||||
.child(ToolDivider::new())
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::<TitleBar>::new("toggle_mic_status", Icon::Mic)
|
||||
.when(self.is_mic_muted(), |this| this.color(IconColor::Error))
|
||||
.on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)),
|
||||
)
|
||||
.child(
|
||||
IconButton::<TitleBar>::new("toggle_deafened", Icon::AudioOn)
|
||||
.when(self.is_deafened, |this| this.color(IconColor::Error))
|
||||
.on_click(|title_bar, cx| title_bar.toggle_deafened(cx)),
|
||||
)
|
||||
.child(
|
||||
IconButton::<TitleBar>::new("toggle_screen_share", Icon::Screen)
|
||||
.when(
|
||||
self.screen_share_status == ScreenShareStatus::Shared,
|
||||
|this| this.color(IconColor::Accent),
|
||||
)
|
||||
.on_click(|title_bar, cx| {
|
||||
title_bar.toggle_screen_share_status(cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().px_2().flex().items_center().child(
|
||||
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.shape(Shape::RoundedRectangle),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
|
||||
pub struct TitleBarStory {
|
||||
title_bar: View<TitleBar>,
|
||||
}
|
||||
|
||||
impl TitleBarStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self {
|
||||
title_bar: TitleBar::view(cx, None),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TitleBarStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TitleBar>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(self.title_bar.clone())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ impl<V: 'static> Toast<V> {
|
|||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.overflow_hidden()
|
||||
.bg(cx.theme().colors().elevated_surface)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
|
61
crates/ui2/src/components/toggle.rs
Normal file
61
crates/ui2/src/components/toggle.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use gpui2::{div, Component, ParentElement};
|
||||
|
||||
use crate::{Icon, IconColor, IconElement, IconSize};
|
||||
|
||||
/// Whether the entry is toggleable, and if so, whether it is currently toggled.
|
||||
///
|
||||
/// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases.
|
||||
///
|
||||
/// You can check if an element is toggleable with `.is_toggleable()`
|
||||
///
|
||||
/// Possible values:
|
||||
/// - `Toggle::NotToggleable` - The entry is not toggleable
|
||||
/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled
|
||||
/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Toggle {
|
||||
NotToggleable,
|
||||
Toggled(bool),
|
||||
}
|
||||
|
||||
impl Toggle {
|
||||
/// Returns true if the entry is toggled (or is not toggleable.)
|
||||
///
|
||||
/// As element that isn't toggleable is always "expanded" or "enabled"
|
||||
/// returning true in that case makes sense.
|
||||
pub fn is_toggled(&self) -> bool {
|
||||
match self {
|
||||
Self::Toggled(false) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_toggleable(&self) -> bool {
|
||||
match self {
|
||||
Self::Toggled(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Toggle {
|
||||
fn from(toggled: bool) -> Self {
|
||||
Toggle::Toggled(toggled)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
|
||||
match (toggle.is_toggleable(), toggle.is_toggled()) {
|
||||
(false, _) => div(),
|
||||
(_, true) => div().child(
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small),
|
||||
),
|
||||
(_, false) => div().child(
|
||||
IconElement::new(Icon::ChevronRight)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small),
|
||||
),
|
||||
}
|
||||
}
|
14
crates/ui2/src/components/tool_divider.rs
Normal file
14
crates/ui2/src/components/tool_divider.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ToolDivider;
|
||||
|
||||
impl ToolDivider {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().w_px().h_3().bg(cx.theme().colors().border)
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
use gpui2::AnyElement;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToolbarItem {}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Toolbar<V: 'static> {
|
||||
left_items: SmallVec<[AnyElement<V>; 2]>,
|
||||
right_items: SmallVec<[AnyElement<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Toolbar<V> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
left_items: SmallVec::new(),
|
||||
right_items: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_item(mut self, child: impl Component<V>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.left_items.push(child.render());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.left_items
|
||||
.extend(iter.into_iter().map(|item| item.render()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_item(mut self, child: impl Component<V>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.right_items.push(child.render());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.right_items
|
||||
.extend(iter.into_iter().map(|item| item.render()));
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.bg(cx.theme().colors().toolbar)
|
||||
.p_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.child(div().flex().children(self.left_items))
|
||||
.child(div().flex().children(self.right_items))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ToolbarStory;
|
||||
|
||||
impl Render for ToolbarStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Toolbar<Self>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
Toolbar::new()
|
||||
.left_item(Breadcrumb::new(
|
||||
PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
|
||||
vec![
|
||||
Symbol(vec![
|
||||
HighlightedText {
|
||||
text: "impl ".to_string(),
|
||||
color: cx.theme().syntax_color("keyword"),
|
||||
},
|
||||
HighlightedText {
|
||||
text: "ToolbarStory".to_string(),
|
||||
color: cx.theme().syntax_color("function"),
|
||||
},
|
||||
]),
|
||||
Symbol(vec![
|
||||
HighlightedText {
|
||||
text: "fn ".to_string(),
|
||||
color: cx.theme().syntax_color("keyword"),
|
||||
},
|
||||
HighlightedText {
|
||||
text: "render".to_string(),
|
||||
color: cx.theme().syntax_color("function"),
|
||||
},
|
||||
]),
|
||||
],
|
||||
))
|
||||
.right_items(vec![
|
||||
IconButton::new("toggle_inlay_hints", Icon::InlayHint),
|
||||
IconButton::new("buffer_search", Icon::MagnifyingGlass),
|
||||
IconButton::new("inline_assist", Icon::MagicWand),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TrafficLightColor {
|
||||
Red,
|
||||
Yellow,
|
||||
Green,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct TrafficLight {
|
||||
color: TrafficLightColor,
|
||||
window_has_focus: bool,
|
||||
}
|
||||
|
||||
impl TrafficLight {
|
||||
fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
|
||||
Self {
|
||||
color,
|
||||
window_has_focus,
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let system_colors = &cx.theme().styles.system;
|
||||
|
||||
let fill = match (self.window_has_focus, self.color) {
|
||||
(true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red,
|
||||
(true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow,
|
||||
(true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green,
|
||||
(false, _) => cx.theme().colors().element,
|
||||
};
|
||||
|
||||
div().w_3().h_3().rounded_full().bg(fill)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct TrafficLights {
|
||||
window_has_focus: bool,
|
||||
}
|
||||
|
||||
impl TrafficLights {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
window_has_focus: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
|
||||
self.window_has_focus = window_has_focus;
|
||||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(TrafficLight::new(
|
||||
TrafficLightColor::Red,
|
||||
self.window_has_focus,
|
||||
))
|
||||
.child(TrafficLight::new(
|
||||
TrafficLightColor::Yellow,
|
||||
self.window_has_focus,
|
||||
))
|
||||
.child(TrafficLight::new(
|
||||
TrafficLightColor::Green,
|
||||
self.window_has_focus,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
use crate::Story;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct TrafficLightsStory;
|
||||
|
||||
impl Render for TrafficLightsStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TrafficLights>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(TrafficLights::new())
|
||||
.child(Story::label(cx, "Unfocused"))
|
||||
.child(TrafficLights::new().window_has_focus(false))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,378 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use chrono::DateTime;
|
||||
use gpui2::{px, relative, Div, Render, Size, View, VisualContext};
|
||||
use settings2::Settings;
|
||||
use theme2::ThemeSettings;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, CollabPanel,
|
||||
EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel,
|
||||
PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
|
||||
Toast, ToastOrigin,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Gpui2UiDebug {
|
||||
pub in_livestream: bool,
|
||||
pub enable_user_settings: bool,
|
||||
pub show_toast: bool,
|
||||
}
|
||||
|
||||
impl Default for Gpui2UiDebug {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
in_livestream: false,
|
||||
enable_user_settings: false,
|
||||
show_toast: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Workspace {
|
||||
title_bar: View<TitleBar>,
|
||||
editor_1: View<EditorPane>,
|
||||
show_project_panel: bool,
|
||||
show_collab_panel: bool,
|
||||
show_chat_panel: bool,
|
||||
show_assistant_panel: bool,
|
||||
show_notifications_panel: bool,
|
||||
show_terminal: bool,
|
||||
show_debug: bool,
|
||||
show_language_selector: bool,
|
||||
debug: Gpui2UiDebug,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
title_bar: TitleBar::view(cx, None),
|
||||
editor_1: EditorPane::view(cx),
|
||||
show_project_panel: true,
|
||||
show_collab_panel: false,
|
||||
show_chat_panel: false,
|
||||
show_assistant_panel: false,
|
||||
show_terminal: true,
|
||||
show_language_selector: false,
|
||||
show_debug: false,
|
||||
show_notifications_panel: true,
|
||||
debug: Gpui2UiDebug::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_project_panel_open(&self) -> bool {
|
||||
self.show_project_panel
|
||||
}
|
||||
|
||||
pub fn toggle_project_panel(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_project_panel = !self.show_project_panel;
|
||||
|
||||
self.show_collab_panel = false;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_collab_panel_open(&self) -> bool {
|
||||
self.show_collab_panel
|
||||
}
|
||||
|
||||
pub fn toggle_collab_panel(&mut self) {
|
||||
self.show_collab_panel = !self.show_collab_panel;
|
||||
|
||||
self.show_project_panel = false;
|
||||
}
|
||||
|
||||
pub fn is_terminal_open(&self) -> bool {
|
||||
self.show_terminal
|
||||
}
|
||||
|
||||
pub fn toggle_terminal(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_terminal = !self.show_terminal;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_chat_panel_open(&self) -> bool {
|
||||
self.show_chat_panel
|
||||
}
|
||||
|
||||
pub fn toggle_chat_panel(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_chat_panel = !self.show_chat_panel;
|
||||
|
||||
self.show_assistant_panel = false;
|
||||
self.show_notifications_panel = false;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_notifications_panel_open(&self) -> bool {
|
||||
self.show_notifications_panel
|
||||
}
|
||||
|
||||
pub fn toggle_notifications_panel(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_notifications_panel = !self.show_notifications_panel;
|
||||
|
||||
self.show_chat_panel = false;
|
||||
self.show_assistant_panel = false;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_assistant_panel_open(&self) -> bool {
|
||||
self.show_assistant_panel
|
||||
}
|
||||
|
||||
pub fn toggle_assistant_panel(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_assistant_panel = !self.show_assistant_panel;
|
||||
|
||||
self.show_chat_panel = false;
|
||||
self.show_notifications_panel = false;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_language_selector_open(&self) -> bool {
|
||||
self.show_language_selector
|
||||
}
|
||||
|
||||
pub fn toggle_language_selector(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_language_selector = !self.show_language_selector;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_debug(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.show_debug = !self.show_debug;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn debug_toggle_user_settings(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.debug.enable_user_settings = !self.debug.enable_user_settings;
|
||||
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
|
||||
if self.debug.enable_user_settings {
|
||||
theme_settings.ui_font_size = 18.0.into();
|
||||
} else {
|
||||
theme_settings.ui_font_size = 16.0.into();
|
||||
}
|
||||
|
||||
ThemeSettings::override_global(theme_settings.clone(), cx);
|
||||
|
||||
cx.set_rem_size(theme_settings.ui_font_size);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn debug_toggle_livestream(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.debug.in_livestream = !self.debug.in_livestream;
|
||||
|
||||
self.title_bar = TitleBar::view(
|
||||
cx,
|
||||
Some(static_livestream()).filter(|_| self.debug.in_livestream),
|
||||
);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn debug_toggle_toast(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.debug.show_toast = !self.debug.show_toast;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self::new(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
|
||||
let root_group = PaneGroup::new_panes(
|
||||
vec![Pane::new(
|
||||
"pane-0",
|
||||
Size {
|
||||
width: relative(1.).into(),
|
||||
height: relative(1.).into(),
|
||||
},
|
||||
)
|
||||
.child(self.editor_1.clone())],
|
||||
SplitDirection::Horizontal,
|
||||
);
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans")
|
||||
.gap_0()
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.bg(cx.theme().colors().background)
|
||||
.child(self.title_bar.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.overflow_hidden()
|
||||
.border_t()
|
||||
.border_b()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("project-panel-outer", cx)
|
||||
.side(PanelSide::Left)
|
||||
.child(ProjectPanel::new("project-panel-inner")),
|
||||
)
|
||||
.filter(|_| self.is_project_panel_open()),
|
||||
)
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("collab-panel-outer", cx)
|
||||
.child(CollabPanel::new("collab-panel-inner"))
|
||||
.side(PanelSide::Left),
|
||||
)
|
||||
.filter(|_| self.is_collab_panel_open()),
|
||||
)
|
||||
// .child(NotificationToast::new(
|
||||
// "maxbrunsfeld has requested to add you as a contact.".into(),
|
||||
// ))
|
||||
.child(
|
||||
v_stack()
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.child(div().flex().flex_1().child(root_group))
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("terminal-panel", cx)
|
||||
.child(Terminal::new())
|
||||
.allowed_sides(PanelAllowedSides::BottomOnly)
|
||||
.side(PanelSide::Bottom),
|
||||
)
|
||||
.filter(|_| self.is_terminal_open()),
|
||||
),
|
||||
)
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("chat-panel-outer", cx)
|
||||
.side(PanelSide::Right)
|
||||
.child(ChatPanel::new("chat-panel-inner").messages(vec![
|
||||
ChatMessage::new(
|
||||
"osiewicz".to_string(),
|
||||
"is this thing on?".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
ChatMessage::new(
|
||||
"maxdeviant".to_string(),
|
||||
"Reading you loud and clear!".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
])),
|
||||
)
|
||||
.filter(|_| self.is_chat_panel_open()),
|
||||
)
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("notifications-panel-outer", cx)
|
||||
.side(PanelSide::Right)
|
||||
.child(NotificationsPanel::new("notifications-panel-inner")),
|
||||
)
|
||||
.filter(|_| self.is_notifications_panel_open()),
|
||||
)
|
||||
.children(
|
||||
Some(
|
||||
Panel::new("assistant-panel-outer", cx)
|
||||
.child(AssistantPanel::new("assistant-panel-inner")),
|
||||
)
|
||||
.filter(|_| self.is_assistant_panel_open()),
|
||||
),
|
||||
)
|
||||
.child(StatusBar::new())
|
||||
.when(self.debug.show_toast, |this| {
|
||||
this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
|
||||
})
|
||||
.children(
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(50.))
|
||||
.left(px(640.))
|
||||
.z_index(8)
|
||||
.child(LanguageSelector::new("language-selector")),
|
||||
)
|
||||
.filter(|_| self.is_language_selector_open()),
|
||||
)
|
||||
.z_index(8)
|
||||
// Debug
|
||||
.child(
|
||||
v_stack()
|
||||
.z_index(9)
|
||||
.absolute()
|
||||
.top_20()
|
||||
.left_1_4()
|
||||
.w_40()
|
||||
.gap_2()
|
||||
.when(self.show_debug, |this| {
|
||||
this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
|
||||
Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
|
||||
))
|
||||
.child(
|
||||
Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
|
||||
|workspace, cx| workspace.debug_toggle_toast(cx),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
|
||||
|workspace, cx| workspace.debug_toggle_livestream(cx),
|
||||
)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Button::<Workspace>::new("Toggle Debug")
|
||||
.on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use gpui2::VisualContext;
|
||||
|
||||
pub struct WorkspaceStory {
|
||||
workspace: View<Workspace>,
|
||||
}
|
||||
|
||||
impl WorkspaceStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self {
|
||||
workspace: Workspace::view(cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for WorkspaceStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
div().child(self.workspace.clone())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue