Merge branch 'main' into arena
This commit is contained in:
commit
3781626379
24 changed files with 1017 additions and 489 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -9263,6 +9263,8 @@ name = "story"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -9277,6 +9279,7 @@ dependencies = [
|
||||||
"editor2",
|
"editor2",
|
||||||
"fuzzy2",
|
"fuzzy2",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"indoc",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"language2",
|
"language2",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -165,7 +165,7 @@ struct ChannelMoveClipboard {
|
||||||
|
|
||||||
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
||||||
|
|
||||||
use std::{iter::once, mem, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
|
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
|
||||||
|
@ -175,12 +175,12 @@ use editor::Editor;
|
||||||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, fill, img, impl_actions, overlay, point, prelude::*, px, rems,
|
actions, canvas, div, fill, impl_actions, list, overlay, point, prelude::*, px, serde_json,
|
||||||
serde_json, size, Action, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem,
|
AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div,
|
||||||
DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, Hsla,
|
EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement,
|
||||||
InteractiveElement, IntoElement, Length, Model, MouseDownEvent, ParentElement, Pixels, Point,
|
ListOffset, ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
|
||||||
PromptLevel, Quad, Render, RenderOnce, ScrollHandle, SharedString, Size, Stateful, Styled,
|
Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext,
|
||||||
Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
WeakView,
|
||||||
};
|
};
|
||||||
use project::{Fs, Project};
|
use project::{Fs, Project};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
@ -188,7 +188,7 @@ use settings::{Settings, SettingsStore};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
|
h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
|
||||||
Label, List, ListHeader, ListItem, Tooltip,
|
Label, ListHeader, ListItem, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -303,6 +303,7 @@ pub struct CollabPanel {
|
||||||
channel_clipboard: Option<ChannelMoveClipboard>,
|
channel_clipboard: Option<ChannelMoveClipboard>,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
|
list_state: ListState,
|
||||||
filter_editor: View<Editor>,
|
filter_editor: View<Editor>,
|
||||||
channel_name_editor: View<Editor>,
|
channel_name_editor: View<Editor>,
|
||||||
channel_editing_state: Option<ChannelEditingState>,
|
channel_editing_state: Option<ChannelEditingState>,
|
||||||
|
@ -313,7 +314,6 @@ pub struct CollabPanel {
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
match_candidates: Vec<StringMatchCandidate>,
|
match_candidates: Vec<StringMatchCandidate>,
|
||||||
scroll_handle: ScrollHandle,
|
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
collapsed_sections: Vec<Section>,
|
collapsed_sections: Vec<Section>,
|
||||||
collapsed_channels: Vec<ChannelId>,
|
collapsed_channels: Vec<ChannelId>,
|
||||||
|
@ -398,7 +398,7 @@ enum ListEntry {
|
||||||
impl CollabPanel {
|
impl CollabPanel {
|
||||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||||
cx.build_view(|cx| {
|
cx.build_view(|cx| {
|
||||||
// let view_id = cx.view_id();
|
let view = cx.view().clone();
|
||||||
|
|
||||||
let filter_editor = cx.build_view(|cx| {
|
let filter_editor = cx.build_view(|cx| {
|
||||||
let mut editor = Editor::single_line(cx);
|
let mut editor = Editor::single_line(cx);
|
||||||
|
@ -445,136 +445,10 @@ impl CollabPanel {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// let list_state =
|
let list_state =
|
||||||
// ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
|
ListState::new(0, gpui::ListAlignment::Top, px(1000.), move |ix, cx| {
|
||||||
// let theme = theme::current(cx).clone();
|
view.update(cx, |view, cx| view.render_list_entry(ix, cx))
|
||||||
// let is_selected = this.selection == Some(ix);
|
});
|
||||||
// let current_project_id = this.project.read(cx).remote_id();
|
|
||||||
|
|
||||||
// match &this.entries[ix] {
|
|
||||||
// ListEntry::Header(section) => {
|
|
||||||
// let is_collapsed = this.collapsed_sections.contains(section);
|
|
||||||
// this.render_header(*section, &theme, is_selected, is_collapsed, cx)
|
|
||||||
// }
|
|
||||||
// ListEntry::CallParticipant {
|
|
||||||
// user,
|
|
||||||
// peer_id,
|
|
||||||
// is_pending,
|
|
||||||
// } => Self::render_call_participant(
|
|
||||||
// user,
|
|
||||||
// *peer_id,
|
|
||||||
// this.user_store.clone(),
|
|
||||||
// *is_pending,
|
|
||||||
// is_selected,
|
|
||||||
// &theme,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::ParticipantProject {
|
|
||||||
// project_id,
|
|
||||||
// worktree_root_names,
|
|
||||||
// host_user_id,
|
|
||||||
// is_last,
|
|
||||||
// } => Self::render_participant_project(
|
|
||||||
// *project_id,
|
|
||||||
// worktree_root_names,
|
|
||||||
// *host_user_id,
|
|
||||||
// Some(*project_id) == current_project_id,
|
|
||||||
// *is_last,
|
|
||||||
// is_selected,
|
|
||||||
// &theme,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::ParticipantScreen { peer_id, is_last } => {
|
|
||||||
// Self::render_participant_screen(
|
|
||||||
// *peer_id,
|
|
||||||
// *is_last,
|
|
||||||
// is_selected,
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// ListEntry::Channel {
|
|
||||||
// channel,
|
|
||||||
// depth,
|
|
||||||
// has_children,
|
|
||||||
// } => {
|
|
||||||
// let channel_row = this.render_channel(
|
|
||||||
// &*channel,
|
|
||||||
// *depth,
|
|
||||||
// &theme,
|
|
||||||
// is_selected,
|
|
||||||
// *has_children,
|
|
||||||
// ix,
|
|
||||||
// cx,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if is_selected && this.context_menu_on_selected {
|
|
||||||
// Stack::new()
|
|
||||||
// .with_child(channel_row)
|
|
||||||
// .with_child(
|
|
||||||
// ChildView::new(&this.context_menu, cx)
|
|
||||||
// .aligned()
|
|
||||||
// .bottom()
|
|
||||||
// .right(),
|
|
||||||
// )
|
|
||||||
// .into_any()
|
|
||||||
// } else {
|
|
||||||
// return channel_row;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
|
|
||||||
// *channel_id,
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// is_selected,
|
|
||||||
// ix,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
|
|
||||||
// *channel_id,
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// is_selected,
|
|
||||||
// ix,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
|
|
||||||
// channel.clone(),
|
|
||||||
// this.channel_store.clone(),
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// is_selected,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::IncomingRequest(user) => Self::render_contact_request(
|
|
||||||
// user.clone(),
|
|
||||||
// this.user_store.clone(),
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// true,
|
|
||||||
// is_selected,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::OutgoingRequest(user) => Self::render_contact_request(
|
|
||||||
// user.clone(),
|
|
||||||
// this.user_store.clone(),
|
|
||||||
// &theme.collab_panel,
|
|
||||||
// false,
|
|
||||||
// is_selected,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::Contact { contact, calling } => Self::render_contact(
|
|
||||||
// contact,
|
|
||||||
// *calling,
|
|
||||||
// &this.project,
|
|
||||||
// &theme,
|
|
||||||
// is_selected,
|
|
||||||
// cx,
|
|
||||||
// ),
|
|
||||||
// ListEntry::ChannelEditor { depth } => {
|
|
||||||
// this.render_channel_editor(&theme, *depth, cx)
|
|
||||||
// }
|
|
||||||
// ListEntry::ContactPlaceholder => {
|
|
||||||
// this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
width: None,
|
width: None,
|
||||||
|
@ -583,6 +457,7 @@ impl CollabPanel {
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
|
list_state,
|
||||||
channel_name_editor,
|
channel_name_editor,
|
||||||
filter_editor,
|
filter_editor,
|
||||||
entries: Vec::default(),
|
entries: Vec::default(),
|
||||||
|
@ -593,7 +468,6 @@ impl CollabPanel {
|
||||||
project: workspace.project().clone(),
|
project: workspace.project().clone(),
|
||||||
subscriptions: Vec::default(),
|
subscriptions: Vec::default(),
|
||||||
match_candidates: Vec::default(),
|
match_candidates: Vec::default(),
|
||||||
scroll_handle: ScrollHandle::new(),
|
|
||||||
collapsed_sections: vec![Section::Offline],
|
collapsed_sections: vec![Section::Offline],
|
||||||
collapsed_channels: Vec::default(),
|
collapsed_channels: Vec::default(),
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
|
@ -709,6 +583,10 @@ impl CollabPanel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_to_item(&mut self, ix: usize) {
|
||||||
|
self.list_state.scroll_to_reveal_item(ix)
|
||||||
|
}
|
||||||
|
|
||||||
fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
|
fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
|
||||||
let channel_store = self.channel_store.read(cx);
|
let channel_store = self.channel_store.read(cx);
|
||||||
let user_store = self.user_store.read(cx);
|
let user_store = self.user_store.read(cx);
|
||||||
|
@ -1084,13 +962,15 @@ impl CollabPanel {
|
||||||
self.entries.push(ListEntry::ContactPlaceholder);
|
self.entries.push(ListEntry::ContactPlaceholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.list_state.reset(self.entries.len());
|
||||||
|
|
||||||
if select_same_item {
|
if select_same_item {
|
||||||
if let Some(prev_selected_entry) = prev_selected_entry {
|
if let Some(prev_selected_entry) = prev_selected_entry {
|
||||||
self.selection.take();
|
self.selection.take();
|
||||||
for (ix, entry) in self.entries.iter().enumerate() {
|
for (ix, entry) in self.entries.iter().enumerate() {
|
||||||
if *entry == prev_selected_entry {
|
if *entry == prev_selected_entry {
|
||||||
self.selection = Some(ix);
|
self.selection = Some(ix);
|
||||||
self.scroll_handle.scroll_to_item(ix);
|
self.scroll_to_item(ix);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1101,16 +981,19 @@ impl CollabPanel {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let ix = prev_selection.min(self.entries.len() - 1);
|
let ix = prev_selection.min(self.entries.len() - 1);
|
||||||
self.scroll_handle.scroll_to_item(ix);
|
self.scroll_to_item(ix);
|
||||||
Some(ix)
|
Some(ix)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if scroll_to_top {
|
if scroll_to_top {
|
||||||
self.scroll_handle.scroll_to_item(0)
|
self.scroll_to_item(0)
|
||||||
} else {
|
} else {
|
||||||
let (old_index, old_offset) = self.scroll_handle.logical_scroll_top();
|
let ListOffset {
|
||||||
|
item_ix: old_index,
|
||||||
|
offset_in_item: old_offset,
|
||||||
|
} = self.list_state.logical_scroll_top();
|
||||||
// Attempt to maintain the same scroll position.
|
// Attempt to maintain the same scroll position.
|
||||||
if let Some(old_top_entry) = old_entries.get(old_index) {
|
if let Some(old_top_entry) = old_entries.get(old_index) {
|
||||||
let (new_index, new_offset) = self
|
let (new_index, new_offset) = self
|
||||||
|
@ -1136,8 +1019,10 @@ impl CollabPanel {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| (old_index, old_offset));
|
.unwrap_or_else(|| (old_index, old_offset));
|
||||||
|
|
||||||
self.scroll_handle
|
self.list_state.scroll_to(ListOffset {
|
||||||
.set_logical_scroll_top(new_index, new_offset);
|
item_ix: new_index,
|
||||||
|
offset_in_item: new_offset,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1628,7 +1513,7 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ix) = self.selection {
|
if let Some(ix) = self.selection {
|
||||||
self.scroll_handle.scroll_to_item(ix)
|
self.scroll_to_item(ix)
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1640,7 +1525,7 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ix) = self.selection {
|
if let Some(ix) = self.selection {
|
||||||
self.scroll_handle.scroll_to_item(ix)
|
self.scroll_to_item(ix)
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1965,7 +1850,7 @@ impl CollabPanel {
|
||||||
};
|
};
|
||||||
let Some(bounds) = self
|
let Some(bounds) = self
|
||||||
.selection
|
.selection
|
||||||
.and_then(|ix| self.scroll_handle.bounds_for_item(ix))
|
.and_then(|ix| self.list_state.bounds_for_item(ix))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -2158,78 +2043,75 @@ impl CollabPanel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_list_entry(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||||
|
let entry = &self.entries[ix];
|
||||||
|
|
||||||
|
let is_selected = self.selection == Some(ix);
|
||||||
|
match entry {
|
||||||
|
ListEntry::Header(section) => {
|
||||||
|
let is_collapsed = self.collapsed_sections.contains(section);
|
||||||
|
self.render_header(*section, is_selected, is_collapsed, cx)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
ListEntry::Contact { contact, calling } => self
|
||||||
|
.render_contact(contact, *calling, is_selected, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ContactPlaceholder => self
|
||||||
|
.render_contact_placeholder(is_selected, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::IncomingRequest(user) => self
|
||||||
|
.render_contact_request(user, true, is_selected, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::OutgoingRequest(user) => self
|
||||||
|
.render_contact_request(user, false, is_selected, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::Channel {
|
||||||
|
channel,
|
||||||
|
depth,
|
||||||
|
has_children,
|
||||||
|
} => self
|
||||||
|
.render_channel(channel, *depth, *has_children, is_selected, ix, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ChannelEditor { depth } => {
|
||||||
|
self.render_channel_editor(*depth, cx).into_any_element()
|
||||||
|
}
|
||||||
|
ListEntry::CallParticipant {
|
||||||
|
user,
|
||||||
|
peer_id,
|
||||||
|
is_pending,
|
||||||
|
} => self
|
||||||
|
.render_call_participant(user, *peer_id, *is_pending, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ParticipantProject {
|
||||||
|
project_id,
|
||||||
|
worktree_root_names,
|
||||||
|
host_user_id,
|
||||||
|
is_last,
|
||||||
|
} => self
|
||||||
|
.render_participant_project(
|
||||||
|
*project_id,
|
||||||
|
&worktree_root_names,
|
||||||
|
*host_user_id,
|
||||||
|
*is_last,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ParticipantScreen { peer_id, is_last } => self
|
||||||
|
.render_participant_screen(*peer_id, *is_last, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ChannelNotes { channel_id } => self
|
||||||
|
.render_channel_notes(*channel_id, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
ListEntry::ChannelChat { channel_id } => {
|
||||||
|
self.render_channel_chat(*channel_id, cx).into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
||||||
v_stack()
|
v_stack()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(list(self.list_state.clone()).full())
|
||||||
v_stack()
|
|
||||||
.size_full()
|
|
||||||
.id("scroll")
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.track_scroll(&self.scroll_handle)
|
|
||||||
.children(self.entries.iter().enumerate().map(|(ix, entry)| {
|
|
||||||
let is_selected = self.selection == Some(ix);
|
|
||||||
match entry {
|
|
||||||
ListEntry::Header(section) => {
|
|
||||||
let is_collapsed = self.collapsed_sections.contains(section);
|
|
||||||
self.render_header(*section, is_selected, is_collapsed, cx)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
ListEntry::Contact { contact, calling } => self
|
|
||||||
.render_contact(contact, *calling, is_selected, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ContactPlaceholder => self
|
|
||||||
.render_contact_placeholder(is_selected, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::IncomingRequest(user) => self
|
|
||||||
.render_contact_request(user, true, is_selected, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::OutgoingRequest(user) => self
|
|
||||||
.render_contact_request(user, false, is_selected, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::Channel {
|
|
||||||
channel,
|
|
||||||
depth,
|
|
||||||
has_children,
|
|
||||||
} => self
|
|
||||||
.render_channel(channel, *depth, *has_children, is_selected, ix, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ChannelEditor { depth } => {
|
|
||||||
self.render_channel_editor(*depth, cx).into_any_element()
|
|
||||||
}
|
|
||||||
ListEntry::CallParticipant {
|
|
||||||
user,
|
|
||||||
peer_id,
|
|
||||||
is_pending,
|
|
||||||
} => self
|
|
||||||
.render_call_participant(user, *peer_id, *is_pending, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ParticipantProject {
|
|
||||||
project_id,
|
|
||||||
worktree_root_names,
|
|
||||||
host_user_id,
|
|
||||||
is_last,
|
|
||||||
} => self
|
|
||||||
.render_participant_project(
|
|
||||||
*project_id,
|
|
||||||
&worktree_root_names,
|
|
||||||
*host_user_id,
|
|
||||||
*is_last,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ParticipantScreen { peer_id, is_last } => self
|
|
||||||
.render_participant_screen(*peer_id, *is_last, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ChannelNotes { channel_id } => self
|
|
||||||
.render_channel_notes(*channel_id, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ChannelChat { channel_id } => {
|
|
||||||
self.render_channel_chat(*channel_id, cx).into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div().p_2().child(
|
div().p_2().child(
|
||||||
div()
|
div()
|
||||||
|
@ -2343,18 +2225,14 @@ impl CollabPanel {
|
||||||
.selected(is_selected),
|
.selected(is_selected),
|
||||||
)
|
)
|
||||||
.when(section == Section::Channels, |el| {
|
.when(section == Section::Channels, |el| {
|
||||||
el.drag_over::<DraggedChannelView>(|style| {
|
el.drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||||
style.bg(cx.theme().colors().ghost_element_hover)
|
.on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
|
||||||
})
|
|
||||||
.on_drop(cx.listener(
|
|
||||||
move |this, view: &View<DraggedChannelView>, cx| {
|
|
||||||
this.channel_store
|
this.channel_store
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.move_channel(view.read(cx).channel.id, None, cx)
|
channel_store.move_channel(dragged_channel.id, None, cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
},
|
}))
|
||||||
))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if section == Section::Offline {
|
if section == Section::Offline {
|
||||||
|
@ -2569,22 +2447,14 @@ impl CollabPanel {
|
||||||
width,
|
width,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.drag_over::<DraggedChannelView>(|style| {
|
.drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||||
style.bg(cx.theme().colors().ghost_element_hover)
|
.on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
|
||||||
})
|
this.channel_store
|
||||||
.on_drop(
|
.update(cx, |channel_store, cx| {
|
||||||
cx.listener(move |this, view: &View<DraggedChannelView>, cx| {
|
channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
|
||||||
this.channel_store
|
})
|
||||||
.update(cx, |channel_store, cx| {
|
.detach_and_log_err(cx)
|
||||||
channel_store.move_channel(
|
}))
|
||||||
view.read(cx).channel.id,
|
|
||||||
Some(channel_id),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(channel_id as usize)
|
ListItem::new(channel_id as usize)
|
||||||
// Offset the indent depth by one to give us room to show the disclosure.
|
// Offset the indent depth by one to give us room to show the disclosure.
|
||||||
|
|
|
@ -74,12 +74,16 @@ impl Render for CollabTitlebarItem {
|
||||||
// Set a non-scaling min-height here to ensure the titlebar is
|
// Set a non-scaling min-height here to ensure the titlebar is
|
||||||
// always at least the height of the traffic lights.
|
// always at least the height of the traffic lights.
|
||||||
.min_h(px(32.))
|
.min_h(px(32.))
|
||||||
.when(
|
.pl_2()
|
||||||
!matches!(cx.window_bounds(), WindowBounds::Fullscreen),
|
.map(|this| {
|
||||||
// Use pixels here instead of a rem-based size because the macOS traffic
|
if matches!(cx.window_bounds(), WindowBounds::Fullscreen) {
|
||||||
// lights are a static size, and don't scale with the rest of the UI.
|
this.pl_2()
|
||||||
|s| s.pl(px(68.)),
|
} else {
|
||||||
)
|
// Use pixels here instead of a rem-based size because the macOS traffic
|
||||||
|
// lights are a static size, and don't scale with the rest of the UI.
|
||||||
|
this.pl(px(72.))
|
||||||
|
}
|
||||||
|
})
|
||||||
.bg(cx.theme().colors().title_bar_background)
|
.bg(cx.theme().colors().title_bar_background)
|
||||||
.on_click(|event, cx| {
|
.on_click(|event, cx| {
|
||||||
if event.up.click_count == 2 {
|
if event.up.click_count == 2 {
|
||||||
|
@ -165,6 +169,7 @@ impl Render for CollabTitlebarItem {
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.pr_1()
|
||||||
.when_some(room, |this, room| {
|
.when_some(room, |this, room| {
|
||||||
let room = room.read(cx);
|
let room = room.read(cx);
|
||||||
let is_shared = self.project.read(cx).is_shared();
|
let is_shared = self.project.read(cx).is_shared();
|
||||||
|
@ -325,8 +330,6 @@ impl CollabTitlebarItem {
|
||||||
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
|
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.border()
|
|
||||||
.border_color(gpui::red())
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("project_name_trigger", name)
|
Button::new("project_name_trigger", name)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
|
@ -365,10 +368,9 @@ impl CollabTitlebarItem {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.border()
|
|
||||||
.border_color(gpui::red())
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("project_branch_trigger", branch_name)
|
Button::new("project_branch_trigger", branch_name)
|
||||||
|
.color(Color::Muted)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.tooltip(move |cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
|
|
|
@ -101,6 +101,7 @@ pub struct CommandInterceptResult {
|
||||||
|
|
||||||
pub struct CommandPaletteDelegate {
|
pub struct CommandPaletteDelegate {
|
||||||
command_palette: WeakView<CommandPalette>,
|
command_palette: WeakView<CommandPalette>,
|
||||||
|
all_commands: Vec<Command>,
|
||||||
commands: Vec<Command>,
|
commands: Vec<Command>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
selected_ix: usize,
|
selected_ix: usize,
|
||||||
|
@ -135,6 +136,7 @@ impl CommandPaletteDelegate {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command_palette,
|
command_palette,
|
||||||
|
all_commands: commands.clone(),
|
||||||
matches: vec![],
|
matches: vec![],
|
||||||
commands,
|
commands,
|
||||||
selected_ix: 0,
|
selected_ix: 0,
|
||||||
|
@ -167,7 +169,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
query: String,
|
query: String,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> gpui::Task<()> {
|
) -> gpui::Task<()> {
|
||||||
let mut commands = self.commands.clone();
|
let mut commands = self.all_commands.clone();
|
||||||
|
|
||||||
cx.spawn(move |picker, mut cx| async move {
|
cx.spawn(move |picker, mut cx| async move {
|
||||||
cx.read_global::<HitCounts, _>(|hit_counts, _| {
|
cx.read_global::<HitCounts, _>(|hit_counts, _| {
|
||||||
|
|
|
@ -9739,12 +9739,8 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||||
};
|
};
|
||||||
highlighted_lines.push(line);
|
highlighted_lines.push(line);
|
||||||
}
|
}
|
||||||
let message = diagnostic.message;
|
|
||||||
Arc::new(move |cx: &mut BlockContext| {
|
Arc::new(move |cx: &mut BlockContext| {
|
||||||
let message = message.clone();
|
|
||||||
let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into();
|
let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into();
|
||||||
let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone()));
|
|
||||||
|
|
||||||
// TODO: Nate: We should tint the background of the block with the severity color
|
// TODO: Nate: We should tint the background of the block with the severity color
|
||||||
// We need to extend the theme before we can do this
|
// We need to extend the theme before we can do this
|
||||||
v_stack()
|
v_stack()
|
||||||
|
@ -9754,7 +9750,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||||
.bg(gpui::red())
|
.bg(gpui::red())
|
||||||
.children(highlighted_lines.iter().map(|(line, highlights)| {
|
.children(highlighted_lines.iter().map(|(line, highlights)| {
|
||||||
let group_id = cx.block_id.to_string();
|
let group_id = cx.block_id.to_string();
|
||||||
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.group(group_id.clone())
|
.group(group_id.clone())
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
@ -9763,13 +9758,18 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
.child(HighlightedLabel::new(line.clone(), highlights.clone()))
|
.child(HighlightedLabel::new(line.clone(), highlights.clone()))
|
||||||
.child(
|
.child(
|
||||||
div().border().border_color(gpui::red()).child(
|
div().z_index(1).child(
|
||||||
IconButton::new(copy_id.clone(), Icon::Copy)
|
IconButton::new(copy_id.clone(), Icon::Copy)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.size(ButtonSize::Compact)
|
.size(ButtonSize::Compact)
|
||||||
.style(ButtonStyle::Transparent)
|
.style(ButtonStyle::Transparent)
|
||||||
.visible_on_hover(group_id)
|
.visible_on_hover(group_id)
|
||||||
.on_click(cx.listener(move |_, _, cx| write_to_clipboard))
|
.on_click(cx.listener({
|
||||||
|
let message = diagnostic.message.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new(message.clone()))
|
||||||
|
}
|
||||||
|
}))
|
||||||
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
|
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2284,8 +2284,8 @@ impl EditorElement {
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||||
.on_click(cx.listener(|_editor, _event, _cx| {
|
.on_click(cx.listener(|_editor, _event, _cx| {
|
||||||
// TODO: Implement collapsing path headers
|
// todo!() Implement collapsing path headers
|
||||||
todo!("Clicking path header")
|
// todo!("Clicking path header")
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
|
@ -2447,13 +2447,13 @@ impl EditorElement {
|
||||||
let interactive_bounds = interactive_bounds.clone();
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
move |event: &ScrollWheelEvent, phase, cx| {
|
move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble
|
||||||
return;
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2461,48 +2461,54 @@ impl EditorElement {
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let stacking_order = cx.stacking_order().clone();
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
move |event: &MouseDownEvent, phase, cx| {
|
move |event: &MouseDownEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble
|
||||||
return;
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
|
match event.button {
|
||||||
|
MouseButton::Left => editor.update(cx, |editor, cx| {
|
||||||
|
Self::mouse_left_down(
|
||||||
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
gutter_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
MouseButton::Right => editor.update(cx, |editor, cx| {
|
||||||
|
Self::mouse_right_down(editor, event, &position_map, text_bounds, cx);
|
||||||
|
}),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
match event.button {
|
cx.on_mouse_event({
|
||||||
MouseButton::Left => editor.update(cx, |editor, cx| {
|
let position_map = layout.position_map.clone();
|
||||||
Self::mouse_left_down(
|
let editor = self.editor.clone();
|
||||||
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
|
move |event: &MouseUpEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
Self::mouse_up(
|
||||||
editor,
|
editor,
|
||||||
event,
|
event,
|
||||||
&position_map,
|
&position_map,
|
||||||
text_bounds,
|
text_bounds,
|
||||||
gutter_bounds,
|
|
||||||
&stacking_order,
|
&stacking_order,
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
}),
|
});
|
||||||
MouseButton::Right => editor.update(cx, |editor, cx| {
|
}
|
||||||
Self::mouse_right_down(editor, event, &position_map, text_bounds, cx);
|
|
||||||
}),
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let position_map = layout.position_map.clone();
|
|
||||||
let editor = self.editor.clone();
|
|
||||||
let stacking_order = cx.stacking_order().clone();
|
|
||||||
|
|
||||||
move |event: &MouseUpEvent, phase, cx| {
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
Self::mouse_up(
|
|
||||||
editor,
|
|
||||||
event,
|
|
||||||
&position_map,
|
|
||||||
text_bounds,
|
|
||||||
&stacking_order,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
|
@ -2511,21 +2517,21 @@ impl EditorElement {
|
||||||
let stacking_order = cx.stacking_order().clone();
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
|
||||||
move |event: &MouseMoveEvent, phase, cx| {
|
move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble
|
||||||
return;
|
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
|
{
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
Self::mouse_moved(
|
||||||
|
editor,
|
||||||
|
event,
|
||||||
|
&position_map,
|
||||||
|
text_bounds,
|
||||||
|
gutter_bounds,
|
||||||
|
&stacking_order,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
Self::mouse_moved(
|
|
||||||
editor,
|
|
||||||
event,
|
|
||||||
&position_map,
|
|
||||||
text_bounds,
|
|
||||||
gutter_bounds,
|
|
||||||
&stacking_order,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, AnyElement, AvailableSpace, BorrowAppContext, DispatchPhase, Element, IntoElement, Pixels,
|
point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element,
|
||||||
Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext,
|
IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use refineable::Refineable as _;
|
use refineable::Refineable as _;
|
||||||
|
@ -23,7 +24,7 @@ pub struct List {
|
||||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||||
|
|
||||||
struct StateInner {
|
struct StateInner {
|
||||||
last_layout_width: Option<Pixels>,
|
last_layout_bounds: Option<Bounds<Pixels>>,
|
||||||
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
|
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
|
||||||
items: SumTree<ListItem>,
|
items: SumTree<ListItem>,
|
||||||
logical_scroll_top: Option<ListOffset>,
|
logical_scroll_top: Option<ListOffset>,
|
||||||
|
@ -83,7 +84,7 @@ impl ListState {
|
||||||
let mut items = SumTree::new();
|
let mut items = SumTree::new();
|
||||||
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
Self(Rc::new(RefCell::new(StateInner {
|
Self(Rc::new(RefCell::new(StateInner {
|
||||||
last_layout_width: None,
|
last_layout_bounds: None,
|
||||||
render_item: Box::new(render_item),
|
render_item: Box::new(render_item),
|
||||||
items,
|
items,
|
||||||
logical_scroll_top: None,
|
logical_scroll_top: None,
|
||||||
|
@ -152,6 +153,64 @@ impl ListState {
|
||||||
}
|
}
|
||||||
state.logical_scroll_top = Some(scroll_top);
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_reveal_item(&self, ix: usize) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
let mut scroll_top = state.logical_scroll_top();
|
||||||
|
let height = state
|
||||||
|
.last_layout_bounds
|
||||||
|
.map_or(px(0.), |bounds| bounds.size.height);
|
||||||
|
|
||||||
|
if ix <= scroll_top.item_ix {
|
||||||
|
scroll_top.item_ix = ix;
|
||||||
|
scroll_top.offset_in_item = px(0.);
|
||||||
|
} else {
|
||||||
|
let mut cursor = state.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Count(ix + 1), Bias::Right, &());
|
||||||
|
let bottom = cursor.start().height;
|
||||||
|
let goal_top = px(0.).max(bottom - height);
|
||||||
|
|
||||||
|
cursor.seek(&Height(goal_top), Bias::Left, &());
|
||||||
|
let start_ix = cursor.start().count;
|
||||||
|
let start_item_top = cursor.start().height;
|
||||||
|
|
||||||
|
if start_ix >= scroll_top.item_ix {
|
||||||
|
scroll_top.item_ix = start_ix;
|
||||||
|
scroll_top.offset_in_item = goal_top - start_item_top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the bounds for the given item in window coordinates.
|
||||||
|
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
||||||
|
let state = &*self.0.borrow();
|
||||||
|
let bounds = state.last_layout_bounds.unwrap_or_default();
|
||||||
|
let scroll_top = state.logical_scroll_top();
|
||||||
|
|
||||||
|
if ix < scroll_top.item_ix {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = state.items.cursor::<(Count, Height)>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
|
||||||
|
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
|
||||||
|
|
||||||
|
cursor.seek_forward(&Count(ix), Bias::Right, &());
|
||||||
|
if let Some(&ListItem::Rendered { height }) = cursor.item() {
|
||||||
|
let &(Count(count), Height(top)) = cursor.start();
|
||||||
|
if count == ix {
|
||||||
|
let top = bounds.top() + top - scroll_top;
|
||||||
|
return Some(Bounds::from_corners(
|
||||||
|
point(bounds.left(), top),
|
||||||
|
point(bounds.right(), top + height),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateInner {
|
impl StateInner {
|
||||||
|
@ -265,7 +324,9 @@ impl Element for List {
|
||||||
let state = &mut *self.state.0.borrow_mut();
|
let state = &mut *self.state.0.borrow_mut();
|
||||||
|
|
||||||
// If the width of the list has changed, invalidate all cached item heights
|
// If the width of the list has changed, invalidate all cached item heights
|
||||||
if state.last_layout_width != Some(bounds.size.width) {
|
if state.last_layout_bounds.map_or(true, |last_bounds| {
|
||||||
|
last_bounds.size.width != bounds.size.width
|
||||||
|
}) {
|
||||||
state.items = SumTree::from_iter(
|
state.items = SumTree::from_iter(
|
||||||
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||||
&(),
|
&(),
|
||||||
|
@ -392,7 +453,7 @@ impl Element for List {
|
||||||
}
|
}
|
||||||
|
|
||||||
state.items = new_items;
|
state.items = new_items;
|
||||||
state.last_layout_width = Some(bounds.size.width);
|
state.last_layout_bounds = Some(bounds);
|
||||||
|
|
||||||
let list_state = self.state.clone();
|
let list_state = self.state.clone();
|
||||||
let height = bounds.size.height;
|
let height = bounds.size.height;
|
||||||
|
|
|
@ -187,8 +187,6 @@ impl MetalRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn draw(&mut self, scene: &Scene) {
|
||||||
let start = std::time::Instant::now();
|
|
||||||
|
|
||||||
let layer = self.layer.clone();
|
let layer = self.layer.clone();
|
||||||
let viewport_size = layer.drawable_size();
|
let viewport_size = layer.drawable_size();
|
||||||
let viewport_size: Size<DevicePixels> = size(
|
let viewport_size: Size<DevicePixels> = size(
|
||||||
|
@ -306,9 +304,6 @@ impl MetalRenderer {
|
||||||
command_buffer.commit();
|
command_buffer.commit();
|
||||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||||
|
|
||||||
let duration_since_start = start.elapsed();
|
|
||||||
println!("renderer draw: {:?}", duration_since_start);
|
|
||||||
|
|
||||||
command_buffer.wait_until_completed();
|
command_buffer.wait_until_completed();
|
||||||
drawable.present();
|
drawable.present();
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,20 +209,9 @@ impl AnyView {
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
cx.with_absolute_element_offset(origin, |cx| {
|
cx.with_absolute_element_offset(origin, |cx| {
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
let (layout_id, mut rendered_element) = (self.layout)(self, cx);
|
let (layout_id, mut rendered_element) = (self.layout)(self, cx);
|
||||||
let duration = start_time.elapsed();
|
|
||||||
println!("request layout: {:?}", duration);
|
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
cx.compute_layout(layout_id, available_space);
|
cx.compute_layout(layout_id, available_space);
|
||||||
let duration = start_time.elapsed();
|
|
||||||
println!("compute layout: {:?}", duration);
|
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
(self.paint)(self, &mut rendered_element, cx);
|
(self.paint)(self, &mut rendered_element, cx);
|
||||||
let duration = start_time.elapsed();
|
|
||||||
println!("paint: {:?}", duration);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1267,7 +1267,6 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
/// Draw pixels to the display for this window based on the contents of its scene.
|
/// Draw pixels to the display for this window based on the contents of its scene.
|
||||||
pub(crate) fn draw(&mut self) -> Scene {
|
pub(crate) fn draw(&mut self) -> Scene {
|
||||||
let t0 = std::time::Instant::now();
|
|
||||||
self.window.dirty = false;
|
self.window.dirty = false;
|
||||||
self.window.drawing = true;
|
self.window.drawing = true;
|
||||||
|
|
||||||
|
@ -1369,7 +1368,6 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.drawing = false;
|
self.window.drawing = false;
|
||||||
eprintln!("window draw: {:?}", t0.elapsed());
|
|
||||||
|
|
||||||
scene
|
scene
|
||||||
}
|
}
|
||||||
|
|
|
@ -1389,7 +1389,9 @@ impl ProjectPanel {
|
||||||
entry_id: *entry_id,
|
entry_id: *entry_id,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.drag_over::<ProjectEntryId>(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
.drag_over::<ProjectEntryId>(|style| {
|
||||||
|
style.bg(cx.theme().colors().drop_target_background)
|
||||||
|
})
|
||||||
.on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
|
.on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
|
||||||
this.move_entry(*dragged_id, entry_id, kind.is_file(), cx);
|
this.move_entry(*dragged_id, entry_id, kind.is_file(), cx);
|
||||||
}))
|
}))
|
||||||
|
@ -1399,7 +1401,7 @@ impl ProjectPanel {
|
||||||
.indent_step_size(px(settings.indent_size))
|
.indent_step_size(px(settings.indent_size))
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
.child(if let Some(icon) = &icon {
|
.child(if let Some(icon) = &icon {
|
||||||
div().child(IconElement::from_path(icon.to_string()))
|
div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
|
||||||
} else {
|
} else {
|
||||||
div()
|
div()
|
||||||
})
|
})
|
||||||
|
|
|
@ -76,7 +76,10 @@ impl RecentProjects {
|
||||||
let delegate =
|
let delegate =
|
||||||
RecentProjectsDelegate::new(weak_workspace, workspace_locations, true);
|
RecentProjectsDelegate::new(weak_workspace, workspace_locations, true);
|
||||||
|
|
||||||
RecentProjects::new(delegate, cx)
|
let modal = RecentProjects::new(delegate, cx);
|
||||||
|
cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent))
|
||||||
|
.detach();
|
||||||
|
modal
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
workspace.show_notification(0, cx, |cx| {
|
workspace.show_notification(0, cx, |cx| {
|
||||||
|
|
|
@ -338,7 +338,9 @@ impl BufferSearchBar {
|
||||||
pane.update(cx, |this, cx| {
|
pane.update(cx, |this, cx| {
|
||||||
this.toolbar().update(cx, |this, cx| {
|
this.toolbar().update(cx, |this, cx| {
|
||||||
if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
|
if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
|
||||||
search_bar.update(cx, |this, cx| this.toggle(deploy, cx));
|
search_bar.update(cx, |this, cx| {
|
||||||
|
this.deploy(deploy, cx);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let view = cx.build_view(|cx| BufferSearchBar::new(cx));
|
let view = cx.build_view(|cx| BufferSearchBar::new(cx));
|
||||||
|
@ -1483,9 +1485,9 @@ mod tests {
|
||||||
search_bar.select_all_matches(&SelectAllMatches, cx);
|
search_bar.select_all_matches(&SelectAllMatches, cx);
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(
|
||||||
editor.update(cx, |this, cx| !this.is_focused(cx.window_context())),
|
editor.update(cx, |this, cx| !this.is_focused(cx.window_context())),
|
||||||
"Should not switch focus to editor if SelectAllMatches does not find any matches"
|
"Should not switch focus to editor if SelectAllMatches does not find any matches"
|
||||||
);
|
);
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
let all_selections =
|
let all_selections =
|
||||||
editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
|
editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
|
||||||
|
@ -1651,6 +1653,7 @@ mod tests {
|
||||||
assert_eq!(search_bar.search_options, SearchOptions::NONE);
|
assert_eq!(search_bar.search_options, SearchOptions::NONE);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_replace_simple(cx: &mut TestAppContext) {
|
async fn test_replace_simple(cx: &mut TestAppContext) {
|
||||||
let (editor, search_bar, cx) = init_test(cx);
|
let (editor, search_bar, cx) = init_test(cx);
|
||||||
|
|
|
@ -1536,13 +1536,30 @@ impl Render for ProjectSearchBar {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
|
.selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx))
|
||||||
.on_click(cx.listener(
|
.on_click(cx.listener(
|
||||||
|this, _, cx| {
|
|this, _, cx| {
|
||||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
this.toggle_search_option(
|
||||||
|
SearchOptions::CASE_SENSITIVE,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("project-search-whole-word", Icon::WholeWord)
|
||||||
|
.tooltip(|cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Toggle whole word",
|
||||||
|
&ToggleWholeWord,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,3 +8,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
smallvec.workspace = true
|
||||||
|
itertools = {package = "itertools", version = "0.10"}
|
||||||
|
|
|
@ -1,22 +1,199 @@
|
||||||
use gpui::prelude::*;
|
use gpui::{
|
||||||
use gpui::{div, hsla, Div, SharedString};
|
div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, Stateful,
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
pub fn reasonably_unique_id() -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let timestamp = now.duration_since(UNIX_EPOCH).unwrap();
|
||||||
|
|
||||||
|
let cnt = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let id = format!("{}_{}", timestamp.as_nanos(), cnt);
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StoryColor {
|
||||||
|
pub primary: Hsla,
|
||||||
|
pub secondary: Hsla,
|
||||||
|
pub border: Hsla,
|
||||||
|
pub background: Hsla,
|
||||||
|
pub card_background: Hsla,
|
||||||
|
pub divider: Hsla,
|
||||||
|
pub link: Hsla,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryColor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
primary: hsla(216. / 360., 11. / 100., 0. / 100., 1.),
|
||||||
|
secondary: hsla(216. / 360., 11. / 100., 16. / 100., 1.),
|
||||||
|
border: hsla(216. / 360., 11. / 100., 91. / 100., 1.),
|
||||||
|
background: hsla(0. / 360., 0. / 100., 100. / 100., 1.),
|
||||||
|
card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.),
|
||||||
|
divider: hsla(216. / 360., 11. / 100., 86. / 100., 1.),
|
||||||
|
link: hsla(206. / 360., 100. / 100., 50. / 100., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn story_color() -> StoryColor {
|
||||||
|
StoryColor::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct StoryContainer {
|
||||||
|
title: SharedString,
|
||||||
|
relative_path: &'static str,
|
||||||
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryContainer {
|
||||||
|
pub fn new(title: impl Into<SharedString>, relative_path: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
title: title.into(),
|
||||||
|
relative_path,
|
||||||
|
children: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for StoryContainer {
|
||||||
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for StoryContainer {
|
||||||
|
type Rendered = Stateful<Div>;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.id("story_container")
|
||||||
|
.bg(story_color().background)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_none()
|
||||||
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
|
.p_2()
|
||||||
|
.bg(story_color().background)
|
||||||
|
.border_b()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.child(Story::title(self.title))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().primary)
|
||||||
|
.child(Story::open_story_link(self.relative_path)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.h_px()
|
||||||
|
.flex_1()
|
||||||
|
.id("story_body")
|
||||||
|
.overflow_hidden_x()
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.pb_4()
|
||||||
|
.children(self.children),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Story {}
|
pub struct Story {}
|
||||||
|
|
||||||
impl Story {
|
impl Story {
|
||||||
pub fn container() -> Div {
|
pub fn container() -> Div {
|
||||||
div().size_full().flex().flex_col().pt_2().px_4().bg(hsla(
|
div().size_full().overflow_hidden().child(
|
||||||
0. / 360.,
|
div()
|
||||||
0. / 100.,
|
.id("story_container")
|
||||||
100. / 100.,
|
.overflow_y_scroll()
|
||||||
1.,
|
.w_full()
|
||||||
))
|
.min_h_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.bg(story_color().background),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move all stories to container2, then rename
|
||||||
|
pub fn container2<T>(relative_path: &'static str) -> Div {
|
||||||
|
div().size_full().child(
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.id("story_container")
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.flex_none()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.justify_between()
|
||||||
|
.p_2()
|
||||||
|
.border_b()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.child(Story::title_for::<T>())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().primary)
|
||||||
|
.child(Story::open_story_link(relative_path)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.min_h_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.bg(story_color().background),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_story_link(relative_path: &'static str) -> impl Element {
|
||||||
|
let path = PathBuf::from_iter([relative_path]);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().primary)
|
||||||
|
.id(SharedString::from(format!("id_{}", relative_path)))
|
||||||
|
.on_click({
|
||||||
|
let path = path.clone();
|
||||||
|
|
||||||
|
move |_event, _cx| {
|
||||||
|
let path = format!("{}:0:0", path.to_string_lossy());
|
||||||
|
|
||||||
|
std::process::Command::new("zed").arg(path).spawn().ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.children(vec![div().child(Story::link("Open in Zed →"))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(title: impl Into<SharedString>) -> impl Element {
|
pub fn title(title: impl Into<SharedString>) -> impl Element {
|
||||||
div()
|
div()
|
||||||
.text_xl()
|
.text_xs()
|
||||||
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
|
.text_color(story_color().primary)
|
||||||
.child(title.into())
|
.child(title.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,12 +201,185 @@ impl Story {
|
||||||
Self::title(std::any::type_name::<T>())
|
Self::title(std::any::type_name::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn section() -> Div {
|
||||||
|
div()
|
||||||
|
.p_4()
|
||||||
|
.m_4()
|
||||||
|
.border()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn section_title() -> Div {
|
||||||
|
div().text_lg().text_color(story_color().primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group() -> Div {
|
||||||
|
div().my_2().bg(story_color().background)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code_block(code: impl Into<SharedString>) -> Div {
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.p_2()
|
||||||
|
.max_w(rems(36.))
|
||||||
|
.bg(gpui::black())
|
||||||
|
.rounded_md()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(gpui::white())
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(code.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn divider() -> Div {
|
||||||
|
div().my_2().h(px(1.)).bg(story_color().divider)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(link: impl Into<SharedString>) -> impl Element {
|
||||||
|
div()
|
||||||
|
.id(ElementId::from(SharedString::from(reasonably_unique_id())))
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().link)
|
||||||
|
.cursor(gpui::CursorStyle::PointingHand)
|
||||||
|
.child(link.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(description: impl Into<SharedString>) -> impl Element {
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(story_color().secondary)
|
||||||
|
.min_w_96()
|
||||||
|
.child(description.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn label(label: impl Into<SharedString>) -> impl Element {
|
pub fn label(label: impl Into<SharedString>) -> impl Element {
|
||||||
div()
|
div()
|
||||||
.mt_4()
|
|
||||||
.mb_2()
|
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
|
.text_color(story_color().primary)
|
||||||
.child(label.into())
|
.child(label.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate.
|
||||||
|
pub fn v_stack() -> Div {
|
||||||
|
div().flex().flex_col().gap_1()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct StoryItem {
|
||||||
|
label: SharedString,
|
||||||
|
item: AnyElement,
|
||||||
|
description: Option<SharedString>,
|
||||||
|
usage: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryItem {
|
||||||
|
pub fn new(label: impl Into<SharedString>, item: impl IntoElement) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
item: item.into_any_element(),
|
||||||
|
description: None,
|
||||||
|
usage: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage(mut self, code: impl Into<SharedString>) -> Self {
|
||||||
|
self.usage = Some(code.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for StoryItem {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
div()
|
||||||
|
.my_2()
|
||||||
|
.flex()
|
||||||
|
.gap_4()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
Story::v_stack()
|
||||||
|
.px_2()
|
||||||
|
.w_1_2()
|
||||||
|
.min_h_px()
|
||||||
|
.child(Story::label(self.label))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.rounded_md()
|
||||||
|
.bg(story_color().card_background)
|
||||||
|
.border()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(self.item),
|
||||||
|
)
|
||||||
|
.when_some(self.description, |this, description| {
|
||||||
|
this.child(Story::description(description))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Story::v_stack()
|
||||||
|
.px_2()
|
||||||
|
.flex_none()
|
||||||
|
.w_1_2()
|
||||||
|
.min_h_px()
|
||||||
|
.when_some(self.usage, |this, usage| {
|
||||||
|
this.child(Story::label("Example Usage"))
|
||||||
|
.child(Story::code_block(usage))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct StorySection {
|
||||||
|
description: Option<SharedString>,
|
||||||
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorySection {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
description: None,
|
||||||
|
children: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for StorySection {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with(
|
||||||
|
self.children.into_iter(),
|
||||||
|
|| Story::divider().into_any_element(),
|
||||||
|
));
|
||||||
|
|
||||||
|
Story::section()
|
||||||
|
// Section title
|
||||||
|
.py_2()
|
||||||
|
// Section description
|
||||||
|
.when_some(self.description.clone(), |section, description| {
|
||||||
|
section.child(Story::description(description))
|
||||||
|
})
|
||||||
|
.child(div().flex().flex_col().gap_2().children(children))
|
||||||
|
.child(Story::divider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for StorySection {
|
||||||
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||||
editor = { package = "editor2", path = "../editor2" }
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
indoc.workspace = true
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
|
div, green, red, Component, HighlightStyle, InteractiveText, IntoElement, ParentElement,
|
||||||
Styled, StyledText, View, VisualContext, WindowContext,
|
Render, Styled, StyledText, View, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use ui::v_stack;
|
use indoc::indoc;
|
||||||
|
use story::*;
|
||||||
|
|
||||||
pub struct TextStory;
|
pub struct TextStory;
|
||||||
|
|
||||||
|
@ -13,62 +14,164 @@ impl TextStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for TextStory {
|
impl Render for TextStory {
|
||||||
type Element = Div;
|
type Element = Component<StoryContainer>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
v_stack()
|
StoryContainer::new("Text Story", "crates/storybook2/src/stories/text.rs")
|
||||||
.bg(blue())
|
.children(
|
||||||
.child(
|
vec![
|
||||||
div()
|
|
||||||
.flex()
|
StorySection::new()
|
||||||
.child(div().max_w_96().bg(white()).child(concat!(
|
.child(
|
||||||
"max-width: 96. The quick brown fox jumps over the lazy dog. ",
|
StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!"))
|
||||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
.usage(indoc! {r##"
|
||||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
div()
|
||||||
))),
|
.child("Hello World!")
|
||||||
)
|
"##
|
||||||
.child(div().h_5())
|
|
||||||
.child(div().flex().flex_col().w_96().bg(white()).child(concat!(
|
|
||||||
"flex-col. width: 96; The quick brown fox jumps over the lazy dog. ",
|
|
||||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
|
||||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
|
||||||
)))
|
|
||||||
.child(div().h_5())
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.child(div().min_w_96().bg(white()).child(concat!(
|
|
||||||
"min-width: 96. The quick brown fox jumps over the lazy dog. ",
|
|
||||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
|
||||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
|
||||||
))))
|
|
||||||
.child(div().h_5())
|
|
||||||
.child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!(
|
|
||||||
"flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
|
|
||||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
|
||||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
|
||||||
))))
|
|
||||||
// NOTE: When rendering text in a horizonal flex container,
|
|
||||||
// Taffy will not pass width constraints down from the parent.
|
|
||||||
// To fix this, render text in a praent with overflow: hidden, which
|
|
||||||
.child(div().h_5())
|
|
||||||
.child(div().flex().w_96().bg(red()).child(concat!(
|
|
||||||
"flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
|
|
||||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
|
||||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
|
||||||
))).child(
|
|
||||||
InteractiveText::new(
|
|
||||||
"interactive",
|
|
||||||
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
|
||||||
(6..11, HighlightStyle {
|
|
||||||
background_color: Some(green()),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
}),
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
.child(
|
||||||
println!("Clicked range {range_ix}");
|
StoryItem::new("Wrapping Text",
|
||||||
})
|
div().max_w_96()
|
||||||
)
|
.child(
|
||||||
|
concat!(
|
||||||
|
"The quick brown fox jumps over the lazy dog. ",
|
||||||
|
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.description("Set a width or max-width to enable text wrapping.")
|
||||||
|
.usage(indoc! {r##"
|
||||||
|
div()
|
||||||
|
.max_w_96()
|
||||||
|
.child("Some text that you want to wrap.")
|
||||||
|
"##
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
StoryItem::new("tbd",
|
||||||
|
div().flex().w_96().child(div().overflow_hidden().child(concat!(
|
||||||
|
"flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
StoryItem::new("Text in Horizontal Flex",
|
||||||
|
div().flex().w_96().bg(red()).child(concat!(
|
||||||
|
"flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
))
|
||||||
|
)
|
||||||
|
.usage(indoc! {r##"
|
||||||
|
// NOTE: When rendering text in a horizonal flex container,
|
||||||
|
// Taffy will not pass width constraints down from the parent.
|
||||||
|
// To fix this, render text in a parent with overflow: hidden
|
||||||
|
|
||||||
|
div()
|
||||||
|
.max_w_96()
|
||||||
|
.child("Some text that you want to wrap.")
|
||||||
|
"##
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
StoryItem::new("Interactive Text",
|
||||||
|
InteractiveText::new(
|
||||||
|
"interactive",
|
||||||
|
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||||
|
(6..11, HighlightStyle {
|
||||||
|
background_color: Some(green()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||||
|
println!("Clicked range {range_ix}");
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.usage(indoc! {r##"
|
||||||
|
InteractiveText::new(
|
||||||
|
"interactive",
|
||||||
|
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||||
|
(6..11, HighlightStyle {
|
||||||
|
background_color: Some(green()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||||
|
println!("Clicked range {range_ix}");
|
||||||
|
})
|
||||||
|
"##
|
||||||
|
})
|
||||||
|
)
|
||||||
|
]
|
||||||
|
).into_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Check all were updated to new style and remove
|
||||||
|
|
||||||
|
// impl Render for TextStory {
|
||||||
|
// type Element = Div;
|
||||||
|
|
||||||
|
// fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
|
// v_stack()
|
||||||
|
// .bg(blue())
|
||||||
|
// .child(
|
||||||
|
// div()
|
||||||
|
// .flex()
|
||||||
|
// .child(div().max_w_96().bg(white()).child(concat!(
|
||||||
|
// "max-width: 96. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
// ))),
|
||||||
|
// )
|
||||||
|
// .child(div().h_5())
|
||||||
|
// .child(div().flex().flex_col().w_96().bg(white()).child(concat!(
|
||||||
|
// "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ",
|
||||||
|
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
// )))
|
||||||
|
// .child(div().h_5())
|
||||||
|
// .child(
|
||||||
|
// div()
|
||||||
|
// .flex()
|
||||||
|
// .child(div().min_w_96().bg(white()).child(concat!(
|
||||||
|
// "min-width: 96. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
// ))))
|
||||||
|
// .child(div().h_5())
|
||||||
|
// .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!(
|
||||||
|
// "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
// ))))
|
||||||
|
// // NOTE: When rendering text in a horizonal flex container,
|
||||||
|
// // Taffy will not pass width constraints down from the parent.
|
||||||
|
// // To fix this, render text in a parent with overflow: hidden
|
||||||
|
// .child(div().h_5())
|
||||||
|
// .child(div().flex().w_96().bg(red()).child(concat!(
|
||||||
|
// "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
|
||||||
|
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||||
|
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||||
|
// ))).child(
|
||||||
|
// InteractiveText::new(
|
||||||
|
// "interactive",
|
||||||
|
// StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||||
|
// (6..11, HighlightStyle {
|
||||||
|
// background_color: Some(green()),
|
||||||
|
// ..Default::default()
|
||||||
|
// }),
|
||||||
|
// ]),
|
||||||
|
// )
|
||||||
|
// .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||||
|
// println!("Clicked range {range_ix}");
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -19,6 +19,7 @@ use ui::prelude::*;
|
||||||
|
|
||||||
use crate::assets::Assets;
|
use crate::assets::Assets;
|
||||||
use crate::story_selector::{ComponentStory, StorySelector};
|
use crate::story_selector::{ComponentStory, StorySelector};
|
||||||
|
pub use indoc::indoc;
|
||||||
|
|
||||||
// gpui::actions! {
|
// gpui::actions! {
|
||||||
// storybook,
|
// storybook,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::{Div, Render};
|
use gpui::{Component, Render};
|
||||||
use story::Story;
|
use story::{StoryContainer, StoryItem, StorySection};
|
||||||
|
|
||||||
use crate::{prelude::*, Tooltip};
|
use crate::{prelude::*, Tooltip};
|
||||||
use crate::{Icon, IconButton};
|
use crate::{Icon, IconButton};
|
||||||
|
@ -7,57 +7,167 @@ use crate::{Icon, IconButton};
|
||||||
pub struct IconButtonStory;
|
pub struct IconButtonStory;
|
||||||
|
|
||||||
impl Render for IconButtonStory {
|
impl Render for IconButtonStory {
|
||||||
type Element = Div;
|
type Element = Component<StoryContainer>;
|
||||||
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
let default_button = StoryItem::new(
|
||||||
.child(Story::title_for::<IconButton>())
|
"Default",
|
||||||
.child(Story::label("Default"))
|
IconButton::new("default_icon_button", Icon::Hash),
|
||||||
.child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
|
)
|
||||||
.child(Story::label("Selected"))
|
.description("Displays an icon button.")
|
||||||
.child(
|
.usage(
|
||||||
div()
|
r#"
|
||||||
.w_8()
|
IconButton::new("default_icon_button", Icon::Hash)
|
||||||
.child(IconButton::new("icon_a", Icon::Hash).selected(true)),
|
"#,
|
||||||
)
|
);
|
||||||
.child(Story::label("Selected with `selected_icon`"))
|
|
||||||
.child(
|
let selected_button = StoryItem::new(
|
||||||
div().w_8().child(
|
"Selected",
|
||||||
IconButton::new("icon_a", Icon::AudioOn)
|
IconButton::new("selected_icon_button", Icon::Hash).selected(true),
|
||||||
.selected(true)
|
)
|
||||||
.selected_icon(Icon::AudioOff),
|
.description("Displays an icon button that is selected.")
|
||||||
),
|
.usage(
|
||||||
)
|
r#"
|
||||||
.child(Story::label("Disabled"))
|
IconButton::new("selected_icon_button", Icon::Hash).selected(true)
|
||||||
.child(
|
"#,
|
||||||
div()
|
);
|
||||||
.w_8()
|
|
||||||
.child(IconButton::new("icon_a", Icon::Hash).disabled(true)),
|
let selected_with_selected_icon = StoryItem::new(
|
||||||
)
|
"Selected with `selected_icon`",
|
||||||
.child(Story::label("With `on_click`"))
|
IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)
|
||||||
.child(
|
.selected(true)
|
||||||
div()
|
.selected_icon(Icon::AudioOff),
|
||||||
.w_8()
|
)
|
||||||
.child(
|
.description(
|
||||||
IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| {
|
"Displays an icon button that is selected and shows a different icon when selected.",
|
||||||
println!("Clicked!");
|
)
|
||||||
}),
|
.usage(
|
||||||
),
|
r#"
|
||||||
)
|
IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)
|
||||||
.child(Story::label("With `tooltip`"))
|
.selected(true)
|
||||||
.child(
|
.selected_icon(Icon::AudioOff)
|
||||||
div().w_8().child(
|
"#,
|
||||||
IconButton::new("with_tooltip", Icon::MessageBubbles)
|
);
|
||||||
.tooltip(|cx| Tooltip::text("Open messages", cx)),
|
|
||||||
),
|
let disabled_button = StoryItem::new(
|
||||||
)
|
"Disabled",
|
||||||
.child(Story::label("Selected with `tooltip`"))
|
IconButton::new("disabled_icon_button", Icon::Hash).disabled(true),
|
||||||
.child(
|
)
|
||||||
div().w_8().child(
|
.description("Displays an icon button that is disabled.")
|
||||||
IconButton::new("selected_with_tooltip", Icon::InlayHint)
|
.usage(
|
||||||
.selected(true)
|
r#"
|
||||||
.tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
|
IconButton::new("disabled_icon_button", Icon::Hash).disabled(true)
|
||||||
),
|
"#,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
let with_on_click_button = StoryItem::new(
|
||||||
|
"With `on_click`",
|
||||||
|
IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| {
|
||||||
|
println!("Clicked!");
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.description("Displays an icon button which triggers an event on click.")
|
||||||
|
.usage(
|
||||||
|
r#"
|
||||||
|
IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| {
|
||||||
|
println!("Clicked!");
|
||||||
|
})
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let with_tooltip_button = StoryItem::new(
|
||||||
|
"With `tooltip`",
|
||||||
|
IconButton::new("with_tooltip_button", Icon::MessageBubbles)
|
||||||
|
.tooltip(|cx| Tooltip::text("Open messages", cx)),
|
||||||
|
)
|
||||||
|
.description("Displays an icon button that has a tooltip when hovered.")
|
||||||
|
.usage(
|
||||||
|
r#"
|
||||||
|
IconButton::new("with_tooltip_button", Icon::MessageBubbles)
|
||||||
|
.tooltip(|cx| Tooltip::text("Open messages", cx))
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let selected_with_tooltip_button = StoryItem::new(
|
||||||
|
"Selected with `tooltip`",
|
||||||
|
IconButton::new("selected_with_tooltip_button", Icon::InlayHint)
|
||||||
|
.selected(true)
|
||||||
|
.tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
|
||||||
|
)
|
||||||
|
.description("Displays a selected icon button with tooltip.")
|
||||||
|
.usage(
|
||||||
|
r#"
|
||||||
|
IconButton::new("selected_with_tooltip_button", Icon::InlayHint)
|
||||||
|
.selected(true)
|
||||||
|
.tooltip(|cx| Tooltip::text("Toggle inlay hints", cx))
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let buttons = vec![
|
||||||
|
default_button,
|
||||||
|
selected_button,
|
||||||
|
selected_with_selected_icon,
|
||||||
|
disabled_button,
|
||||||
|
with_on_click_button,
|
||||||
|
with_tooltip_button,
|
||||||
|
selected_with_tooltip_button,
|
||||||
|
];
|
||||||
|
|
||||||
|
StoryContainer::new(
|
||||||
|
"Icon Button",
|
||||||
|
"crates/ui2/src/components/stories/icon_button.rs",
|
||||||
|
)
|
||||||
|
.children(vec![StorySection::new().children(buttons)])
|
||||||
|
.into_element()
|
||||||
|
|
||||||
|
// Story::container()
|
||||||
|
// .child(Story::title_for::<IconButton>())
|
||||||
|
// .child(Story::label("Default"))
|
||||||
|
// .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
|
||||||
|
// .child(Story::label("Selected"))
|
||||||
|
// .child(
|
||||||
|
// div()
|
||||||
|
// .w_8()
|
||||||
|
// .child(IconButton::new("icon_a", Icon::Hash).selected(true)),
|
||||||
|
// )
|
||||||
|
// .child(Story::label("Selected with `selected_icon`"))
|
||||||
|
// .child(
|
||||||
|
// div().w_8().child(
|
||||||
|
// IconButton::new("icon_a", Icon::AudioOn)
|
||||||
|
// .selected(true)
|
||||||
|
// .selected_icon(Icon::AudioOff),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .child(Story::label("Disabled"))
|
||||||
|
// .child(
|
||||||
|
// div()
|
||||||
|
// .w_8()
|
||||||
|
// .child(IconButton::new("icon_a", Icon::Hash).disabled(true)),
|
||||||
|
// )
|
||||||
|
// .child(Story::label("With `on_click`"))
|
||||||
|
// .child(
|
||||||
|
// div()
|
||||||
|
// .w_8()
|
||||||
|
// .child(
|
||||||
|
// IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| {
|
||||||
|
// println!("Clicked!");
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .child(Story::label("With `tooltip`"))
|
||||||
|
// .child(
|
||||||
|
// div().w_8().child(
|
||||||
|
// IconButton::new("with_tooltip", Icon::MessageBubbles)
|
||||||
|
// .tooltip(|cx| Tooltip::text("Open messages", cx)),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .child(Story::label("Selected with `tooltip`"))
|
||||||
|
// .child(
|
||||||
|
// div().w_8().child(
|
||||||
|
// IconButton::new("selected_with_tooltip", Icon::InlayHint)
|
||||||
|
// .selected(true)
|
||||||
|
// .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,13 @@ impl Render for Tooltip {
|
||||||
v_stack()
|
v_stack()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.text_ui_sm()
|
.text_ui()
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.py_1()
|
.py_1()
|
||||||
.px_2()
|
.px_2()
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_2()
|
.gap_4()
|
||||||
.child(self.title.clone())
|
.child(self.title.clone())
|
||||||
.when_some(self.key_binding.clone(), |this, key_binding| {
|
.when_some(self.key_binding.clone(), |this, key_binding| {
|
||||||
this.justify_between().child(key_binding)
|
this.justify_between().child(key_binding)
|
||||||
|
|
|
@ -65,8 +65,13 @@ impl ModalBranchList {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Modal branch picker has a longer trailoff than a popover one.
|
// Modal branch picker has a longer trailoff than a popover one.
|
||||||
let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
|
let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
|
||||||
workspace.toggle_modal(cx, |cx| ModalBranchList {
|
workspace.toggle_modal(cx, |cx| {
|
||||||
picker: cx.build_view(|cx| Picker::new(delegate, cx)),
|
let modal = ModalBranchList {
|
||||||
|
picker: cx.build_view(|cx| Picker::new(delegate, cx)),
|
||||||
|
};
|
||||||
|
cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent))
|
||||||
|
.detach();
|
||||||
|
modal
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1897,19 +1897,14 @@ impl Render for Pane {
|
||||||
.on_drag_move::<ProjectEntryId>(cx.listener(Self::handle_drag_move))
|
.on_drag_move::<ProjectEntryId>(cx.listener(Self::handle_drag_move))
|
||||||
.map(|div| {
|
.map(|div| {
|
||||||
if let Some(item) = self.active_item() {
|
if let Some(item) = self.active_item() {
|
||||||
div.flex_col()
|
div.v_flex()
|
||||||
.child(self.toolbar.clone())
|
.child(self.toolbar.clone())
|
||||||
.child(item.to_any())
|
.child(item.to_any())
|
||||||
} else {
|
} else {
|
||||||
div.flex()
|
div.h_flex().size_full().justify_center().child(
|
||||||
.flex_row()
|
Label::new("Open a file or project to get started.")
|
||||||
.items_center()
|
.color(Color::Muted),
|
||||||
.size_full()
|
)
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
Label::new("Open a file or project to get started.")
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
|
|
|
@ -693,7 +693,8 @@ mod element {
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
|
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Style, WindowContext,
|
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -736,7 +737,8 @@ mod element {
|
||||||
e: &MouseMoveEvent,
|
e: &MouseMoveEvent,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
axis_bounds: Bounds<Pixels>,
|
child_start: Point<Pixels>,
|
||||||
|
container_size: Size<Pixels>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let min_size = match axis {
|
let min_size = match axis {
|
||||||
|
@ -747,7 +749,7 @@ mod element {
|
||||||
debug_assert!(flex_values_in_bounds(flexes.as_slice()));
|
debug_assert!(flex_values_in_bounds(flexes.as_slice()));
|
||||||
|
|
||||||
let size = move |ix, flexes: &[f32]| {
|
let size = move |ix, flexes: &[f32]| {
|
||||||
axis_bounds.size.along(axis) * (flexes[ix] / flexes.len() as f32)
|
container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't allow resizing to less than the minimum size, if elements are already too small
|
// Don't allow resizing to less than the minimum size, if elements are already too small
|
||||||
|
@ -756,10 +758,10 @@ mod element {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut proposed_current_pixel_change =
|
let mut proposed_current_pixel_change =
|
||||||
(e.position - axis_bounds.origin).along(axis) - size(ix, flexes.as_slice());
|
(e.position - child_start).along(axis) - size(ix, flexes.as_slice());
|
||||||
|
|
||||||
let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
|
let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
|
||||||
let flex_change = pixel_dx / axis_bounds.size.along(axis);
|
let flex_change = pixel_dx / container_size.along(axis);
|
||||||
let current_target_flex = flexes[target_ix] + flex_change;
|
let current_target_flex = flexes[target_ix] + flex_change;
|
||||||
let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
|
let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
|
||||||
(current_target_flex, next_target_flex)
|
(current_target_flex, next_target_flex)
|
||||||
|
@ -854,7 +856,15 @@ mod element {
|
||||||
cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
|
||||||
let dragged_handle = dragged_handle.borrow();
|
let dragged_handle = dragged_handle.borrow();
|
||||||
if phase.bubble() && *dragged_handle == Some(ix) {
|
if phase.bubble() && *dragged_handle == Some(ix) {
|
||||||
Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx)
|
Self::compute_resize(
|
||||||
|
&flexes,
|
||||||
|
e,
|
||||||
|
ix,
|
||||||
|
axis,
|
||||||
|
pane_bounds.origin,
|
||||||
|
axis_bounds.size,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue