Merge branch 'main' into callback-handles

This commit is contained in:
Conrad Irwin 2023-11-20 12:21:42 -07:00
commit d0dd44faad
117 changed files with 3170 additions and 2027 deletions

View file

@ -49,13 +49,13 @@ use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0))
}
}
~~~
Every component needs a render method, and it should return `impl Component<V>`. This basic component will render a 16x16px yellow square on the screen.
Every component needs a render method, and it should return `impl Element<V>`. This basic component will render a 16x16px yellow square on the screen.
A couple of questions might come to mind:
@ -87,7 +87,7 @@ We can access the current theme's colors like this:
~~~rust
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0)
@ -102,7 +102,7 @@ use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div().size_4().bg(color.surface)
@ -117,7 +117,7 @@ use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div()

View file

@ -1,27 +1,16 @@
use gpui::img;
use crate::prelude::*;
use gpui::{img, Img, RenderOnce};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Avatar {
src: SharedString,
shape: Shape,
}
impl Avatar {
pub fn new(src: impl Into<SharedString>) -> Self {
Self {
src: src.into(),
shape: Shape::Circle,
}
}
impl<V: 'static> Component<V> for Avatar {
type Rendered = Img<V>;
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> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let mut img = img();
if self.shape == Shape::Circle {
@ -37,6 +26,20 @@ impl Avatar {
}
}
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
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -48,7 +51,7 @@ mod stories {
pub struct AvatarStory;
impl Render for AvatarStory {
impl Render<Self> for AvatarStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,8 @@
use std::sync::Arc;
use gpui::{
CallbackHandle, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext,
DefiniteLength, DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful,
StatefulInteractiveElement, WindowContext,
};
use crate::prelude::*;
@ -63,7 +64,6 @@ impl ButtonVariant {
}
}
// #[derive(Component)] <- todo
pub struct Button {
disabled: bool,
click_handler: Option<CallbackHandle<()>>,
@ -75,6 +75,57 @@ pub struct Button {
color: Option<TextColor>,
}
impl RenderOnce for Button {
type Element = Stateful<Div>;
fn render(self) -> Self::Rendered {
let (icon_color, label_color) = match (self.disabled, self.color) {
(true, _) => (TextColor::Disabled, TextColor::Disabled),
(_, None) => (TextColor::Default, TextColor::Default),
(_, Some(color)) => (TextColor::from(color), color),
};
let mut button = h_stack()
.id(SharedString::from(format!("{}", self.label)))
.relative()
.p_1()
.text_ui()
.rounded_md()
.bg(self.variant.bg_color(cx))
.cursor_pointer()
.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(label_color))
.children(self.render_icon(icon_color))
}
(Some(_), Some(IconPosition::Right)) => {
button = button
.gap_1()
.children(self.render_icon(icon_color))
.child(self.render_label(label_color))
}
(_, _) => button = button.child(self.render_label(label_color)),
}
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
}
}
impl Button {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
@ -150,72 +201,30 @@ impl Button {
fn render_icon(&self, icon_color: TextColor) -> Option<IconElement> {
self.icon.map(|i| IconElement::new(i).color(icon_color))
}
pub fn render(self, cx: &mut WindowContext) -> impl Component {
let (icon_color, label_color) = match (self.disabled, self.color) {
(true, _) => (TextColor::Disabled, TextColor::Disabled),
(_, None) => (TextColor::Default, TextColor::Default),
(_, Some(color)) => (TextColor::from(color), color),
};
let mut button = h_stack()
.id(SharedString::from(format!("{}", self.label)))
.relative()
.p_1()
.text_ui()
.rounded_md()
.bg(self.variant.bg_color(cx))
.cursor_pointer()
.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(label_color))
.children(self.render_icon(icon_color))
}
(Some(_), Some(IconPosition::Right)) => {
button = button
.gap_1()
.children(self.render_icon(icon_color))
.child(self.render_label(label_color))
}
(_, _) => button = button.child(self.render_label(label_color)),
}
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(RenderOnce)]
pub struct ButtonGroup {
buttons: Vec<Button>,
}
impl ButtonGroup {
pub fn new(buttons: Vec<Button>) -> Self {
Self { buttons }
}
impl Component for ButtonGroup {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> impl Component {
let mut el = h_stack().text_ui();
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let mut group = h_stack();
for button in self.buttons {
el = el.child(button.render(cx));
for button in self.buttons.into_iter() {
group = group.child(button.render(view, cx));
}
el
group
}
}
impl<V: 'static> ButtonGroup<V> {
pub fn new(buttons: Vec<Button<V>>) -> Self {
Self { buttons }
}
}

View file

@ -1,4 +1,4 @@
use gpui::{div, prelude::*, Component, ElementId, Styled, ViewContext};
use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext};
use std::sync::Arc;
use theme2::ActiveTheme;
@ -11,7 +11,7 @@ pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) +
/// 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.
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Checkbox<V: 'static> {
id: ElementId,
checked: Selection,
@ -19,6 +19,130 @@ pub struct Checkbox<V: 'static> {
on_click: Option<CheckHandler<V>>,
}
impl<V: 'static> Component<V> for Checkbox<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
// When selected, we show a checkmark.
Selection::Selected => {
Some(
IconElement::new(Icon::Check)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
} else {
TextColor::Selected
},
),
)
}
// In an indeterminate state, we show a dash.
Selection::Indeterminate => {
Some(
IconElement::new(Icon::Dash)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
} else {
TextColor::Selected
},
),
)
}
// When unselected, we show nothing.
Selection::Unselected => None,
};
// 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 == Selection::Selected || self.checked == Selection::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()
.id(self.id)
// 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 interactivity 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 interactivity 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)
})
})
.children(icon),
)
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
},
)
}
}
impl<V: 'static> Checkbox<V> {
pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
Self {
@ -42,7 +166,7 @@ impl<V: 'static> Checkbox<V> {
self
}
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
@ -175,7 +299,7 @@ mod stories {
pub struct CheckboxStory;
impl Render for CheckboxStory {
impl Render<Self> for CheckboxStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,12 +1,12 @@
use std::cell::RefCell;
use std::rc::Rc;
use crate::prelude::*;
use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
use crate::{prelude::*, v_stack, List, ListItem};
use crate::{ListEntry, ListSeparator, ListSubHeader};
use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
MouseDownEvent, Pixels, Point, Render, View, VisualContext, WeakView,
MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
};
pub enum ContextMenuItem<V> {
@ -24,15 +24,15 @@ pub struct ContextMenu<V> {
handle: WeakView<V>,
}
impl<V: Render> FocusableView for ContextMenu<V> {
impl<V: 'static> FocusableView for ContextMenu<V> {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl<V: Render> EventEmitter<Manager> for ContextMenu<V> {}
impl<V: 'static> EventEmitter<Manager> for ContextMenu<V> {}
impl<V: Render> ContextMenu<V> {
impl<V: 'static> ContextMenu<V> {
pub fn build(
cx: &mut ViewContext<V>,
f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
@ -86,7 +86,7 @@ impl<V: Render> ContextMenu<V> {
}
}
impl<V: Render> Render for ContextMenu<V> {
impl<V: 'static> Render<Self> for ContextMenu<V> {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -129,7 +129,7 @@ impl<V: Render> Render for ContextMenu<V> {
}
pub struct MenuHandle<V: 'static, M: ManagedView> {
id: Option<ElementId>,
id: ElementId,
child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>,
menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>,
@ -138,18 +138,13 @@ pub struct MenuHandle<V: 'static, M: ManagedView> {
}
impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
pub fn id(mut self, id: impl Into<ElementId>) -> Self {
self.id = Some(id.into());
self
}
pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self {
self.menu_builder = Some(Rc::new(f));
self
}
pub fn child<R: Component<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
self.child_builder = Some(Box::new(|b| f(b).render()));
pub fn child<R: RenderOnce<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
self.child_builder = Some(Box::new(|b| f(b).render_once().into_any()));
self
}
@ -167,9 +162,9 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
}
}
pub fn menu_handle<V: 'static, M: ManagedView>() -> MenuHandle<V, M> {
pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<V, M> {
MenuHandle {
id: None,
id: id.into(),
child_builder: None,
menu_builder: None,
anchor: None,
@ -185,18 +180,14 @@ pub struct MenuHandleState<V, M> {
menu_element: Option<AnyElement<V>>,
}
impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
type ElementState = MenuHandleState<V, M>;
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone().expect("menu_handle must have an id()"))
}
type State = MenuHandleState<V, M>;
fn layout(
&mut self,
view_state: &mut V,
element_state: Option<Self::ElementState>,
element_state: Option<Self::State>,
cx: &mut crate::ViewContext<V>,
) -> (gpui::LayoutId, Self::ElementState) {
) -> (gpui::LayoutId, Self::State) {
let (menu, position) = if let Some(element_state) = element_state {
(element_state.menu, element_state.position)
} else {
@ -212,9 +203,9 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
}
overlay = overlay.position(*position.borrow());
let mut view = overlay.child(menu.clone()).render();
menu_layout_id = Some(view.layout(view_state, cx));
view
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.layout(view_state, cx));
element
});
let mut child_element = self
@ -244,22 +235,22 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
}
fn paint(
&mut self,
self,
bounds: Bounds<gpui::Pixels>,
view_state: &mut V,
element_state: &mut Self::ElementState,
element_state: &mut Self::State,
cx: &mut crate::ViewContext<V>,
) {
if let Some(child) = element_state.child_element.as_mut() {
if let Some(child) = element_state.child_element.take() {
child.paint(view_state, cx);
}
if let Some(menu) = element_state.menu_element.as_mut() {
if let Some(menu) = element_state.menu_element.take() {
menu.paint(view_state, cx);
return;
}
let Some(builder) = self.menu_builder.clone() else {
let Some(builder) = self.menu_builder else {
return;
};
let menu = element_state.menu.clone();
@ -300,9 +291,15 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
}
}
impl<V: 'static, M: ManagedView> Component<V> for MenuHandle<V, M> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
type Element = Self;
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone())
}
fn render_once(self) -> Self::Element {
self
}
}
@ -312,12 +309,12 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::story::Story;
use crate::{story::Story, Label};
use gpui::{actions, Div, Render};
actions!(PrintCurrentDate, PrintBestFood);
fn build_menu<V: Render>(
fn build_menu<V: Render<V>>(
cx: &mut ViewContext<V>,
header: impl Into<SharedString>,
) -> View<ContextMenu<V>> {
@ -337,7 +334,7 @@ mod stories {
pub struct ContextMenuStory;
impl Render for ContextMenuStory {
impl Render<Self> for ContextMenuStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -360,28 +357,24 @@ mod stories {
.flex_col()
.justify_between()
.child(
menu_handle()
.id("test2")
menu_handle("test2")
.child(|is_open| {
Label::new(if is_open {
"TOP LEFT"
} else {
"RIGHT CLICK ME"
})
.render()
})
.menu(move |_, cx| build_menu(cx, "top left")),
)
.child(
menu_handle()
.id("test1")
menu_handle("test1")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM LEFT"
} else {
"RIGHT CLICK ME"
})
.render()
})
.anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft)
@ -394,29 +387,25 @@ mod stories {
.flex_col()
.justify_between()
.child(
menu_handle()
.id("test3")
menu_handle("test3")
.child(|is_open| {
Label::new(if is_open {
"TOP RIGHT"
} else {
"RIGHT CLICK ME"
})
.render()
})
.anchor(AnchorCorner::TopRight)
.menu(move |_, cx| build_menu(cx, "top right")),
)
.child(
menu_handle()
.id("test4")
menu_handle("test4")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM RIGHT"
} else {
"RIGHT CLICK ME"
})
.render()
})
.anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight)

