Add disclosable components into channels
Rename components to more closely match their purpose
This commit is contained in:
parent
2d37128693
commit
bd3ab82dac
10 changed files with 359 additions and 194 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue