Add disclosable components into channels

Rename components to more closely match their purpose
This commit is contained in:
Mikayla 2023-08-19 05:18:53 -07:00
parent 2d37128693
commit bd3ab82dac
No known key found for this signature in database
10 changed files with 359 additions and 194 deletions

View file

@ -114,6 +114,16 @@ impl ChannelStore {
}
}
pub fn has_children(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
path.len() > ix + 1
} else {
false
}
})
}
pub fn channel_count(&self) -> usize {
self.channel_paths.len()
}

View file

@ -16,9 +16,9 @@ use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions,
elements::{
Canvas, ChildView, Empty, Flex, GeneralComponent, GeneralStyleableComponent, Image, Label,
List, ListOffset, ListState, MouseEventHandler, Orientation, OverlayPositionMode, Padding,
ParentElement, Stack, Svg,
Canvas, ChildView, Component, Empty, Flex, Image, Label, List, ListOffset, ListState,
MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, Stack,
StyleableComponent, Svg,
},
geometry::{
rect::RectF,
@ -36,7 +36,7 @@ use serde_derive::{Deserialize, Serialize};
use settings::SettingsStore;
use staff_mode::StaffMode;
use std::{borrow::Cow, mem, sync::Arc};
use theme::IconButton;
use theme::{components::ComponentExt, IconButton};
use util::{iife, ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel},
@ -44,10 +44,7 @@ use workspace::{
Workspace,
};
use crate::{
collab_panel::components::{DisclosureExt, DisclosureStyle},
face_pile::FacePile,
};
use crate::face_pile::FacePile;
use channel_modal::ChannelModal;
use self::contact_finder::ContactFinder;
@ -57,6 +54,11 @@ struct RemoveChannel {
channel_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct ToggleCollapsed {
channel_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct NewChannel {
channel_id: u64,
@ -86,7 +88,8 @@ impl_actions!(
NewChannel,
InviteMembers,
ManageMembers,
RenameChannel
RenameChannel,
ToggleCollapsed
]
);
@ -109,6 +112,7 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
cx.add_action(CollabPanel::manage_members);
cx.add_action(CollabPanel::rename_selected_channel);
cx.add_action(CollabPanel::rename_channel);
cx.add_action(CollabPanel::toggle_channel_collapsed);
}
#[derive(Debug)]
@ -151,6 +155,7 @@ pub struct CollabPanel {
list_state: ListState<Self>,
subscriptions: Vec<Subscription>,
collapsed_sections: Vec<Section>,
collapsed_channels: Vec<ChannelId>,
workspace: WeakViewHandle<Workspace>,
context_menu_on_selected: bool,
}
@ -402,6 +407,7 @@ impl CollabPanel {
subscriptions: Vec::default(),
match_candidates: Vec::default(),
collapsed_sections: vec![Section::Offline],
collapsed_channels: Vec::default(),
workspace: workspace.weak_handle(),
client: workspace.app_state().client.clone(),
context_menu_on_selected: true,
@ -661,10 +667,24 @@ impl CollabPanel {
self.entries.push(ListEntry::ChannelEditor { depth: 0 });
}
}
let mut collapse_depth = None;
for mat in matches {
let (depth, channel) =
channel_store.channel_at_index(mat.candidate_id).unwrap();
if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
collapse_depth = Some(depth);
} else if let Some(collapsed_depth) = collapse_depth {
if depth > collapsed_depth {
continue;
}
if self.is_channel_collapsed(channel.id) {
collapse_depth = Some(depth);
} else {
collapse_depth = None;
}
}
match &self.channel_editing_state {
Some(ChannelEditingState::Create { parent_id, .. })
if *parent_id == Some(channel.id) =>
@ -1483,6 +1503,11 @@ impl CollabPanel {
cx: &AppContext,
) -> AnyElement<Self> {
Flex::row()
.with_child(
Empty::new()
.constrained()
.with_width(theme.collab_panel.disclosure.button_space()),
)
.with_child(
Svg::new("icons/hash.svg")
.with_color(theme.collab_panel.channel_hash.color)
@ -1541,6 +1566,10 @@ impl CollabPanel {
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
let channel_id = channel.id;
let has_children = self.channel_store.read(cx).has_children(channel_id);
let disclosed =
has_children.then(|| !self.collapsed_channels.binary_search(&channel_id).is_ok());
let is_active = iife!({
let call_channel = ActiveCall::global(cx)
.read(cx)
@ -1554,7 +1583,7 @@ impl CollabPanel {
const FACEPILE_LIMIT: usize = 3;
MouseEventHandler::new::<Channel, _>(channel.id as usize, cx, |state, cx| {
Flex::row()
Flex::<Self>::row()
.with_child(
Svg::new("icons/hash.svg")
.with_color(theme.channel_hash.color)
@ -1603,6 +1632,14 @@ impl CollabPanel {
}
})
.align_children_center()
.styleable_component()
.disclosable(
disclosed,
Box::new(ToggleCollapsed { channel_id }),
channel_id as usize,
)
.with_style(theme.disclosure.clone())
.element()
.constrained()
.with_height(theme.row_height)
.contained()
@ -1619,17 +1656,6 @@ impl CollabPanel {
this.deploy_channel_context_menu(Some(e.position), channel_id, cx);
})
.with_cursor_style(CursorStyle::PointingHand)
.dynamic_component()
.stylable()
.disclosable(true, Box::new(RemoveChannel { channel_id: 0 }))
.with_style({
fn style() -> DisclosureStyle<()> {
todo!()
}
style()
})
.element()
.into_any()
}
@ -2024,6 +2050,24 @@ impl CollabPanel {
self.update_entries(false, cx);
}
fn toggle_channel_collapsed(&mut self, action: &ToggleCollapsed, cx: &mut ViewContext<Self>) {
let channel_id = action.channel_id;
match self.collapsed_channels.binary_search(&channel_id) {
Ok(ix) => {
self.collapsed_channels.remove(ix);
}
Err(ix) => {
self.collapsed_channels.insert(ix, channel_id);
}
};
self.update_entries(false, cx);
cx.notify();
}
fn is_channel_collapsed(&self, channel: ChannelId) -> bool {
self.collapsed_channels.binary_search(&channel).is_ok()
}
fn leave_call(cx: &mut ViewContext<Self>) {
ActiveCall::global(cx)
.update(cx, |call, cx| call.hang_up(cx))
@ -2537,87 +2581,3 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen
.contained()
.with_style(style.container)
}
mod components {
use gpui::{
elements::{Empty, Flex, GeneralComponent, GeneralStyleableComponent, ParentElement},
Action, Element,
};
use theme::components::{
action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
};
#[derive(Clone)]
pub struct DisclosureStyle<S> {
disclosure: ToggleIconButtonStyle,
spacing: f32,
content: S,
}
pub struct Disclosable<C, S> {
disclosed: bool,
action: Box<dyn Action>,
content: C,
style: S,
}
impl Disclosable<(), ()> {
fn new<C>(disclosed: bool, content: C, action: Box<dyn Action>) -> Disclosable<C, ()> {
Disclosable {
disclosed,
content,
action,
style: (),
}
}
}
impl<C: GeneralStyleableComponent> GeneralStyleableComponent for Disclosable<C, ()> {
type Style = DisclosureStyle<C::Style>;
type Output = Disclosable<C, Self::Style>;
fn with_style(self, style: Self::Style) -> Self::Output {
Disclosable {
disclosed: self.disclosed,
action: self.action,
content: self.content,
style,
}
}
}
impl<C: GeneralStyleableComponent> GeneralComponent for Disclosable<C, DisclosureStyle<C::Style>> {
fn render<V: gpui::View>(
self,
v: &mut V,
cx: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
Flex::row()
.with_child(
ActionButton::new_dynamic(self.action)
.with_contents(Svg::new("path"))
.toggleable(self.disclosed)
.with_style(self.style.disclosure)
.element(),
)
.with_child(Empty::new().constrained().with_width(self.style.spacing))
.with_child(self.content.with_style(self.style.content).render(v, cx))
.align_children_center()
.into_any()
}
}
pub trait DisclosureExt {
fn disclosable(self, disclosed: bool, action: Box<dyn Action>) -> Disclosable<Self, ()>
where
Self: Sized;
}
impl<C: GeneralStyleableComponent> DisclosureExt for C {
fn disclosable(self, disclosed: bool, action: Box<dyn Action>) -> Disclosable<Self, ()> {
Disclosable::new(disclosed, self, action)
}
}
}

View file

@ -2,7 +2,7 @@ use button_component::Button;
use gpui::{
color::Color,
elements::{Component, ContainerStyle, Flex, Label, ParentElement},
elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent},
fonts::{self, TextStyle},
platform::WindowOptions,
AnyElement, App, Element, Entity, View, ViewContext,
@ -72,7 +72,7 @@ impl View for TestView {
TextStyle::for_color(Color::blue()),
)
.with_style(ButtonStyle::fill(Color::yellow()))
.c_element(),
.stateful_element(),
)
.with_child(
ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
@ -84,7 +84,7 @@ impl View for TestView {
inactive: ButtonStyle::fill(Color::red()),
active: ButtonStyle::fill(Color::green()),
})
.c_element(),
.stateful_element(),
)
.expanded()
.contained()
@ -114,7 +114,7 @@ mod theme {
// Component creation:
mod toggleable_button {
use gpui::{
elements::{Component, ContainerStyle, LabelStyle},
elements::{ContainerStyle, LabelStyle, StatefulComponent},
scene::MouseClick,
EventContext, View,
};
@ -156,7 +156,7 @@ mod toggleable_button {
}
}
impl<V: View> Component<V> for ToggleableButton<V> {
impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
let button = if let Some(style) = self.style {
self.button.with_style(*style.style_for(self.active))
@ -171,7 +171,7 @@ mod toggleable_button {
mod button_component {
use gpui::{
elements::{Component, ContainerStyle, Label, LabelStyle, MouseEventHandler},
elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent},
platform::MouseButton,
scene::MouseClick,
AnyElement, Element, EventContext, TypeTag, View, ViewContext,
@ -212,7 +212,7 @@ mod button_component {
}
}
impl<V: View> Component<V> for Button<V> {
impl<V: View> StatefulComponent<V> for Button<V> {
fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
let click_handler = self.click_handler;

View file

@ -230,19 +230,26 @@ pub trait Element<V: View>: 'static {
MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
}
fn component(self) -> ElementAdapter<V>
fn stateful_component(self) -> ElementAdapter<V>
where
Self: Sized,
{
ElementAdapter::new(self.into_any())
}
fn dynamic_component(self) -> DynamicElementAdapter
fn component(self) -> DynamicElementAdapter
where
Self: Sized,
{
DynamicElementAdapter::new(self.into_any())
}
fn styleable_component(self) -> StylableAdapter<DynamicElementAdapter>
where
Self: Sized,
{
DynamicElementAdapter::new(self.into_any()).stylable()
}
}
pub trait RenderElement {

View file

@ -9,35 +9,35 @@ use crate::{
use super::Empty;
pub trait GeneralComponent {
pub trait Component {
fn render<V: View>(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
fn element<V: View>(self) -> ComponentAdapter<V, Self>
fn element<V: View>(self) -> StatefulAdapter<V, Self>
where
Self: Sized,
{
ComponentAdapter::new(self)
StatefulAdapter::new(self)
}
fn stylable(self) -> GeneralStylableComponentAdapter<Self>
fn stylable(self) -> StylableAdapter<Self>
where
Self: Sized,
{
GeneralStylableComponentAdapter::new(self)
StylableAdapter::new(self)
}
}
pub struct GeneralStylableComponentAdapter<C: GeneralComponent> {
pub struct StylableAdapter<C: Component> {
component: C,
}
impl<C: GeneralComponent> GeneralStylableComponentAdapter<C> {
impl<C: Component> StylableAdapter<C> {
pub fn new(component: C) -> Self {
Self { component }
}
}
impl<C: GeneralComponent> GeneralStyleableComponent for GeneralStylableComponentAdapter<C> {
impl<C: Component> StyleableComponent for StylableAdapter<C> {
type Style = ();
type Output = C;
@ -47,20 +47,20 @@ impl<C: GeneralComponent> GeneralStyleableComponent for GeneralStylableComponent
}
}
pub trait GeneralStyleableComponent {
pub trait StyleableComponent {
type Style: Clone;
type Output: GeneralComponent;
type Output: Component;
fn with_style(self, style: Self::Style) -> Self::Output;
}
impl GeneralComponent for () {
impl Component for () {
fn render<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
Empty::new().into_any()
}
}
impl GeneralStyleableComponent for () {
impl StyleableComponent for () {
type Style = ();
type Output = ();
@ -69,54 +69,54 @@ impl GeneralStyleableComponent for () {
}
}
pub trait StyleableComponent<V: View> {
pub trait StatefulStyleableComponent<V: View> {
type Style: Clone;
type Output: Component<V>;
type Output: StatefulComponent<V>;
fn c_with_style(self, style: Self::Style) -> Self::Output;
fn stateful_with_style(self, style: Self::Style) -> Self::Output;
}
impl<V: View, C: GeneralStyleableComponent> StyleableComponent<V> for C {
impl<V: View, C: StyleableComponent> StatefulStyleableComponent<V> for C {
type Style = C::Style;
type Output = C::Output;
fn c_with_style(self, style: Self::Style) -> Self::Output {
fn stateful_with_style(self, style: Self::Style) -> Self::Output {
self.with_style(style)
}
}
pub trait Component<V: View> {
pub trait StatefulComponent<V: View> {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
fn c_element(self) -> ComponentAdapter<V, Self>
fn stateful_element(self) -> StatefulAdapter<V, Self>
where
Self: Sized,
{
ComponentAdapter::new(self)
StatefulAdapter::new(self)
}
fn c_styleable(self) -> StylableComponentAdapter<Self, V>
fn stateful_styleable(self) -> StatefulStylableAdapter<Self, V>
where
Self: Sized,
{
StylableComponentAdapter::new(self)
StatefulStylableAdapter::new(self)
}
}
impl<V: View, C: GeneralComponent> Component<V> for C {
impl<V: View, C: Component> StatefulComponent<V> for C {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
self.render(v, cx)
}
}
// StylableComponent -> Component
pub struct StylableComponentAdapter<C: Component<V>, V: View> {
pub struct StatefulStylableAdapter<C: StatefulComponent<V>, V: View> {
component: C,
phantom: std::marker::PhantomData<V>,
}
impl<C: Component<V>, V: View> StylableComponentAdapter<C, V> {
impl<C: StatefulComponent<V>, V: View> StatefulStylableAdapter<C, V> {
pub fn new(component: C) -> Self {
Self {
component,
@ -125,12 +125,14 @@ impl<C: Component<V>, V: View> StylableComponentAdapter<C, V> {
}
}
impl<C: Component<V>, V: View> StyleableComponent<V> for StylableComponentAdapter<C, V> {
impl<C: StatefulComponent<V>, V: View> StatefulStyleableComponent<V>
for StatefulStylableAdapter<C, V>
{
type Style = ();
type Output = C;
fn c_with_style(self, _: Self::Style) -> Self::Output {
fn stateful_with_style(self, _: Self::Style) -> Self::Output {
self.component
}
}
@ -149,7 +151,7 @@ impl DynamicElementAdapter {
}
}
impl GeneralComponent for DynamicElementAdapter {
impl Component for DynamicElementAdapter {
fn render<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
let element = self
.element
@ -174,20 +176,20 @@ impl<V: View> ElementAdapter<V> {
}
}
impl<V: View> Component<V> for ElementAdapter<V> {
impl<V: View> StatefulComponent<V> for ElementAdapter<V> {
fn render(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
self.element
}
}
// Component -> Element
pub struct ComponentAdapter<V: View, E> {
pub struct StatefulAdapter<V: View, E> {
component: Option<E>,
element: Option<AnyElement<V>>,
phantom: PhantomData<V>,
}
impl<E, V: View> ComponentAdapter<V, E> {
impl<E, V: View> StatefulAdapter<V, E> {
pub fn new(e: E) -> Self {
Self {
component: Some(e),
@ -197,7 +199,7 @@ impl<E, V: View> ComponentAdapter<V, E> {
}
}
impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
impl<V: View, C: StatefulComponent<V> + 'static> Element<V> for StatefulAdapter<V, C> {
type LayoutState = ();
type PaintState = ();

View file

@ -45,6 +45,14 @@ impl ContainerStyle {
..Default::default()
}
}
pub fn additional_length(&self) -> f32 {
self.padding.left
+ self.padding.right
+ self.border.width * 2.
+ self.margin.left
+ self.margin.right
}
}
pub struct Container<V: View> {

View file

@ -2,7 +2,7 @@ use bitflags::bitflags;
pub use buffer_search::BufferSearchBar;
use gpui::{
actions,
elements::{Component, GeneralStyleableComponent, TooltipStyle},
elements::{Component, StyleableComponent, TooltipStyle},
Action, AnyElement, AppContext, Element, View,
};
pub use mode::SearchMode;
@ -96,7 +96,7 @@ impl SearchOptions {
.with_contents(Svg::new(self.icon()))
.toggleable(active)
.with_style(button_style)
.c_element()
.element()
.into_any()
}
}

View file

@ -1,23 +1,147 @@
use gpui::elements::GeneralStyleableComponent;
use gpui::{elements::StyleableComponent, Action};
use crate::{Interactive, Toggleable};
use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle};
use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle};
pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
pub trait ComponentExt<C: GeneralStyleableComponent> {
pub trait ComponentExt<C: StyleableComponent> {
fn toggleable(self, active: bool) -> Toggle<C, ()>;
fn disclosable(
self,
disclosed: Option<bool>,
action: Box<dyn Action>,
id: usize,
) -> Disclosable<C, ()>;
}
impl<C: GeneralStyleableComponent> ComponentExt<C> for C {
impl<C: StyleableComponent> ComponentExt<C> for C {
fn toggleable(self, active: bool) -> Toggle<C, ()> {
Toggle::new(self, active)
}
/// Some(True) => disclosed => content is visible
/// Some(false) => closed => content is hidden
/// None => No disclosure button, but reserve spacing
fn disclosable(
self,
disclosed: Option<bool>,
action: Box<dyn Action>,
id: usize,
) -> Disclosable<C, ()> {
Disclosable::new(disclosed, self, action, id)
}
}
pub mod disclosure {
use gpui::{
elements::{Component, Empty, Flex, ParentElement, StyleableComponent},
Action, Element,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
use super::{action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle};
#[derive(Clone, Default, Deserialize, JsonSchema)]
pub struct DisclosureStyle<S> {
pub button: ToggleIconButtonStyle,
pub spacing: f32,
#[serde(flatten)]
content: S,
}
impl<S> DisclosureStyle<S> {
pub fn button_space(&self) -> f32 {
self.spacing + self.button.button_width.unwrap()
}
}
pub struct Disclosable<C, S> {
disclosed: Option<bool>,
action: Box<dyn Action>,
id: usize,
content: C,
style: S,
}
impl Disclosable<(), ()> {
pub fn new<C>(
disclosed: Option<bool>,
content: C,
action: Box<dyn Action>,
id: usize,
) -> Disclosable<C, ()> {
Disclosable {
disclosed,
content,
action,
id,
style: (),
}
}
}
impl<C: StyleableComponent> StyleableComponent for Disclosable<C, ()> {
type Style = DisclosureStyle<C::Style>;
type Output = Disclosable<C, Self::Style>;
fn with_style(self, style: Self::Style) -> Self::Output {
Disclosable {
disclosed: self.disclosed,
action: self.action,
content: self.content,
id: self.id,
style,
}
}
}
impl<C: StyleableComponent> Component for Disclosable<C, DisclosureStyle<C::Style>> {
fn render<V: gpui::View>(
self,
v: &mut V,
cx: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
Flex::row()
.with_child(if let Some(disclosed) = self.disclosed {
ActionButton::new_dynamic(self.action)
.with_id(self.id)
.with_contents(Svg::new(if disclosed {
"icons/file_icons/chevron_down.svg"
} else {
"icons/file_icons/chevron_right.svg"
}))
.toggleable(disclosed)
.with_style(self.style.button)
.element()
.into_any()
} else {
Empty::new()
.into_any()
.constrained()
// TODO: Why is this optional at all?
.with_width(self.style.button.button_width.unwrap())
.into_any()
})
.with_child(Empty::new().constrained().with_width(self.style.spacing))
.with_child(
self.content
.with_style(self.style.content)
.render(v, cx)
.flex(1., true),
)
.align_children_center()
.into_any()
}
}
}
pub mod toggle {
use gpui::elements::{GeneralComponent, GeneralStyleableComponent};
use gpui::elements::{Component, StyleableComponent};
use crate::Toggleable;
@ -27,7 +151,7 @@ pub mod toggle {
component: C,
}
impl<C: GeneralStyleableComponent> Toggle<C, ()> {
impl<C: StyleableComponent> Toggle<C, ()> {
pub fn new(component: C, active: bool) -> Self {
Toggle {
active,
@ -37,7 +161,7 @@ pub mod toggle {
}
}
impl<C: GeneralStyleableComponent> GeneralStyleableComponent for Toggle<C, ()> {
impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
type Style = Toggleable<C::Style>;
type Output = Toggle<C, Self::Style>;
@ -51,7 +175,7 @@ pub mod toggle {
}
}
impl<C: GeneralStyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
impl<C: StyleableComponent> Component for Toggle<C, Toggleable<C::Style>> {
fn render<V: gpui::View>(
self,
v: &mut V,
@ -69,8 +193,7 @@ pub mod action_button {
use gpui::{
elements::{
ContainerStyle, GeneralComponent, GeneralStyleableComponent, MouseEventHandler,
TooltipStyle,
Component, ContainerStyle, MouseEventHandler, StyleableComponent, TooltipStyle,
},
platform::{CursorStyle, MouseButton},
Action, Element, TypeTag, View,
@ -80,24 +203,28 @@ pub mod action_button {
use crate::Interactive;
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ButtonStyle<C> {
#[serde(flatten)]
pub container: ContainerStyle,
// TODO: These are incorrect for the intended usage of the buttons.
// The size should be constant, but putting them here duplicates them
// across the states the buttons can be in
pub button_width: Option<f32>,
pub button_height: Option<f32>,
#[serde(flatten)]
contents: C,
}
pub struct ActionButton<C, S> {
action: Box<dyn Action>,
tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
tag: TypeTag,
id: usize,
contents: C,
style: Interactive<S>,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ButtonStyle<C> {
#[serde(flatten)]
container: ContainerStyle,
button_width: Option<f32>,
button_height: Option<f32>,
#[serde(flatten)]
contents: C,
}
impl ActionButton<(), ()> {
pub fn new_dynamic(action: Box<dyn Action>) -> Self {
Self {
@ -105,6 +232,7 @@ pub mod action_button {
tag: action.type_tag(),
style: Interactive::new_blank(),
tooltip: None,
id: 0,
action,
}
}
@ -122,21 +250,24 @@ pub mod action_button {
self
}
pub fn with_contents<C: GeneralStyleableComponent>(
self,
contents: C,
) -> ActionButton<C, ()> {
pub fn with_id(mut self, id: usize) -> Self {
self.id = id;
self
}
pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
ActionButton {
action: self.action,
tag: self.tag,
style: self.style,
tooltip: self.tooltip,
id: self.id,
contents,
}
}
}
impl<C: GeneralStyleableComponent> GeneralStyleableComponent for ActionButton<C, ()> {
impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
type Style = Interactive<ButtonStyle<C::Style>>;
type Output = ActionButton<C, ButtonStyle<C::Style>>;
@ -146,15 +277,15 @@ pub mod action_button {
tag: self.tag,
contents: self.contents,
tooltip: self.tooltip,
id: self.id,
style,
}
}
}
impl<C: GeneralStyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
impl<C: StyleableComponent> Component for ActionButton<C, ButtonStyle<C::Style>> {
fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| {
let style = self.style.style_for(state);
let mut contents = self
.contents
@ -177,8 +308,13 @@ pub mod action_button {
.on_click(MouseButton::Left, {
let action = self.action.boxed_clone();
move |_, _, cx| {
cx.window()
.dispatch_action(cx.view_id(), action.as_ref(), cx);
let window = cx.window();
let view = cx.view_id();
let action = action.boxed_clone();
cx.spawn(|_, mut cx| async move {
window.dispatch_action(view, action.as_ref(), &mut cx)
})
.detach();
}
})
.with_cursor_style(CursorStyle::PointingHand)
@ -199,7 +335,7 @@ pub mod svg {
use std::borrow::Cow;
use gpui::{
elements::{GeneralComponent, GeneralStyleableComponent},
elements::{Component, Empty, StyleableComponent},
Element,
};
use schemars::JsonSchema;
@ -222,6 +358,7 @@ pub mod svg {
pub enum IconSize {
IconSize { icon_size: f32 },
Dimensions { width: f32, height: f32 },
IconDimensions { icon_width: f32, icon_height: f32 },
}
#[derive(Deserialize)]
@ -245,6 +382,14 @@ pub mod svg {
icon_height: height,
color,
},
IconSize::IconDimensions {
icon_width,
icon_height,
} => SvgStyle {
icon_width,
icon_height,
color,
},
};
Ok(result)
@ -252,20 +397,27 @@ pub mod svg {
}
pub struct Svg<S> {
path: Cow<'static, str>,
path: Option<Cow<'static, str>>,
style: S,
}
impl Svg<()> {
pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
Self {
path: path.into(),
path: Some(path.into()),
style: (),
}
}
pub fn optional(path: Option<impl Into<Cow<'static, str>>>) -> Self {
Self {
path: path.map(Into::into),
style: (),
}
}
}
impl GeneralStyleableComponent for Svg<()> {
impl StyleableComponent for Svg<()> {
type Style = SvgStyle;
type Output = Svg<SvgStyle>;
@ -278,18 +430,23 @@ pub mod svg {
}
}
impl GeneralComponent for Svg<SvgStyle> {
impl Component for Svg<SvgStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,
_: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
gpui::elements::Svg::new(self.path)
.with_color(self.style.color)
.constrained()
.with_width(self.style.icon_width)
.with_height(self.style.icon_height)
.into_any()
if let Some(path) = self.path {
gpui::elements::Svg::new(path)
.with_color(self.style.color)
.constrained()
} else {
Empty::new().constrained()
}
.constrained()
.with_width(self.style.icon_width)
.with_height(self.style.icon_height)
.into_any()
}
}
}
@ -298,7 +455,7 @@ pub mod label {
use std::borrow::Cow;
use gpui::{
elements::{GeneralComponent, GeneralStyleableComponent, LabelStyle},
elements::{Component, LabelStyle, StyleableComponent},
Element,
};
@ -316,7 +473,7 @@ pub mod label {
}
}
impl GeneralStyleableComponent for Label<()> {
impl StyleableComponent for Label<()> {
type Style = LabelStyle;
type Output = Label<LabelStyle>;
@ -329,7 +486,7 @@ pub mod label {
}
}
impl GeneralComponent for Label<LabelStyle> {
impl Component for Label<LabelStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,

View file

@ -3,7 +3,7 @@ mod theme_registry;
mod theme_settings;
pub mod ui;
use components::ToggleIconButtonStyle;
use components::{disclosure::DisclosureStyle, ToggleIconButtonStyle};
use gpui::{
color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
@ -14,7 +14,7 @@ use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use settings::SettingsStore;
use std::{collections::HashMap, sync::Arc};
use std::{collections::HashMap, ops::Deref, sync::Arc};
use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
pub use theme_registry::*;
@ -221,6 +221,7 @@ pub struct CopilotAuthAuthorized {
pub struct CollabPanel {
#[serde(flatten)]
pub container: ContainerStyle,
pub disclosure: DisclosureStyle<()>,
pub list_empty_state: Toggleable<Interactive<ContainedText>>,
pub list_empty_icon: Icon,
pub list_empty_label_container: ContainerStyle,
@ -890,6 +891,14 @@ pub struct Interactive<T> {
pub disabled: Option<T>,
}
impl<T> Deref for Interactive<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.default
}
}
impl Interactive<()> {
pub fn new_blank() -> Self {
Self {
@ -907,6 +916,14 @@ pub struct Toggleable<T> {
inactive: T,
}
impl<T> Deref for Toggleable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inactive
}
}
impl Toggleable<()> {
pub fn new_blank() -> Self {
Self {

View file

@ -152,6 +152,10 @@ export default function contacts_panel(): any {
return {
...collab_modals(),
disclosure: {
button: toggleable_icon_button(theme, {}),
spacing: 4,
},
log_in_button: interactive({
base: {
background: background(theme.middle),