View file

@ -1,13 +1,29 @@
use crate::prelude::*;
use crate::{v_stack, ButtonGroup};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Details<V: 'static> {
text: &'static str,
meta: Option<&'static str>,
actions: Option<ButtonGroup<V>>,
}
impl<V: 'static> Component<V> for Details<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.p_1()
.gap_0p5()
.text_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
}
impl<V: 'static> Details<V> {
pub fn new(text: &'static str) -> Self {
Self {
@ -26,20 +42,9 @@ impl<V: 'static> Details<V> {
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_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
@ -51,7 +56,7 @@ mod stories {
pub struct DetailsStory;
impl Render for DetailsStory {
impl Render<Self> for DetailsStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,3 +1,5 @@
use gpui::{Div, RenderOnce};
use crate::prelude::*;
enum DividerDirection {
@ -5,12 +7,29 @@ enum DividerDirection {
Vertical,
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Divider {
direction: DividerDirection,
inset: bool,
}
impl<V: 'static> Component<V> for Divider {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
}
DividerDirection::Vertical => {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(cx.theme().colors().border_variant)
}
}
impl Divider {
pub fn horizontal() -> Self {
Self {
@ -31,7 +50,7 @@ impl Divider {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {

View file

@ -1,19 +1,15 @@
use crate::prelude::*;
use crate::{Avatar, Player};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Facepile {
players: Vec<Player>,
}
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
players: players.collect(),
}
}
impl<V: 'static> Component<V> for Facepile {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
@ -26,6 +22,15 @@ impl Facepile {
}
}
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
players: players.collect(),
}
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
@ -37,7 +42,7 @@ mod stories {
pub struct FacepileStory;
impl Render for FacepileStory {
impl Render<Self> for FacepileStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{rems, svg};
use gpui::{rems, svg, RenderOnce, Svg};
use strum::EnumIter;
use crate::prelude::*;
@ -129,13 +129,30 @@ impl Icon {
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct IconElement {
path: SharedString,
color: TextColor,
size: IconSize,
}
impl<V: 'static> Component<V> for IconElement {
type Rendered = Svg<V>;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
};
svg()
.size(svg_size)
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
}
impl IconElement {
pub fn new(icon: Icon) -> Self {
Self {
@ -163,7 +180,7 @@ impl IconElement {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
@ -191,7 +208,7 @@ mod stories {
pub struct IconStory;
impl Render for IconStory {
impl Render<Self> for IconStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,5 @@
use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
use gpui::{prelude::*, Action, AnyView, MouseButton};
use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
use std::sync::Arc;
struct IconButtonHandlers<V: 'static> {
@ -12,7 +12,7 @@ impl<V: 'static> Default for IconButtonHandlers<V> {
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct IconButton<V: 'static> {
id: ElementId,
icon: Icon,
@ -24,6 +24,64 @@ pub struct IconButton<V: 'static> {
handlers: IconButtonHandlers<V>,
}
impl<V: 'static> Component<V> for IconButton<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let icon_color = match (self.state, self.color) {
(InteractionState::Disabled, _) => TextColor::Disabled,
(InteractionState::Active, _) => TextColor::Selected,
_ => self.color,
};
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
};
if self.selected {
bg_color = bg_hover_color;
}
let mut button = h_stack()
.id(self.id.clone())
.justify_center()
.rounded_md()
.p_1()
.bg(bg_color)
.cursor_pointer()
// Nate: Trying to figure out the right places we want to show a
// hover state here. I think it is a bit heavy to have it on every
// place we use an icon button.
// .hover(|style| style.bg(bg_hover_color))
.active(|style| style.bg(bg_active_color))
.child(IconElement::new(self.icon).color(icon_color));
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx);
})
}
if let Some(tooltip) = self.tooltip {
if !self.selected {
button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
}
}
button
}
}
impl<V: 'static> IconButton<V> {
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
Self {
@ -79,58 +137,4 @@ impl<V: 'static> IconButton<V> {
pub fn action(self, action: Box<dyn Action>) -> Self {
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
}
fn render(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let icon_color = match (self.state, self.color) {
(InteractionState::Disabled, _) => TextColor::Disabled,
(InteractionState::Active, _) => TextColor::Selected,
_ => self.color,
};
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
};
if self.selected {
bg_color = bg_hover_color;
}
let mut button = h_stack()
.id(self.id.clone())
.justify_center()
.rounded_md()
.p_1()
.bg(bg_color)
.cursor_pointer()
// Nate: Trying to figure out the right places we want to show a
// hover state here. I think it is a bit heavy to have it on every
// place we use an icon button.
// .hover(|style| style.bg(bg_hover_color))
.active(|style| style.bg(bg_active_color))
.child(IconElement::new(self.icon).color(icon_color));
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx);
})
}
if let Some(tooltip) = self.tooltip.take() {
if !self.selected {
button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
}
}
button
}
}

View file

@ -1,16 +1,30 @@
use gpui::px;
use crate::prelude::*;
use gpui::{px, Div, RenderOnce};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct UnreadIndicator;
impl UnreadIndicator {
pub fn new() -> Self {
Self
}
impl<V: 'static> Component<V> for UnreadIndicator {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
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)
}
}
impl UnreadIndicator {
pub fn new() -> Self {
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div()
.rounded_full()
.border_2()

View file

@ -1,5 +1,5 @@
use crate::{prelude::*, Label};
use gpui::prelude::*;
use gpui::{prelude::*, Div, RenderOnce, Stateful};
#[derive(Default, PartialEq)]
pub enum InputVariant {
@ -8,7 +8,7 @@ pub enum InputVariant {
Filled,
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Input {
placeholder: SharedString,
value: String,
@ -18,6 +18,57 @@ pub struct Input {
is_active: bool,
}
impl<V: 'static> Component<V> for Input {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
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 {
TextColor::Disabled
} else {
TextColor::Placeholder
});
let label = Label::new(self.value.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::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_ui_sm().map(move |this| {
if self.value.is_empty() {
this.child(placeholder_label)
} else {
this.child(label)
}
}))
}
}
impl Input {
pub fn new(placeholder: impl Into<SharedString>) -> Self {
Self {
@ -54,53 +105,6 @@ impl Input {
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 {
TextColor::Disabled
} else {
TextColor::Placeholder
});
let label = Label::new(self.value.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::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_ui_sm().map(|this| {
if self.value.is_empty() {
this.child(placeholder_label)
} else {
this.child(label)
}
}))
}
}
#[cfg(feature = "stories")]
@ -114,7 +118,7 @@ mod stories {
pub struct InputStory;
impl Render for InputStory {
impl Render<Self> for InputStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,9 +1,7 @@
use gpui::{actions, Action};
use strum::EnumIter;
use crate::prelude::*;
use gpui::{Action, Div, RenderOnce};
#[derive(Component, Clone)]
#[derive(RenderOnce, Clone)]
pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
@ -12,19 +10,10 @@ pub struct KeyBinding {
key_binding: gpui::KeyBinding,
}
impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
impl<V: 'static> Component<V> for KeyBinding {
type Rendered = Div<V>;
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.flex()
.gap_2()
@ -42,17 +31,29 @@ impl KeyBinding {
}
}
#[derive(Component)]
impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
}
#[derive(RenderOnce)]
pub struct Key {
key: SharedString,
}
impl Key {
pub fn new(key: impl Into<SharedString>) -> Self {
Self { key: key.into() }
}
impl<V: 'static> Component<V> for Key {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
div()
.px_2()
.py_0()
@ -64,20 +65,10 @@ impl Key {
}
}
// NOTE: The order the modifier keys appear in this enum impacts the order in
// which they are rendered in the UI.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
pub enum ModifierKey {
Control,
Alt,
Command,
Shift,
}
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
impl Key {
pub fn new(key: impl Into<SharedString>) -> Self {
Self { key: key.into() }
}
}
#[cfg(feature = "stories")]
@ -87,12 +78,18 @@ pub use stories::*;
mod stories {
use super::*;
pub use crate::KeyBinding;
use crate::{binding, Story};
use gpui::{Div, Render};
use crate::Story;
use gpui::{actions, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
impl Render for KeybindingStory {
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render<Self> for KeybindingStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{relative, Hsla, Text, TextRun, WindowContext};
use crate::prelude::*;
use crate::styled_ext::StyledExt;
use gpui::{relative, Div, Hsla, RenderOnce, StyledText, TextRun, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize {
@ -60,7 +59,7 @@ pub enum LineHeightStyle {
UILabel,
}
#[derive(Clone, Component)]
#[derive(Clone, RenderOnce)]
pub struct Label {
label: SharedString,
size: LabelSize,
@ -69,6 +68,33 @@ pub struct Label {
strikethrough: bool,
}
impl<V: 'static> Component<V> for Label {
type Rendered = Div<V>;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.when(self.strikethrough, |this| {
this.relative().child(
div()
.absolute()
.top_1_2()
.w_full()
.h_px()
.bg(TextColor::Hidden.color(cx)),
)
})
.map(|this| match self.size {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.when(self.line_height_style == LineHeightStyle::UILabel, |this| {
this.line_height(relative(1.))
})
.text_color(self.color.color(cx))
.child(self.label.clone())
}
}
impl Label {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
@ -99,32 +125,9 @@ impl Label {
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(TextColor::Hidden.color(cx)),
)
})
.map(|this| match self.size {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.when(self.line_height_style == LineHeightStyle::UILabel, |this| {
this.line_height(relative(1.))
})
.text_color(self.color.color(cx))
.child(self.label.clone())
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct HighlightedLabel {
label: SharedString,
size: LabelSize,
@ -133,35 +136,10 @@ pub struct HighlightedLabel {
strikethrough: bool,
}
impl HighlightedLabel {
/// shows a label with the given characters highlighted.
/// characters are identified by utf8 byte position.
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
Self {
label: label.into(),
size: LabelSize::Default,
color: TextColor::Default,
highlight_indices,
strikethrough: false,
}
}
impl<V: 'static> Component<V> for HighlightedLabel {
type Rendered = Div<V>;
pub fn size(mut self, size: LabelSize) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: TextColor) -> 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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let highlight_color = cx.theme().colors().text_accent;
let mut text_style = cx.text_style().clone();
@ -214,7 +192,36 @@ impl HighlightedLabel {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.child(Text::styled(self.label, runs))
.child(StyledText::new(self.label, runs))
}
}
impl HighlightedLabel {
/// shows a label with the given characters highlighted.
/// characters are identified by utf8 byte position.
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
Self {
label: label.into(),
size: LabelSize::Default,
color: TextColor::Default,
highlight_indices,
strikethrough: false,
}
}
pub fn size(mut self, size: LabelSize) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: TextColor) -> Self {
self.color = color;
self
}
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
self.strikethrough = strikethrough;
self
}
}
@ -235,7 +242,7 @@ mod stories {
pub struct LabelStory;
impl Render for LabelStory {
impl Render<Self> for LabelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{div, Div, RenderOnce, Stateful, StatefulInteractiveElement};
use std::rc::Rc;
use gpui::{div, Div, Stateful, StatefulInteractiveComponent};
use crate::settings::user_settings;
use crate::{
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
@ -24,7 +23,7 @@ pub enum ListHeaderMeta {
Text(Label),
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
@ -33,33 +32,10 @@ pub struct ListHeader {
toggle: Toggle,
}
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
impl<V: 'static> Component<V> for ListHeader {
type Rendered = Div<V>;
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let disclosure_control = disclosure_control(self.toggle);
let meta = match self.meta {
@ -81,11 +57,6 @@ impl ListHeader {
h_stack()
.w_full()
.bg(cx.theme().colors().surface_background)
// TODO: Add focus state
// .when(self.state == InteractionState::Focused, |this| {
// this.border()
// .border_color(cx.theme().colors().border_focused)
// })
.relative()
.child(
div()
@ -119,7 +90,94 @@ impl ListHeader {
}
}
#[derive(Component, Clone)]
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
// before_ship!("delete")
// fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
// let disclosure_control = disclosure_control(self.toggle);
// let meta = match self.meta {
// Some(ListHeaderMeta::Tools(icons)) => div().child(
// h_stack()
// .gap_2()
// .items_center()
// .children(icons.into_iter().map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// })),
// ),
// Some(ListHeaderMeta::Button(label)) => div().child(label),
// Some(ListHeaderMeta::Text(label)) => div().child(label),
// None => div(),
// };
// h_stack()
// .w_full()
// .bg(cx.theme().colors().surface_background)
// // TODO: Add focus state
// // .when(self.state == InteractionState::Focused, |this| {
// // this.border()
// // .border_color(cx.theme().colors().border_focused)
// // })
// .relative()
// .child(
// div()
// .h_5()
// .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
// .flex()
// .flex_1()
// .items_center()
// .justify_between()
// .w_full()
// .gap_1()
// .child(
// h_stack()
// .gap_1()
// .child(
// div()
// .flex()
// .gap_1()
// .items_center()
// .children(self.left_icon.map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// }))
// .child(Label::new(self.label.clone()).color(TextColor::Muted)),
// )
// .child(disclosure_control),
// )
// .child(meta),
// )
// }
}
#[derive(Clone)]
pub struct ListSubHeader {
label: SharedString,
left_icon: Option<Icon>,
@ -140,7 +198,7 @@ impl ListSubHeader {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
h_stack().flex_1().w_full().relative().py_1().child(
div()
.h_6()
@ -200,14 +258,6 @@ impl<V: 'static> From<ListSubHeader> for ListItem<V> {
}
impl<V: 'static> ListItem<V> {
fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> impl Component<V> {
match self {
ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
ListItem::Header(header) => div().child(header.render(view, cx)),
}
}
pub fn new(label: Label) -> Self {
Self::Entry(ListEntry::new(label))
}
@ -219,8 +269,17 @@ impl<V: 'static> ListItem<V> {
None
}
}
fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> Div<V> {
match self {
ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
ListItem::Header(header) => div().child(header.render(view, cx)),
}
}
}
// #[derive(RenderOnce)]
pub struct ListEntry<V> {
disabled: bool,
// TODO: Reintroduce this
@ -376,20 +435,24 @@ impl<V: 'static> ListEntry<V> {
}
}
#[derive(Clone, Component)]
#[derive(RenderOnce, Clone)]
pub struct ListSeparator;
impl ListSeparator {
pub fn new() -> Self {
Self
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
impl<V: 'static> Component<V> for ListSeparator {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().h_px().w_full().bg(cx.theme().colors().border_variant)
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct List<V: 'static> {
items: Vec<ListItem<V>>,
/// Message to display when the list is empty
@ -399,6 +462,31 @@ pub struct List<V: 'static> {
toggle: Toggle,
}
impl<V: 'static> Component<V> for List<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let list_content = match (self.items.is_empty(), self.toggle) {
(false, _) => div().children(
self.items
.into_iter()
.enumerate()
.map(|(ix, item)| item.render(view, ix, cx)),
),
(true, Toggle::Toggled(false)) => div(),
(true, _) => {
div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
}
};
v_stack()
.w_full()
.py_1()
.children(self.header.map(|header| header))
.child(list_content)
}
}
impl<V: 'static> List<V> {
pub fn new(items: Vec<ListItem<V>>) -> Self {
Self {
@ -424,7 +512,7 @@ impl<V: 'static> List<V> {
self
}
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let list_content = match (self.items.is_empty(), self.toggle) {
(false, _) => div().children(
self.items

View file

@ -1,9 +1,9 @@
use gpui::AnyElement;
use gpui::{AnyElement, Div, RenderOnce, Stateful};
use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Modal<V: 'static> {
id: ElementId,
title: Option<SharedString>,
@ -12,33 +12,11 @@ pub struct Modal<V: 'static> {
children: SmallVec<[AnyElement<V>; 2]>,
}
impl<V: 'static> Modal<V> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
impl<V: 'static> Component<V> for Modal<V> {
type Rendered = Stateful<V, Div<V>>;
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
pub fn primary_action(mut self, action: Button<V>) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button<V>) -> Self {
self.secondary_action = Some(action);
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
v_stack()
.id(self.id.clone())
.w_96()
@ -74,7 +52,34 @@ impl<V: 'static> Modal<V> {
}
}
impl<V: 'static> ParentComponent<V> for Modal<V> {
impl<V: 'static> Modal<V> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
pub fn primary_action(mut self, action: Button<V>) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button<V>) -> Self {
self.secondary_action = Some(action);
self
}
}
impl<V: 'static> ParentElement<V> for Modal<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}

View file

@ -3,7 +3,7 @@ use gpui::rems;
use crate::prelude::*;
use crate::{h_stack, Icon};
#[derive(Component)]
// #[derive(RenderOnce)]
pub struct NotificationToast {
label: SharedString,
icon: Option<Icon>,
@ -22,7 +22,7 @@ impl NotificationToast {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
h_stack()
.z_index(5)
.absolute()

View file

@ -1,7 +1,9 @@
use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Palette {
id: ElementId,
input_placeholder: SharedString,
@ -10,6 +12,70 @@ pub struct Palette {
default_order: OrderMethod,
}
impl<V: 'static> Component<V> for Palette {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.id(self.id)
.w_96()
.rounded_lg()
.bg(cx.theme().colors().elevated_surface_background)
.border()
.border_color(cx.theme().colors().border)
.child(
v_stack()
.gap_px()
.child(v_stack().py_0p5().px_1().child(
div().px_2().py_0p5().child(
Label::new(self.input_placeholder).color(TextColor::Placeholder),
),
))
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().colors().element_background),
)
.child(
v_stack()
.id("items")
.py_0p5()
.px_1()
.grow()
.max_h_96()
.overflow_y_scroll()
.children(
vec![if self.items.is_empty() {
Some(h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string).color(TextColor::Muted),
))
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.into_iter().enumerate().map(|(index, item)| {
h_stack()
.id(index)
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.active(|style| {
style.bg(cx.theme().colors().ghost_element_active)
})
.child(item)
})),
),
)
}
}
impl Palette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
@ -41,76 +107,33 @@ impl Palette {
self.default_order = default_order;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
v_stack()
.id(self.id.clone())
.w_96()
.rounded_lg()
.bg(cx.theme().colors().elevated_surface_background)
.border()
.border_color(cx.theme().colors().border)
.child(
v_stack()
.gap_px()
.child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
Label::new(self.input_placeholder.clone()).color(TextColor::Placeholder),
)))
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().colors().element_background),
)
.child(
v_stack()
.id("items")
.py_0p5()
.px_1()
.grow()
.max_h_96()
.overflow_y_scroll()
.children(
vec![if self.items.is_empty() {
Some(
h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string.clone())
.color(TextColor::Muted),
),
)
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.into_iter().enumerate().map(|(index, item)| {
h_stack()
.id(index)
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.active(|style| {
style.bg(cx.theme().colors().ghost_element_active)
})
.child(item)
})),
),
)
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
pub key_binding: Option<KeyBinding>,
}
impl<V: 'static> Component<V> for PaletteItem {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.flex()
.flex_row()
.grow()
.justify_between()
.child(
v_stack()
.child(Label::new(self.label))
.children(self.sublabel.map(|sublabel| Label::new(sublabel))),
)
.children(self.key_binding)
}
}
impl PaletteItem {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
@ -134,20 +157,6 @@ impl PaletteItem {
self.key_binding = key_binding.into();
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.flex_row()
.grow()
.justify_between()
.child(
v_stack()
.child(Label::new(self.label.clone()))
.children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
)
.children(self.key_binding)
}
}
use gpui::ElementId;
@ -164,7 +173,7 @@ mod stories {
pub struct PaletteStory;
impl Render for PaletteStory {
impl Render<Self> for PaletteStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{prelude::*, AbsoluteLength, AnyElement};
use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce, Stateful};
use smallvec::SmallVec;
use crate::prelude::*;
@ -38,7 +38,7 @@ pub enum PanelSide {
use std::collections::HashSet;
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Panel<V: 'static> {
id: ElementId,
current_side: PanelSide,
@ -49,6 +49,30 @@ pub struct Panel<V: 'static> {
children: SmallVec<[AnyElement<V>; 2]>,
}
impl<V: 'static> Component<V> for Panel<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let current_size = self.width.unwrap_or(self.initial_width);
v_stack()
.id(self.id.clone())
.flex_initial()
.map(|this| match self.current_side {
PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
PanelSide::Bottom => this,
})
.map(|this| match self.current_side {
PanelSide::Left => this.border_r(),
PanelSide::Right => this.border_l(),
PanelSide::Bottom => this.border_b().w_full().h(current_size),
})
.bg(cx.theme().colors().surface_background)
.border_color(cx.theme().colors().border)
.children(self.children)
}
}
impl<V: 'static> Panel<V> {
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
let settings = user_settings(cx);
@ -91,29 +115,9 @@ impl<V: 'static> Panel<V> {
}
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let current_size = self.width.unwrap_or(self.initial_width);
v_stack()
.id(self.id.clone())
.flex_initial()
.map(|this| match self.current_side {
PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
PanelSide::Bottom => this,
})
.map(|this| match self.current_side {
PanelSide::Left => this.border_r(),
PanelSide::Right => this.border_l(),
PanelSide::Bottom => this.border_b().w_full().h(current_size),
})
.bg(cx.theme().colors().surface_background)
.border_color(cx.theme().colors().border)
.children(self.children)
}
}
impl<V: 'static> ParentComponent<V> for Panel<V> {
impl<V: 'static> ParentElement<V> for Panel<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
@ -126,11 +130,11 @@ pub use stories::*;
mod stories {
use super::*;
use crate::{Label, Story};
use gpui::{Div, InteractiveComponent, Render};
use gpui::{Div, InteractiveElement, Render};
pub struct PanelStory;
impl Render for PanelStory {
impl Render<Self> for PanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,19 +1,17 @@
use gpui::{Div, RenderOnce};
use crate::prelude::*;
use crate::{Avatar, Facepile, PlayerWithCallStatus};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct PlayerStack {
player_with_call_status: PlayerWithCallStatus,
}
impl PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
player_with_call_status,
}
}
impl<V: 'static> Component<V> for PlayerStack {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let player = self.player_with_call_status.get_player();
let followers = self
@ -59,3 +57,11 @@ impl PlayerStack {
)
}
}
impl PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
player_with_call_status,
}
}
}

View file

@ -1,8 +1,8 @@
use crate::prelude::*;
use crate::{Icon, IconElement, Label, TextColor};
use gpui::{prelude::*, red, Div, ElementId, Render, View};
use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, Stateful, View};
#[derive(Component, Clone)]
#[derive(RenderOnce, Clone)]
pub struct Tab {
id: ElementId,
title: String,
@ -20,7 +20,7 @@ struct TabDragState {
title: String,
}
impl Render for TabDragState {
impl Render<Self> for TabDragState {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -28,65 +28,10 @@ impl Render for TabDragState {
}
}
impl Tab {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: "untitled".to_string(),
icon: None,
current: false,
dirty: false,
fs_status: FileSystemStatus::None,
git_status: GitStatus::None,
diagnostic_status: DiagnosticStatus::None,
close_side: IconSide::Right,
}
}
impl<V: 'static> Component<V> for Tab {
type Rendered = Stateful<V, Div<V>>;
pub fn current(mut self, current: bool) -> Self {
self.current = current;
self
}
pub fn title(mut self, title: String) -> Self {
self.title = title;
self
}
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Option<Icon>>,
{
self.icon = icon.into();
self
}
pub fn dirty(mut self, dirty: bool) -> Self {
self.dirty = dirty;
self
}
pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
self.fs_status = fs_status;
self
}
pub fn git_status(mut self, git_status: GitStatus) -> Self {
self.git_status = git_status;
self
}
pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
self.diagnostic_status = diagnostic_status;
self
}
pub fn close_side(mut self, close_side: IconSide) -> Self {
self.close_side = close_side;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
let is_deleted = self.fs_status == FileSystemStatus::Deleted;
@ -164,6 +109,65 @@ impl Tab {
}
}
impl Tab {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: "untitled".to_string(),
icon: None,
current: false,
dirty: false,
fs_status: FileSystemStatus::None,
git_status: GitStatus::None,
diagnostic_status: DiagnosticStatus::None,
close_side: IconSide::Right,
}
}
pub fn current(mut self, current: bool) -> Self {
self.current = current;
self
}
pub fn title(mut self, title: String) -> Self {
self.title = title;
self
}
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Option<Icon>>,
{
self.icon = icon.into();
self
}
pub fn dirty(mut self, dirty: bool) -> Self {
self.dirty = dirty;
self
}
pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
self.fs_status = fs_status;
self
}
pub fn git_status(mut self, git_status: GitStatus) -> Self {
self.git_status = git_status;
self
}
pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
self.diagnostic_status = diagnostic_status;
self
}
pub fn close_side(mut self, close_side: IconSide) -> Self {
self.close_side = close_side;
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -175,7 +179,7 @@ mod stories {
pub struct TabStory;
impl Render for TabStory {
impl Render<Self> for TabStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,6 @@
use crate::prelude::*;
use gpui::{prelude::*, AnyElement};
use gpui::{prelude::*, AnyElement, RenderOnce};
use gpui::{Div, Element};
use smallvec::SmallVec;
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@ -21,21 +22,16 @@ pub enum ToastOrigin {
/// they are actively showing the a process in progress.
///
/// Only one toast may be visible at a time.
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Toast<V: 'static> {
origin: ToastOrigin,
children: SmallVec<[AnyElement<V>; 2]>,
}
impl<V: 'static> Toast<V> {
pub fn new(origin: ToastOrigin) -> Self {
Self {
origin,
children: SmallVec::new(),
}
}
impl<V: 'static> Component<V> for Toast<V> {
type Rendered = Div<V>;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let mut div = div();
if self.origin == ToastOrigin::Bottom {
@ -58,7 +54,38 @@ impl<V: 'static> Toast<V> {
}
}
impl<V: 'static> ParentComponent<V> for Toast<V> {
impl<V: 'static> Toast<V> {
pub fn new(origin: ToastOrigin) -> Self {
Self {
origin,
children: SmallVec::new(),
}
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let mut div = div();
if self.origin == ToastOrigin::Bottom {
div = div.right_1_2();
} else {
div = div.right_2();
}
div.z_index(5)
.absolute()
.bottom_9()
.flex()
.py_1()
.px_1p5()
.rounded_lg()
.shadow_md()
.overflow_hidden()
.bg(cx.theme().colors().elevated_surface_background)
.children(self.children)
}
}
impl<V: 'static> ParentElement<V> for Toast<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
@ -77,7 +104,7 @@ mod stories {
pub struct ToastStory;
impl Render for ToastStory {
impl Render<Self> for ToastStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{div, Component, ParentComponent};
use gpui::{div, Element, ParentElement};
use crate::{Icon, IconElement, IconSize, TextColor};
@ -44,7 +44,7 @@ impl From<bool> for Toggle {
}
}
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Element<V> {
match (toggle.is_toggleable(), toggle.is_toggled()) {
(false, _) => div(),
(_, true) => div().child(

View file

@ -1,14 +1,23 @@
use crate::prelude::*;
use gpui::{Div, RenderOnce};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct ToolDivider;
impl<V: 'static> Component<V> for ToolDivider {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().w_px().h_3().bg(cx.theme().colors().border)
}
}
impl ToolDivider {
pub fn new() -> Self {
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().w_px().h_3().bg(cx.theme().colors().border)
}
}

View file

@ -1,4 +1,4 @@
use gpui::{overlay, Action, AnyView, Overlay, Render, VisualContext};
use gpui::{overlay, Action, AnyView, Overlay, Render, RenderOnce, VisualContext};
use settings2::Settings;
use theme2::{ActiveTheme, ThemeSettings};
@ -67,7 +67,7 @@ impl Tooltip {
}
}
impl Render for Tooltip {
impl Render<Self> for Tooltip {
type Element = Overlay<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,8 +1,8 @@
use gpui::rems;
use gpui::Rems;
pub use gpui::{
div, Component, Element, ElementId, InteractiveComponent, ParentComponent, SharedString,
Styled, ViewContext, WindowContext,
div, Component, Element, ElementId, InteractiveElement, ParentElement, SharedString, Styled,
ViewContext, WindowContext,
};
pub use crate::elevation::*;

View file

@ -745,11 +745,11 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> Edit
PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
vec![Symbol(vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "main".to_string(),
text: "main".into(),
color: cx.theme().syntax_color("function"),
},
])],
@ -779,15 +779,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine {
highlighted_texts: vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "main".to_string(),
text: "main".into(),
color: cx.theme().syntax_color("function"),
},
HighlightedText {
text: "() {".to_string(),
text: "() {".into(),
color: cx.theme().colors().text,
},
],
@ -803,7 +803,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: " // Statements here are executed when the compiled binary is called."
.to_string(),
.into(),
color: cx.theme().syntax_color("comment"),
}],
}),
@ -826,7 +826,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: " // Print text to the console.".to_string(),
text: " // Print text to the console.".into(),
color: cx.theme().syntax_color("comment"),
}],
}),
@ -841,15 +841,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine {
highlighted_texts: vec![
HighlightedText {
text: " println!(".to_string(),
text: " println!(".into(),
color: cx.theme().colors().text,
},
HighlightedText {
text: "\"Hello, world!\"".to_string(),
text: "\"Hello, world!\"".into(),
color: cx.theme().syntax_color("string"),
},
HighlightedText {
text: ");".to_string(),
text: ");".into(),
color: cx.theme().colors().text,
},
],
@ -864,7 +864,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "}".to_string(),
text: "}".into(),
color: cx.theme().colors().text,
}],
}),
@ -882,11 +882,11 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPa
PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
vec![Symbol(vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "main".to_string(),
text: "main".into(),
color: cx.theme().syntax_color("function"),
},
])],
@ -916,15 +916,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine {
highlighted_texts: vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "main".to_string(),
text: "main".into(),
color: cx.theme().syntax_color("function"),
},
HighlightedText {
text: "() {".to_string(),
text: "() {".into(),
color: cx.theme().colors().text,
},
],
@ -940,7 +940,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "// Statements here are executed when the compiled binary is called."
.to_string(),
.into(),
color: cx.theme().syntax_color("comment"),
}],
}),
@ -963,7 +963,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: " // Print text to the console.".to_string(),
text: " // Print text to the console.".into(),
color: cx.theme().syntax_color("comment"),
}],
}),
@ -978,15 +978,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine {
highlighted_texts: vec![
HighlightedText {
text: " println!(".to_string(),
text: " println!(".into(),
color: cx.theme().colors().text,
},
HighlightedText {
text: "\"Hello, world!\"".to_string(),
text: "\"Hello, world!\"".into(),
color: cx.theme().syntax_color("string"),
},
HighlightedText {
text: ");".to_string(),
text: ");".into(),
color: cx.theme().colors().text,
},
],
@ -1001,7 +1001,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "}".to_string(),
text: "}".into(),
color: cx.theme().colors().text,
}],
}),
@ -1015,7 +1015,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "".to_string(),
text: "".into(),
color: cx.theme().colors().text,
}],
}),
@ -1029,7 +1029,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "// Marshall and Nate were here".to_string(),
text: "// Marshall and Nate were here".into(),
color: cx.theme().syntax_color("comment"),
}],
}),
@ -1042,7 +1042,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
pub fn terminal_buffer(cx: &AppContext) -> Buffer {
Buffer::new("terminal")
.set_title("zed — fish".to_string())
.set_title(Some("zed — fish".into()))
.set_rows(Some(BufferRows {
show_line_numbers: false,
rows: terminal_buffer_rows(cx),
@ -1060,31 +1060,31 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine {
highlighted_texts: vec![
HighlightedText {
text: "maxdeviant ".to_string(),
text: "maxdeviant ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "in ".to_string(),
text: "in ".into(),
color: cx.theme().colors().text,
},
HighlightedText {
text: "profaned-capital ".to_string(),
text: "profaned-capital ".into(),
color: cx.theme().syntax_color("function"),
},
HighlightedText {
text: "in ".to_string(),
text: "in ".into(),
color: cx.theme().colors().text,
},
HighlightedText {
text: "~/p/zed ".to_string(),
text: "~/p/zed ".into(),
color: cx.theme().syntax_color("function"),
},
HighlightedText {
text: "on ".to_string(),
text: "on ".into(),
color: cx.theme().colors().text,
},
HighlightedText {
text: " gpui2-ui ".to_string(),
text: " gpui2-ui ".into(),
color: cx.theme().syntax_color("keyword"),
},
],
@ -1099,7 +1099,7 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false,
line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText {
text: "λ ".to_string(),
text: "λ ".into(),
color: cx.theme().syntax_color("string"),
}],
}),

View file

@ -15,23 +15,29 @@ impl Story {
.bg(cx.theme().colors().background)
}
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
pub fn title<V: 'static>(
cx: &mut ViewContext<V>,
title: impl Into<SharedString>,
) -> impl Element<V> {
div()
.text_xl()
.text_color(cx.theme().colors().text)
.child(title.to_owned())
.child(title.into())
}
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
Self::title(cx, std::any::type_name::<T>())
}
pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
pub fn label<V: 'static>(
cx: &mut ViewContext<V>,
label: impl Into<SharedString>,
) -> impl Element<V> {
div()
.mt_4()
.mb_2()
.text_xs()
.text_color(cx.theme().colors().text)
.child(label.to_owned())
.child(label.into())
}
}

View file

@ -1,27 +1,17 @@
use crate::prelude::*;
use crate::{Icon, IconButton, Label, Panel, PanelSide};
use gpui::{prelude::*, rems, AbsoluteLength};
use gpui::{prelude::*, rems, AbsoluteLength, RenderOnce};
#[derive(Component)]
#[derive(RenderOnce)]
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(),
}
}
impl<V: 'static> Component<V> for AssistantPanel {
type Rendered = Panel<V>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
Panel::new(self.id.clone(), cx)
.children(vec![div()
.flex()
@ -64,12 +54,26 @@ impl AssistantPanel {
.overflow_y_scroll()
.child(Label::new("Is this thing on?")),
)
.render()])
.into_any()])
.side(self.current_side)
.width(AbsoluteLength::Rems(rems(32.)))
}
}
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
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -80,7 +84,7 @@ mod stories {
use gpui::{Div, Render};
pub struct AssistantPanelStory;
impl Render for AssistantPanelStory {
impl Render<Self> for AssistantPanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,30 +1,21 @@
use crate::{h_stack, prelude::*, HighlightedText};
use gpui::{prelude::*, Div};
use gpui::{prelude::*, Div, Stateful};
use std::path::PathBuf;
#[derive(Clone)]
pub struct Symbol(pub Vec<HighlightedText>);
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Breadcrumb {
path: PathBuf,
symbols: Vec<Symbol>,
}
impl Breadcrumb {
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
Self { path, symbols }
}
impl<V: 'static> Component<V> for Breadcrumb {
type Rendered = Stateful<V, Div<V>>;
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> {
fn render(self, view_state: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let symbols_len = self.symbols.len();
h_stack()
.id("breadcrumb")
.px_1()
@ -33,7 +24,9 @@ impl Breadcrumb {
.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(SharedString::from(
self.path.clone().to_str().unwrap().to_string(),
))
.child(if !self.symbols.is_empty() {
self.render_separator(cx)
} else {
@ -64,6 +57,18 @@ impl Breadcrumb {
}
}
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)
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -76,7 +81,7 @@ mod stories {
pub struct BreadcrumbStory;
impl Render for BreadcrumbStory {
impl Render<Self> for BreadcrumbStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -88,21 +93,21 @@ mod stories {
vec![
Symbol(vec![
HighlightedText {
text: "impl ".to_string(),
text: "impl ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "BreadcrumbStory".to_string(),
text: "BreadcrumbStory".into(),
color: cx.theme().syntax_color("function"),
},
]),
Symbol(vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "render".to_string(),
text: "render".into(),
color: cx.theme().syntax_color("function"),
},
]),

View file

@ -1,4 +1,4 @@
use gpui::{Hsla, WindowContext};
use gpui::{Div, Hsla, RenderOnce, WindowContext};
use crate::prelude::*;
use crate::{h_stack, v_stack, Icon, IconElement};
@ -11,7 +11,7 @@ pub struct PlayerCursor {
#[derive(Default, PartialEq, Clone)]
pub struct HighlightedText {
pub text: String,
pub text: SharedString,
pub color: Hsla,
}
@ -107,7 +107,7 @@ impl BufferRow {
}
}
#[derive(Component, Clone)]
#[derive(RenderOnce, Clone)]
pub struct Buffer {
id: ElementId,
rows: Option<BufferRows>,
@ -117,6 +117,21 @@ pub struct Buffer {
path: Option<String>,
}
impl<V: 'static> Component<V> for Buffer {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let rows = self.render_rows(cx);
v_stack()
.flex_1()
.w_full()
.h_full()
.bg(cx.theme().colors().editor_background)
.children(rows)
}
}
impl Buffer {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
@ -154,7 +169,7 @@ impl Buffer {
self
}
fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> {
fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Element<V> {
let line_background = if row.current {
cx.theme().colors().editor_active_line_background
} else {
@ -186,7 +201,7 @@ impl Buffer {
h_stack().justify_end().px_0p5().w_3().child(
div()
.text_color(line_number_color)
.child(row.line_number.to_string()),
.child(SharedString::from(row.line_number.to_string())),
),
)
})
@ -202,7 +217,7 @@ impl Buffer {
}))
}
fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<V>> {
fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Element<V>> {
match &self.rows {
Some(rows) => rows
.rows
@ -213,7 +228,7 @@ impl Buffer {
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let rows = self.render_rows(cx);
v_stack()
@ -239,7 +254,7 @@ mod stories {
pub struct BufferStory;
impl Render for BufferStory {
impl Render<Self> for BufferStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{Div, Render, View, VisualContext};
use crate::prelude::*;
use crate::{h_stack, Icon, IconButton, Input, TextColor};
use gpui::{Div, Render, RenderOnce, View, VisualContext};
#[derive(Clone)]
pub struct BufferSearch {
@ -26,7 +25,7 @@ impl BufferSearch {
}
}
impl Render for BufferSearch {
impl Render<Self> for BufferSearch {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

View file

@ -1,27 +1,17 @@
use crate::{prelude::*, Icon, IconButton, Input, Label};
use chrono::NaiveDateTime;
use gpui::prelude::*;
use gpui::{prelude::*, Div, Stateful};
#[derive(Component)]
#[derive(RenderOnce)]
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(),
}
}
impl<V: 'static> Component<V> for ChatPanel {
type Rendered = Stateful<V, Div<V>>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.id(self.element_id.clone())
.flex()
@ -67,23 +57,31 @@ impl ChatPanel {
}
}
#[derive(Component)]
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
}
}
#[derive(RenderOnce)]
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,
}
}
impl<V: 'static> Component<V> for ChatMessage {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.flex()
.flex_col()
@ -101,6 +99,16 @@ impl ChatMessage {
}
}
impl ChatMessage {
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
Self {
author,
text,
sent_at,
}
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -115,7 +123,7 @@ mod stories {
pub struct ChatPanelStory;
impl Render for ChatPanelStory {
impl Render<Self> for ChatPanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -2,19 +2,17 @@ use crate::{
prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon,
List, ListHeader, Toggle,
};
use gpui::prelude::*;
use gpui::{prelude::*, Div, Stateful};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct CollabPanel {
id: ElementId,
}
impl CollabPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for CollabPanel {
type Rendered = Stateful<V, Div<V>>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.id(self.id.clone())
.h_full()
@ -86,6 +84,12 @@ impl CollabPanel {
}
}
impl CollabPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@ -97,7 +101,7 @@ mod stories {
pub struct CollabPanelStory;
impl Render for CollabPanelStory {
impl Render<Self> for CollabPanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*;
use crate::{example_editor_actions, OrderMethod, Palette};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct CommandPalette {
id: ElementId,
}
impl CommandPalette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for CommandPalette {
type Rendered = Stateful<V, Div<V>>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(example_editor_actions())
@ -22,6 +20,13 @@ impl CommandPalette {
}
}
impl CommandPalette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")]
pub use stories::*;
@ -35,7 +40,7 @@ mod stories {
pub struct CommandPaletteStory;
impl Render for CommandPaletteStory {
impl Render<Self> for CommandPaletteStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,39 +1,42 @@
use crate::{prelude::*, Button, Label, Modal, TextColor};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct CopilotModal {
id: ElementId,
}
impl<V: 'static> Component<V> for CopilotModal {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
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(TextColor::Muted))
.primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
)
}
}
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(TextColor::Muted))
.primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
)
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use crate::Story;
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct CopilotModalStory;
impl Render for CopilotModalStory {
impl Render<Self> for CopilotModalStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,6 +1,6 @@
use std::path::PathBuf;
use gpui::{Div, Render, View, VisualContext};
use gpui::{Div, Render, RenderOnce, View, VisualContext};
use crate::prelude::*;
use crate::{
@ -47,7 +47,7 @@ impl EditorPane {
}
}
impl Render for EditorPane {
impl Render<Self> for EditorPane {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

View file

@ -1,17 +1,15 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct LanguageSelector {
id: ElementId,
}
impl LanguageSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for LanguageSelector {
type Rendered = Stateful<V, Div<V>>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(vec![
@ -33,6 +31,34 @@ impl LanguageSelector {
}
}
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 Element<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),
)
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")]
pub use stories::*;
@ -44,7 +70,7 @@ mod stories {
pub struct LanguageSelectorStory;
impl Render for LanguageSelectorStory {
impl Render<Self> for LanguageSelectorStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*;
use crate::{v_stack, Buffer, Icon, IconButton, Label};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct MultiBuffer {
buffers: Vec<Buffer>,
}
impl MultiBuffer {
pub fn new(buffers: Vec<Buffer>) -> Self {
Self { buffers }
}
impl<V: 'static> Component<V> for MultiBuffer {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.w_full()
.h_full()
@ -33,6 +31,13 @@ impl MultiBuffer {
}
}
impl MultiBuffer {
pub fn new(buffers: Vec<Buffer>) -> Self {
Self { buffers }
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
@ -44,7 +49,7 @@ mod stories {
pub struct MultiBufferStory;
impl Render for MultiBufferStory {
impl Render<Self> for MultiBufferStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -3,19 +3,17 @@ use crate::{
v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle,
ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator,
};
use gpui::prelude::*;
use gpui::{prelude::*, Div, Stateful};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct NotificationsPanel {
id: ElementId,
}
impl NotificationsPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for NotificationsPanel {
type Rendered = Stateful<V, Div<V>>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.id(self.id.clone())
.flex()
@ -56,6 +54,12 @@ impl NotificationsPanel {
}
}
impl NotificationsPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
pub struct NotificationAction<V: 'static> {
button: ButtonOrIconButton<V>,
tooltip: SharedString,
@ -102,7 +106,7 @@ impl<V: 'static> Default for NotificationHandlers<V> {
}
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Notification<V: 'static> {
id: ElementId,
slot: ActorOrIcon,
@ -116,6 +120,85 @@ pub struct Notification<V: 'static> {
handlers: NotificationHandlers<V>,
}
impl<V: 'static> Component<V> for Notification<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
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(TextColor::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::TextColor::Muted)
}))
.child(
Label::new(action_taken.taken_message.1.clone())
.color(TextColor::Muted),
),
// Show the actions
(Some(actions), None) => {
h_stack().children(actions.map(|action| match action.button {
ButtonOrIconButton::Button(button) => {
button.render_into_any()
}
ButtonOrIconButton::IconButton(icon_button) => {
icon_button.render_into_any()
}
}))
}
}),
),
)
}
}
impl<V> Notification<V> {
fn new(
id: ElementId,
@ -241,7 +324,7 @@ impl<V> Notification<V> {
self
}
fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
if let Some(meta) = &self.meta {
h_stack().children(
meta.items
@ -260,87 +343,12 @@ impl<V> Notification<V> {
}
}
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
match &self.slot {
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(),
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render_into_any(),
ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render_into_any(),
}
}
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(TextColor::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::TextColor::Muted)
}))
.child(
Label::new(action_taken.taken_message.1.clone())
.color(TextColor::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;
@ -356,7 +364,7 @@ mod stories {
pub struct NotificationsPanelStory;
impl Render for NotificationsPanelStory {
impl Render<Self> for NotificationsPanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,7 @@
use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
use gpui::{
hsla, red, AnyElement, Div, ElementId, ExternalPaths, Hsla, Length, RenderOnce, Size, Stateful,
View,
};
use smallvec::SmallVec;
use crate::prelude::*;
@ -10,7 +13,7 @@ pub enum SplitDirection {
Vertical,
}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Pane<V: 'static> {
id: ElementId,
size: Size<Length>,
@ -18,24 +21,10 @@ pub struct Pane<V: 'static> {
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
impl<V: 'static> Component<V> for Pane<V> {
type Rendered = Stateful<V, Div<V>>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.id(self.id.clone())
.flex()
@ -59,37 +48,41 @@ impl<V: 'static> Pane<V> {
}
}
impl<V: 'static> ParentComponent<V> for Pane<V> {
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
}
}
impl<V: 'static> ParentElement<V> for Pane<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
#[derive(Component)]
#[derive(RenderOnce)]
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,
}
}
impl<V: 'static> Component<V> for PaneGroup<V> {
type Rendered = Div<V>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
if !self.panes.is_empty() {
let el = div()
.flex()
@ -126,3 +119,21 @@ impl<V: 'static> PaneGroup<V> {
unreachable!()
}
}
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,
}
}
}

View file

@ -3,18 +3,56 @@ use crate::{
ListHeader,
};
use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)]
#[derive(RenderOnce)]
pub struct ProjectPanel {
id: ElementId,
}
impl<V: 'static> Component<V> for ProjectPanel {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.id(self.id.clone())
.flex()
.flex_col()
.size_full()
.bg(cx.theme().colors().surface_background)
.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"))
.empty_message("No files in directory"),
)
.child(
List::new(static_project_panel_project_items())
.header(ListHeader::new("PROJECT"))
.empty_message("No folders in directory"),
),
)
.child(
Input::new("Find something...")
.value("buffe".to_string())
.state(InteractionState::Focused),
)
}
}
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> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div()
.id(self.id.clone())
.flex()
@ -59,7 +97,7 @@ mod stories {
pub struct ProjectPanelStory;
impl Render for ProjectPanelStory {
impl Render<Self> for ProjectPanelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct RecentProjects {
id: ElementId,
}
impl RecentProjects {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for RecentProjects {
type Rendered = Stateful<V, Div<V>>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(vec![
@ -29,6 +27,13 @@ impl RecentProjects {
}
}
impl RecentProjects {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")]
pub use stories::*;
@ -40,7 +45,7 @@ mod stories {
pub struct RecentProjectsStory;
impl Render for RecentProjectsStory {
impl Render<Self> for RecentProjectsStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,7 @@
use std::sync::Arc;
use gpui::{Div, RenderOnce};
use crate::prelude::*;
use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace};
@ -28,14 +30,31 @@ impl Default for ToolGroup {
}
}
#[derive(Component)]
#[component(view_type = "Workspace")]
#[derive(RenderOnce)]
#[view = "Workspace"]
pub struct StatusBar {
left_tools: Option<ToolGroup>,
right_tools: Option<ToolGroup>,
bottom_tools: Option<ToolGroup>,
}
impl Component<Workspace> for StatusBar {
type Rendered = Div<Workspace>;
fn render(self, view: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Self::Rendered {
div()
.py_0p5()
.px_1()
.flex()
.items_center()
.justify_between()
.w_full()
.bg(cx.theme().colors().status_bar_background)
.child(self.left_tools(view, cx))
.child(self.right_tools(view, cx))
}
}
impl StatusBar {
pub fn new() -> Self {
Self {
@ -81,28 +100,7 @@ impl StatusBar {
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_background)
.child(self.left_tools(view, cx))
.child(self.right_tools(view, cx))
}
fn left_tools(
&self,
workspace: &mut Workspace,
cx: &WindowContext,
) -> impl Component<Workspace> {
fn left_tools(&self, workspace: &mut Workspace, cx: &WindowContext) -> impl Element<Workspace> {
div()
.flex()
.items_center()
@ -133,7 +131,7 @@ impl StatusBar {
&self,
workspace: &mut Workspace,
cx: &WindowContext,
) -> impl Component<Workspace> {
) -> impl Element<Workspace> {
div()
.flex()
.items_center()

View file

@ -1,7 +1,9 @@
use crate::{prelude::*, Icon, IconButton, Tab};
use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)]
#[derive(RenderOnce)]
pub struct TabBar {
id: ElementId,
/// Backwards, Forwards
@ -9,21 +11,10 @@ pub struct TabBar {
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,
}
}
impl<V: 'static> Component<V> for TabBar {
type Rendered = Stateful<V, Div<V>>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let (can_navigate_back, can_navigate_forward) = self.can_navigate;
div()
@ -92,6 +83,21 @@ impl TabBar {
}
}
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
}
}
use gpui::ElementId;
#[cfg(feature = "stories")]
pub use stories::*;
@ -104,7 +110,7 @@ mod stories {
pub struct TabBarStory;
impl Render for TabBarStory {
impl Render<Self> for TabBarStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,84 @@
use gpui::{relative, rems, Size};
use crate::prelude::*;
use crate::{Icon, IconButton, Pane, Tab};
use gpui::{relative, rems, Div, RenderOnce, Size};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Terminal;
impl<V: 'static> Component<V> for Terminal {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
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_background)
.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)),
)
}
}
impl Terminal {
pub fn new() -> Self {
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let can_navigate_back = true;
let can_navigate_forward = false;
@ -86,7 +153,7 @@ mod stories {
use gpui::{Div, Render};
pub struct TerminalStory;
impl Render for TerminalStory {
impl Render<Self> for TerminalStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,16 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Component)]
#[derive(RenderOnce)]
pub struct ThemeSelector {
id: ElementId,
}
impl ThemeSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
impl<V: 'static> Component<V> for ThemeSelector {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let cx: &mut ViewContext<V> = cx;
div().child(
Palette::new(self.id.clone())
.items(vec![
@ -34,6 +33,13 @@ impl ThemeSelector {
}
}
impl ThemeSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
@ -47,7 +53,7 @@ mod stories {
pub struct ThemeSelectorStory;
impl Render for ThemeSelectorStory {
impl Render<Self> for ThemeSelectorStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,7 @@
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use gpui::{Div, Render, View, VisualContext};
use gpui::{Div, Render, RenderOnce, View, VisualContext};
use crate::prelude::*;
use crate::settings::user_settings;
@ -85,7 +85,7 @@ impl TitleBar {
}
}
impl Render for TitleBar {
impl Render<Self> for TitleBar {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
@ -205,7 +205,7 @@ mod stories {
}
}
impl Render for TitleBarStory {
impl Render<Self> for TitleBarStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

View file

@ -1,4 +1,4 @@
use gpui::AnyElement;
use gpui::{AnyElement, Div, RenderOnce};
use smallvec::SmallVec;
use crate::prelude::*;
@ -6,12 +6,26 @@ use crate::prelude::*;
#[derive(Clone)]
pub struct ToolbarItem {}
#[derive(Component)]
#[derive(RenderOnce)]
pub struct Toolbar<V: 'static> {
left_items: SmallVec<[AnyElement<V>; 2]>,
right_items: SmallVec<[AnyElement<V>; 2]>,
}
impl<V: 'static> Component<V> for Toolbar<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.bg(cx.theme().colors().toolbar_background)
.p_2()
.flex()
.justify_between()
.child(div().flex().children(self.left_items))
.child(div().flex().children(self.right_items))
}
}
impl<V: 'static> Toolbar<V> {
pub fn new() -> Self {
Self {
@ -20,49 +34,39 @@ impl<V: 'static> Toolbar<V> {
}
}
pub fn left_item(mut self, child: impl Component<V>) -> Self
pub fn left_item(mut self, child: impl RenderOnce<V>) -> Self
where
Self: Sized,
{
self.left_items.push(child.render());
self.left_items.push(child.render_into_any());
self
}
pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
pub fn left_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
where
Self: Sized,
{
self.left_items
.extend(iter.into_iter().map(|item| item.render()));
.extend(iter.into_iter().map(|item| item.render_into_any()));
self
}
pub fn right_item(mut self, child: impl Component<V>) -> Self
pub fn right_item(mut self, child: impl RenderOnce<V>) -> Self
where
Self: Sized,
{
self.right_items.push(child.render());
self.right_items.push(child.render_into_any());
self
}
pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
pub fn right_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
where
Self: Sized,
{
self.right_items
.extend(iter.into_iter().map(|item| item.render()));
.extend(iter.into_iter().map(|item| item.render_into_any()));
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.bg(cx.theme().colors().toolbar_background)
.p_2()
.flex()
.justify_between()
.child(div().flex().children(self.left_items))
.child(div().flex().children(self.right_items))
}
}
#[cfg(feature = "stories")]
@ -81,7 +85,7 @@ mod stories {
pub struct ToolbarStory;
impl Render for ToolbarStory {
impl Render<Self> for ToolbarStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -95,21 +99,21 @@ mod stories {
vec![
Symbol(vec![
HighlightedText {
text: "impl ".to_string(),
text: "impl ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "ToolbarStory".to_string(),
text: "ToolbarStory".into(),
color: cx.theme().syntax_color("function"),
},
]),
Symbol(vec![
HighlightedText {
text: "fn ".to_string(),
text: "fn ".into(),
color: cx.theme().syntax_color("keyword"),
},
HighlightedText {
text: "render".to_string(),
text: "render".into(),
color: cx.theme().syntax_color("function"),
},
]),

View file

@ -7,21 +7,16 @@ enum TrafficLightColor {
Green,
}
#[derive(Component)]
#[derive(RenderOnce)]
struct TrafficLight {
color: TrafficLightColor,
window_has_focus: bool,
}
impl TrafficLight {
fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
Self {
color,
window_has_focus,
}
}
impl<V: 'static> Component<V> for TrafficLight {
type Rendered = Div<V>;
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let system_colors = &cx.theme().styles.system;
let fill = match (self.window_has_focus, self.color) {
@ -35,24 +30,24 @@ impl TrafficLight {
}
}
#[derive(Component)]
impl TrafficLight {
fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
Self {
color,
window_has_focus,
}
}
}
#[derive(RenderOnce)]
pub struct TrafficLights {
window_has_focus: bool,
}
impl TrafficLights {
pub fn new() -> Self {
Self {
window_has_focus: true,
}
}
impl<V: 'static> Component<V> for TrafficLights {
type Rendered = Div<V>;
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> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.flex()
.items_center()
@ -72,6 +67,20 @@ impl TrafficLights {
}
}
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
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
@ -85,7 +94,7 @@ mod stories {
pub struct TrafficLightsStory;
impl Render for TrafficLightsStory {
impl Render<Self> for TrafficLightsStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use chrono::DateTime;
use gpui::{px, relative, Div, Render, Size, View, VisualContext};
use gpui::{px, relative, Div, Render, RenderOnce, Size, View, VisualContext};
use settings2::Settings;
use theme2::ThemeSettings;
@ -191,7 +191,7 @@ impl Workspace {
}
}
impl Render for Workspace {
impl Render<Self> for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
@ -388,7 +388,7 @@ mod stories {
}
}
impl Render for WorkspaceStory {
impl Render<Self> for WorkspaceStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {