History

Merge branch 'main' into guest-promotion

Conrad Irwin committed 2 years ago

Commit 276bfa02 parents 4da9d61 0daa2bf121 files · +1644 −899Browse files

Unified · Split

121 files
  1. Cargo.lock +2 −0
  2. crates/assistant/src/assistant_panel.rs +17 −17
  3. crates/auto_update/src/update_notification.rs +2 −2
  4. crates/call/src/call_settings.rs +3 −0
  5. crates/client/src/user.rs +2 −2
  6. crates/collab_ui/Cargo.toml +3 −0
  7. crates/collab_ui/src/chat_panel.rs +7 −7
  8. crates/collab_ui/src/collab_panel.rs +33 −29
  9. crates/collab_ui/src/collab_panel/channel_modal.rs +2 −2
  10. crates/collab_ui/src/collab_panel/contact_finder.rs +1 −3
  11. crates/collab_ui/src/collab_titlebar_item.rs +10 −10
  12. crates/collab_ui/src/notification_panel.rs +6 −6
  13. crates/collab_ui/src/notifications.rs +9 −2
  14. crates/collab_ui/src/notifications/collab_notification.rs +52 −0
  15. crates/collab_ui/src/notifications/incoming_call_notification.rs +18 −34
  16. crates/collab_ui/src/notifications/project_shared_notification.rs +27 −47
  17. crates/collab_ui/src/notifications/stories.rs +3 −0
  18. crates/collab_ui/src/notifications/stories/collab_notification.rs +50 −0
  19. crates/copilot_ui/src/copilot_button.rs +7 −5
  20. crates/copilot_ui/src/sign_in.rs +2 −2
  21. crates/diagnostics/src/diagnostics.rs +5 −7
  22. crates/diagnostics/src/items.rs +7 −7
  23. crates/diagnostics/src/toolbar_controls.rs +2 −2
  24. crates/editor/src/display_map.rs +0 −1
  25. crates/editor/src/editor.rs +25 −7
  26. crates/editor/src/element.rs +55 −46
  27. crates/editor/src/hover_popover.rs +13 −11
  28. crates/editor/src/scroll.rs +6 −4
  29. crates/editor/src/test.rs +2 −4
  30. crates/feedback/src/deploy_feedback_button.rs +2 −2
  31. crates/feedback/src/feedback_modal.rs +1 −1
  32. crates/gpui/docs/key_dispatch.md +2 −2
  33. crates/gpui/src/action.rs +17 −6
  34. crates/gpui/src/app.rs +28 −13
  35. crates/gpui/src/app/async_context.rs +6 −0
  36. crates/gpui/src/app/entity_map.rs +4 −1
  37. crates/gpui/src/app/test_context.rs +76 −6
  38. crates/gpui/src/element.rs +5 −5
  39. crates/gpui/src/elements/img.rs +4 −4
  40. crates/gpui/src/elements/overlay.rs +1 −1
  41. crates/gpui/src/executor.rs +42 −2
  42. crates/gpui/src/gpui.rs +2 −0
  43. crates/gpui/src/image_cache.rs +3 −3
  44. crates/gpui/src/input.rs +1 −1
  45. crates/gpui/src/interactive.rs +14 −0
  46. crates/gpui/src/key_dispatch.rs +75 −2
  47. crates/gpui/src/platform/mac/display.rs +2 −2
  48. crates/gpui/src/platform/test/display.rs +1 −1
  49. crates/gpui/src/platform/test/platform.rs +1 −1
  50. crates/gpui/src/shared_url.rs +25 −0
  51. crates/gpui/src/subscription.rs +3 −3
  52. crates/gpui/src/test.rs +28 −0
  53. crates/gpui/src/window.rs +142 −12
  54. crates/gpui/tests/action_macros.rs +6 −6
  55. crates/gpui_macros/src/gpui_macros.rs +30 −0
  56. crates/language/src/syntax_map/syntax_map_tests.rs +14 −14
  57. crates/project_panel/src/project_panel.rs +8 −5
  58. crates/quick_action_bar/src/quick_action_bar.rs +6 −6
  59. crates/rope/src/rope.rs +3 −3
  60. crates/rpc/src/notification.rs +30 −25
  61. crates/search/src/buffer_search.rs +17 −12
  62. crates/search/src/project_search.rs +14 −13
  63. crates/search/src/search.rs +4 −4
  64. crates/search/src/search_bar.rs +1 −1
  65. crates/storybook/Cargo.toml +1 −0
  66. crates/storybook/src/story_selector.rs +4 −0
  67. crates/terminal_view/src/terminal_element.rs +12 −0
  68. crates/terminal_view/src/terminal_panel.rs +6 −6
  69. crates/terminal_view/src/terminal_view.rs +2 −2
  70. crates/text/src/anchor.rs +1 −1
  71. crates/theme/src/scale.rs +8 −1
  72. crates/theme/src/settings.rs +1 −1
  73. crates/theme/src/styles/colors.rs +4 −1
  74. crates/theme/src/styles/status.rs +0 −9
  75. crates/theme/src/theme.rs +8 −0
  76. crates/theme/theme.md +15 −0
  77. crates/ui/src/clickable.rs +1 −1
  78. crates/ui/src/components/avatar.rs +35 −5
  79. crates/ui/src/components/button/button.rs +175 −5
  80. crates/ui/src/components/button/button_icon.rs +6 −6
  81. crates/ui/src/components/button/button_like.rs +6 −2
  82. crates/ui/src/components/button/icon_button.rs +5 −5
  83. crates/ui/src/components/checkbox.rs +3 −3
  84. crates/ui/src/components/context_menu.rs +5 −5
  85. crates/ui/src/components/disclosure.rs +3 −3
  86. crates/ui/src/components/divider.rs +1 −0
  87. crates/ui/src/components/icon.rs +101 −101
  88. crates/ui/src/components/keybinding.rs +22 −22
  89. crates/ui/src/components/label/label.rs +57 −0
  90. crates/ui/src/components/label/label_like.rs +1 −0
  91. crates/ui/src/components/list/list_sub_header.rs +7 −8
  92. crates/ui/src/components/popover_menu.rs +1 −0
  93. crates/ui/src/components/right_click_menu.rs +1 −0
  94. crates/ui/src/components/stack.rs +2 −6
  95. crates/ui/src/components/stories/button.rs +3 −3
  96. crates/ui/src/components/stories/icon.rs +4 −4
  97. crates/ui/src/components/stories/icon_button.rs +9 −9
  98. crates/ui/src/components/stories/list_header.rs +7 −7
  99. crates/ui/src/components/stories/list_item.rs +13 −13
  100. crates/ui/src/components/stories/tab.rs +1 −1
  101. crates/ui/src/components/stories/tab_bar.rs +7 −4
  102. crates/ui/src/disableable.rs +1 −1
  103. crates/ui/src/fixed.rs +1 −1
  104. crates/ui/src/prelude.rs +3 −1
  105. crates/ui/src/selectable.rs +9 −0
  106. crates/ui/src/styled_ext.rs +14 −7
  107. crates/ui/src/styles/color.rs +1 −0
  108. crates/ui/src/styles/elevation.rs +1 −0
  109. crates/ui/src/styles/typography.rs +1 −0
  110. crates/ui/src/ui.rs +0 −2
  111. crates/ui/src/utils.rs +2 −0
  112. crates/ui/src/utils/format_distance.rs +10 −10
  113. crates/workspace/src/dock.rs +4 −5
  114. crates/workspace/src/modal_layer.rs +4 −0
  115. crates/workspace/src/notifications.rs +2 −101
  116. crates/workspace/src/pane.rs +9 −9
  117. crates/workspace/src/pane_group.rs +46 −22
  118. crates/workspace/src/shared_screen.rs +2 −2
  119. crates/workspace/src/toolbar.rs +0 −83
  120. crates/workspace/src/workspace.rs +7 −6
  121. crates/zed/src/zed.rs +0 −1
Cargo.lock+2−0
@@ -1561,6 +1561,7 @@ dependencies = [ · 1560 unmodified lines
15611561 "serde_json",
15621562 "settings",
15631563 "smallvec",
1564+ "story",
15641565 "theme",
15651566 "theme_selector",
15661567 "time",
@@ -7449,6 +7450,7 @@ dependencies = [ · 5882 unmodified lines
74497450 "backtrace-on-stack-overflow",
74507451 "chrono",
74517452 "clap 4.4.4",
7453+ "collab_ui",
74527454 "dialoguer",
74537455 "editor",
74547456 "fuzzy",
crates/assistant/src/assistant_panel.rs+17−17
@@ -933,7 +933,7 @@ impl AssistantPanel { · 932 unmodified lines
933933 }
934934
935935 fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
936− IconButton::new("hamburger_button", Icon::Menu)
936+ IconButton::new("hamburger_button", IconName::Menu)
937937 .on_click(cx.listener(|this, _event, cx| {
938938 if this.active_editor().is_some() {
939939 this.set_active_editor_index(None, cx);
@@ -957,7 +957,7 @@ impl AssistantPanel { · 17 unmodified lines
957957 }
958958
959959 fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
960− IconButton::new("split_button", Icon::Snip)
960+ IconButton::new("split_button", IconName::Snip)
961961 .on_click(cx.listener(|this, _event, cx| {
962962 if let Some(active_editor) = this.active_editor() {
963963 active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
@@ -968,7 +968,7 @@ impl AssistantPanel { · 4 unmodified lines
968968 }
969969
970970 fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
971− IconButton::new("assist_button", Icon::MagicWand)
971+ IconButton::new("assist_button", IconName::MagicWand)
972972 .on_click(cx.listener(|this, _event, cx| {
973973 if let Some(active_editor) = this.active_editor() {
974974 active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
@@ -979,7 +979,7 @@ impl AssistantPanel { · 4 unmodified lines
979979 }
980980
981981 fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
982− IconButton::new("quote_button", Icon::Quote)
982+ IconButton::new("quote_button", IconName::Quote)
983983 .on_click(cx.listener(|this, _event, cx| {
984984 if let Some(workspace) = this.workspace.upgrade() {
985985 cx.window_context().defer(move |cx| {
@@ -994,7 +994,7 @@ impl AssistantPanel { · 8 unmodified lines
994994 }
995995
996996 fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
997− IconButton::new("plus_button", Icon::Plus)
997+ IconButton::new("plus_button", IconName::Plus)
998998 .on_click(cx.listener(|this, _event, cx| {
999999 this.new_conversation(cx);
10001000 }))
@@ -1004,12 +1004,12 @@ impl AssistantPanel { · 3 unmodified lines
10041004
10051005 fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
10061006 let zoomed = self.zoomed;
1007− IconButton::new("zoom_button", Icon::Maximize)
1007+ IconButton::new("zoom_button", IconName::Maximize)
10081008 .on_click(cx.listener(|this, _event, cx| {
10091009 this.toggle_zoom(&ToggleZoom, cx);
10101010 }))
10111011 .selected(zoomed)
1012− .selected_icon(Icon::Minimize)
1012+ .selected_icon(IconName::Minimize)
10131013 .icon_size(IconSize::Small)
10141014 .tooltip(move |cx| {
10151015 Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
@@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { · 270 unmodified lines
12861286 }
12871287 }
12881288
1289− fn icon(&self, cx: &WindowContext) -> Option<Icon> {
1290− Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button)
1289+ fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1290+ Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button)
12911291 }
12921292
12931293 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2349,7 +2349,7 @@ impl ConversationEditor { · 1055 unmodified lines
23492349 div()
23502350 .id("error")
23512351 .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2352− .child(IconElement::new(Icon::XCircle)),
2352+ .child(Icon::new(IconName::XCircle)),
23532353 )
23542354 } else {
23552355 None
@@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { · 289 unmodified lines
26452645 .justify_center()
26462646 .w(measurements.gutter_width)
26472647 .child(
2648− IconButton::new("include_conversation", Icon::Ai)
2648+ IconButton::new("include_conversation", IconName::Ai)
26492649 .on_click(cx.listener(|this, _, cx| {
26502650 this.toggle_include_conversation(&ToggleIncludeConversation, cx)
26512651 }))
@@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { · 8 unmodified lines
26602660 )
26612661 .children(if SemanticIndex::enabled(cx) {
26622662 Some(
2663− IconButton::new("retrieve_context", Icon::MagnifyingGlass)
2663+ IconButton::new("retrieve_context", IconName::MagnifyingGlass)
26642664 .on_click(cx.listener(|this, _, cx| {
26652665 this.toggle_retrieve_context(&ToggleRetrieveContext, cx)
26662666 }))
@@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { · 15 unmodified lines
26822682 div()
26832683 .id("error")
26842684 .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
2685− .child(IconElement::new(Icon::XCircle).color(Color::Error)),
2685+ .child(Icon::new(IconName::XCircle).color(Color::Error)),
26862686 )
26872687 } else {
26882688 None
@@ -2957,7 +2957,7 @@ impl InlineAssistant { · 268 unmodified lines
29572957 div()
29582958 .id("error")
29592959 .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))
2960− .child(IconElement::new(Icon::XCircle))
2960+ .child(Icon::new(IconName::XCircle))
29612961 .into_any_element()
29622962 ),
29632963
@@ -2965,7 +2965,7 @@ impl InlineAssistant { · 1 unmodified line
29652965 div()
29662966 .id("error")
29672967 .tooltip(|cx| Tooltip::text("Not Indexed", cx))
2968− .child(IconElement::new(Icon::XCircle))
2968+ .child(Icon::new(IconName::XCircle))
29692969 .into_any_element()
29702970 ),
29712971
@@ -2996,7 +2996,7 @@ impl InlineAssistant { · 24 unmodified lines
29962996 div()
29972997 .id("update")
29982998 .tooltip(move |cx| Tooltip::text(status_text.clone(), cx))
2999− .child(IconElement::new(Icon::Update).color(Color::Info))
2999+ .child(Icon::new(IconName::Update).color(Color::Info))
30003000 .into_any_element()
30013001 )
30023002 }
@@ -3005,7 +3005,7 @@ impl InlineAssistant { · 2 unmodified lines
30053005 div()
30063006 .id("check")
30073007 .tooltip(|cx| Tooltip::text("Index up to date", cx))
3008− .child(IconElement::new(Icon::Check).color(Color::Success))
3008+ .child(Icon::new(IconName::Check).color(Color::Success))
30093009 .into_any_element()
30103010 ),
30113011 }
crates/auto_update/src/update_notification.rs+2−2
@@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines
44 };
55 use menu::Cancel;
66 use util::channel::ReleaseChannel;
7−use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
7+use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt};
88
99 pub struct UpdateNotification {
1010 version: SemanticVersion,
@@ -30,7 +30,7 @@ impl Render for UpdateNotification { · 19 unmodified lines
3030 .child(
3131 div()
3232 .id("cancel")
33− .child(IconElement::new(Icon::Close))
33+ .child(Icon::new(IconName::Close))
3434 .cursor_pointer()
3535 .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
3636 ),
crates/call/src/call_settings.rs+3−0
@@ -9,9 +9,12 @@ pub struct CallSettings { · 8 unmodified lines
99 pub mute_on_join: bool,
1010 }
1111
12+/// Configuration of voice calls in Zed.
1213 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
1314 pub struct CallSettingsContent {
1415 /// Whether the microphone should be muted when joining a channel or a call.
16+ ///
17+ /// Default: false
1518 pub mute_on_join: Option<bool>,
1619 }
1720
crates/client/src/user.rs+2−2
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; · 2 unmodified lines
33 use collections::{hash_map::Entry, HashMap, HashSet};
44 use feature_flags::FeatureFlagAppExt;
55 use futures::{channel::mpsc, Future, StreamExt};
6−use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
6+use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task};
77 use postage::{sink::Sink, watch};
88 use rpc::proto::{RequestMessage, UsersResponse};
99 use std::sync::{Arc, Weak};
@@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); · 9 unmodified lines
1919 pub struct User {
2020 pub id: UserId,
2121 pub github_login: String,
22− pub avatar_uri: SharedString,
22+ pub avatar_uri: SharedUrl,
2323 }
2424
2525 #[derive(Clone, Debug, PartialEq, Eq)]
crates/collab_ui/Cargo.toml+3−0
@@ -9,6 +9,8 @@ path = "src/collab_ui.rs" · 8 unmodified lines
99 doctest = false
1010
1111 [features]
12+default = []
13+stories = ["dep:story"]
1214 test-support = [
1315 "call/test-support",
1416 "client/test-support",
@@ -44,6 +46,7 @@ project = { path = "../project" } · 29 unmodified lines
4446 recent_projects = { path = "../recent_projects" }
4547 rpc = { path = "../rpc" }
4648 settings = { path = "../settings" }
49+story = { path = "../story", optional = true }
4750 feature_flags = { path = "../feature_flags"}
4851 theme = { path = "../theme" }
4952 theme_selector = { path = "../theme_selector" }
crates/collab_ui/src/chat_panel.rs+7−7
@@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; · 19 unmodified lines
2020 use settings::{Settings, SettingsStore};
2121 use std::sync::Arc;
2222 use time::{OffsetDateTime, UtcOffset};
23−use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};
23+use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip};
2424 use util::{ResultExt, TryFutureExt};
2525 use workspace::{
2626 dock::{DockPosition, Panel, PanelEvent},
@@ -281,12 +281,12 @@ impl ChatPanel { · 254 unmodified lines
281281 )),
282282 )
283283 .end_child(
284− IconButton::new("notes", Icon::File)
284+ IconButton::new("notes", IconName::File)
285285 .on_click(cx.listener(Self::open_notes))
286286 .tooltip(|cx| Tooltip::text("Open notes", cx)),
287287 )
288288 .end_child(
289− IconButton::new("call", Icon::AudioOn)
289+ IconButton::new("call", IconName::AudioOn)
290290 .on_click(cx.listener(Self::join_call))
291291 .tooltip(|cx| Tooltip::text("Join call", cx)),
292292 ),
@@ -395,7 +395,7 @@ impl ChatPanel { · 102 unmodified lines
395395 .w_8()
396396 .visible_on_hover("")
397397 .children(message_id_to_remove.map(|message_id| {
398− IconButton::new(("remove", message_id), Icon::XCircle).on_click(
398+ IconButton::new(("remove", message_id), IconName::XCircle).on_click(
399399 cx.listener(move |this, _, cx| {
400400 this.remove_message(message_id, cx);
401401 }),
@@ -429,7 +429,7 @@ impl ChatPanel { · 27 unmodified lines
429429 Button::new("sign-in", "Sign in")
430430 .style(ButtonStyle::Filled)
431431 .icon_color(Color::Muted)
432− .icon(Icon::Github)
432+ .icon(IconName::Github)
433433 .icon_position(IconPosition::Start)
434434 .full_width()
435435 .on_click(cx.listener(move |this, _, cx| {
@@ -622,12 +622,12 @@ impl Panel for ChatPanel { · 186 unmodified lines
622622 "ChatPanel"
623623 }
624624
625− fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
625+ fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
626626 if !is_channels_feature_enabled(cx) {
627627 return None;
628628 }
629629
630− Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
630+ Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
631631 }
632632
633633 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
crates/collab_ui/src/collab_panel.rs+33−29
@@ -31,7 +31,7 @@ use smallvec::SmallVec; · 30 unmodified lines
3131 use std::{mem, sync::Arc};
3232 use theme::{ActiveTheme, ThemeSettings};
3333 use ui::{
34− prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label,
34+ prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label,
3535 ListHeader, ListItem, Tooltip,
3636 };
3737 use util::{maybe, ResultExt, TryFutureExt};
@@ -844,7 +844,7 @@ impl CollabPanel { · 806 unmodified lines
844844 .end_slot(if is_pending {
845845 Label::new("Calling").color(Color::Muted).into_any_element()
846846 } else if is_current_user {
847− IconButton::new("leave-call", Icon::Exit)
847+ IconButton::new("leave-call", IconName::Exit)
848848 .style(ButtonStyle::Subtle)
849849 .on_click(move |_, cx| Self::leave_call(cx))
850850 .tooltip(|cx| Tooltip::text("Leave Call", cx))
@@ -903,7 +903,7 @@ impl CollabPanel { · 52 unmodified lines
903903 h_stack()
904904 .gap_1()
905905 .child(render_tree_branch(is_last, false, cx))
906− .child(IconButton::new(0, Icon::Folder)),
906+ .child(IconButton::new(0, IconName::Folder)),
907907 )
908908 .child(Label::new(project_name.clone()))
909909 .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
@@ -924,7 +924,7 @@ impl CollabPanel { · 14 unmodified lines
924924 h_stack()
925925 .gap_1()
926926 .child(render_tree_branch(is_last, false, cx))
927− .child(IconButton::new(0, Icon::Screen)),
927+ .child(IconButton::new(0, IconName::Screen)),
928928 )
929929 .child(Label::new("Screen"))
930930 .when_some(peer_id, |this, _| {
@@ -965,7 +965,7 @@ impl CollabPanel { · 34 unmodified lines
965965 h_stack()
966966 .gap_1()
967967 .child(render_tree_branch(false, true, cx))
968− .child(IconButton::new(0, Icon::File)),
968+ .child(IconButton::new(0, IconName::File)),
969969 )
970970 .child(div().h_7().w_full().child(Label::new("notes")))
971971 .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
@@ -986,7 +986,7 @@ impl CollabPanel { · 14 unmodified lines
986986 h_stack()
987987 .gap_1()
988988 .child(render_tree_branch(false, false, cx))
989− .child(IconButton::new(0, Icon::MessageBubbles)),
989+ .child(IconButton::new(0, IconName::MessageBubbles)),
990990 )
991991 .child(Label::new("chat"))
992992 .tooltip(move |cx| Tooltip::text("Open Chat", cx))
@@ -1757,7 +1757,7 @@ impl CollabPanel { · 764 unmodified lines
17571757 .child(
17581758 Button::new("sign_in", "Sign in")
17591759 .icon_color(Color::Muted)
1760− .icon(Icon::Github)
1760+ .icon(IconName::Github)
17611761 .icon_position(IconPosition::Start)
17621762 .style(ButtonStyle::Filled)
17631763 .full_width()
@@ -1949,7 +1949,7 @@ impl CollabPanel { · 185 unmodified lines
19491949 let button = match section {
19501950 Section::ActiveCall => channel_link.map(|channel_link| {
19511951 let channel_link_copy = channel_link.clone();
1952− IconButton::new("channel-link", Icon::Copy)
1952+ IconButton::new("channel-link", IconName::Copy)
19531953 .icon_size(IconSize::Small)
19541954 .size(ButtonSize::None)
19551955 .visible_on_hover("section-header")
@@ -1961,13 +1961,13 @@ impl CollabPanel { · 5 unmodified lines
19611961 .into_any_element()
19621962 }),
19631963 Section::Contacts => Some(
1964− IconButton::new("add-contact", Icon::Plus)
1964+ IconButton::new("add-contact", IconName::Plus)
19651965 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
19661966 .tooltip(|cx| Tooltip::text("Search for new contact", cx))
19671967 .into_any_element(),
19681968 ),
19691969 Section::Channels => Some(
1970− IconButton::new("add-channel", Icon::Plus)
1970+ IconButton::new("add-channel", IconName::Plus)
19711971 .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
19721972 .tooltip(|cx| Tooltip::text("Create a channel", cx))
19731973 .into_any_element(),
@@ -2038,7 +2038,7 @@ impl CollabPanel { · 64 unmodified lines
20382038 })
20392039 .when(!calling, |el| {
20402040 el.child(
2041− IconButton::new("remove_contact", Icon::Close)
2041+ IconButton::new("remove_contact", IconName::Close)
20422042 .icon_color(Color::Muted)
20432043 .visible_on_hover("")
20442044 .tooltip(|cx| Tooltip::text("Remove Contact", cx))
@@ -2099,13 +2099,13 @@ impl CollabPanel { · 54 unmodified lines
20992099
21002100 let controls = if is_incoming {
21012101 vec![
2102− IconButton::new("decline-contact", Icon::Close)
2102+ IconButton::new("decline-contact", IconName::Close)
21032103 .on_click(cx.listener(move |this, _, cx| {
21042104 this.respond_to_contact_request(user_id, false, cx);
21052105 }))
21062106 .icon_color(color)
21072107 .tooltip(|cx| Tooltip::text("Decline invite", cx)),
2108− IconButton::new("accept-contact", Icon::Check)
2108+ IconButton::new("accept-contact", IconName::Check)
21092109 .on_click(cx.listener(move |this, _, cx| {
21102110 this.respond_to_contact_request(user_id, true, cx);
21112111 }))
@@ -2114,7 +2114,7 @@ impl CollabPanel { · 2 unmodified lines
21142114 ]
21152115 } else {
21162116 let github_login = github_login.clone();
2117− vec![IconButton::new("remove_contact", Icon::Close)
2117+ vec![IconButton::new("remove_contact", IconName::Close)
21182118 .on_click(cx.listener(move |this, _, cx| {
21192119 this.remove_contact(user_id, &github_login, cx);
21202120 }))
@@ -2154,13 +2154,13 @@ impl CollabPanel { · 33 unmodified lines
21542154 };
21552155
21562156 let controls = [
2157− IconButton::new("reject-invite", Icon::Close)
2157+ IconButton::new("reject-invite", IconName::Close)
21582158 .on_click(cx.listener(move |this, _, cx| {
21592159 this.respond_to_channel_invite(channel_id, false, cx);
21602160 }))
21612161 .icon_color(color)
21622162 .tooltip(|cx| Tooltip::text("Decline invite", cx)),
2163− IconButton::new("accept-invite", Icon::Check)
2163+ IconButton::new("accept-invite", IconName::Check)
21642164 .on_click(cx.listener(move |this, _, cx| {
21652165 this.respond_to_channel_invite(channel_id, true, cx);
21662166 }))
@@ -2178,7 +2178,7 @@ impl CollabPanel { · 11 unmodified lines
21782178 .child(h_stack().children(controls)),
21792179 )
21802180 .start_slot(
2181− IconElement::new(Icon::Hash)
2181+ Icon::new(IconName::Hash)
21822182 .size(IconSize::Small)
21832183 .color(Color::Muted),
21842184 )
@@ -2190,7 +2190,7 @@ impl CollabPanel { · 5 unmodified lines
21902190 cx: &mut ViewContext<Self>,
21912191 ) -> ListItem {
21922192 ListItem::new("contact-placeholder")
2193− .child(IconElement::new(Icon::Plus))
2193+ .child(Icon::new(IconName::Plus))
21942194 .child(Label::new("Add a Contact"))
21952195 .selected(is_selected)
21962196 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
@@ -2274,7 +2274,7 @@ impl CollabPanel { · 77 unmodified lines
22742274 };
22752275
22762276 let messages_button = |cx: &mut ViewContext<Self>| {
2277− IconButton::new("channel_chat", Icon::MessageBubbles)
2277+ IconButton::new("channel_chat", IconName::MessageBubbles)
22782278 .icon_size(IconSize::Small)
22792279 .icon_color(if has_messages_notification {
22802280 Color::Default
@@ -2286,7 +2286,7 @@ impl CollabPanel { · 5 unmodified lines
22862286 };
22872287
22882288 let notes_button = |cx: &mut ViewContext<Self>| {
2289− IconButton::new("channel_notes", Icon::File)
2289+ IconButton::new("channel_notes", IconName::File)
22902290 .icon_size(IconSize::Small)
22912291 .icon_color(if has_notes_notification {
22922292 Color::Default
@@ -2343,9 +2343,13 @@ impl CollabPanel { · 50 unmodified lines
23432343 },
23442344 ))
23452345 .start_slot(
2346− IconElement::new(if is_public { Icon::Public } else { Icon::Hash })
2347− .size(IconSize::Small)
2348− .color(Color::Muted),
2346+ Icon::new(if is_public {
2347+ IconName::Public
2348+ } else {
2349+ IconName::Hash
2350+ })
2351+ .size(IconSize::Small)
2352+ .color(Color::Muted),
23492353 )
23502354 .child(
23512355 h_stack()
@@ -2414,7 +2418,7 @@ impl CollabPanel { · 62 unmodified lines
24142418 .indent_level(depth + 1)
24152419 .indent_step_size(px(20.))
24162420 .start_slot(
2417− IconElement::new(Icon::Hash)
2421+ Icon::new(IconName::Hash)
24182422 .size(IconSize::Small)
24192423 .color(Color::Muted),
24202424 );
@@ -2528,10 +2532,10 @@ impl Panel for CollabPanel { · 107 unmodified lines
25282532 cx.notify();
25292533 }
25302534
2531− fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::Icon> {
2535+ fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
25322536 CollaborationPanelSettings::get_global(cx)
25332537 .button
2534− .then(|| ui::Icon::Collab)
2538+ .then(|| ui::IconName::Collab)
25352539 }
25362540
25372541 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2669,11 +2673,11 @@ impl Render for DraggedChannelView { · 131 unmodified lines
26692673 .p_1()
26702674 .gap_1()
26712675 .child(
2672− IconElement::new(
2676+ Icon::new(
26732677 if self.channel.visibility == proto::ChannelVisibility::Public {
2674− Icon::Public
2678+ IconName::Public
26752679 } else {
2676− Icon::Hash
2680+ IconName::Hash
26772681 },
26782682 )
26792683 .size(IconSize::Small)
crates/collab_ui/src/collab_panel/channel_modal.rs+2−2
@@ -168,7 +168,7 @@ impl Render for ChannelModal { · 167 unmodified lines
168168 .w_px()
169169 .flex_1()
170170 .gap_1()
171− .child(IconElement::new(Icon::Hash).size(IconSize::Medium))
171+ .child(Icon::new(IconName::Hash).size(IconSize::Medium))
172172 .child(Label::new(channel_name)),
173173 )
174174 .child(
@@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { · 231 unmodified lines
406406 Some(ChannelRole::Guest) => Some(Label::new("Guest")),
407407 _ => None,
408408 })
409− .child(IconButton::new("ellipsis", Icon::Ellipsis))
409+ .child(IconButton::new("ellipsis", IconName::Ellipsis))
410410 .children(
411411 if let (Some((menu, _)), true) = (&self.context_menu, selected) {
412412 Some(
crates/collab_ui/src/collab_panel/contact_finder.rs+1−3
@@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { · 154 unmodified lines
155155 .selected(selected)
156156 .start_slot(Avatar::new(user.avatar_uri.clone()))
157157 .child(Label::new(user.github_login.clone()))
158− .end_slot::<IconElement>(
159− icon_path.map(|icon_path| IconElement::from_path(icon_path)),
160− ),
158+ .end_slot::<Icon>(icon_path.map(|icon_path| Icon::from_path(icon_path))),
161159 )
162160 }
163161 }
crates/collab_ui/src/collab_titlebar_item.rs+10−10
@@ -15,7 +15,7 @@ use std::sync::Arc; · 14 unmodified lines
1515 use theme::{ActiveTheme, PlayerColors};
1616 use ui::{
1717 h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
18− IconButton, IconElement, TintColor, Tooltip,
18+ IconButton, IconName, TintColor, Tooltip,
1919 };
2020 use util::ResultExt;
2121 use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { · 191 unmodified lines
213213 .child(
214214 div()
215215 .child(
216− IconButton::new("leave-call", ui::Icon::Exit)
216+ IconButton::new("leave-call", ui::IconName::Exit)
217217 .style(ButtonStyle::Subtle)
218218 .tooltip(|cx| Tooltip::text("Leave call", cx))
219219 .icon_size(IconSize::Small)
@@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { · 10 unmodified lines
230230 IconButton::new(
231231 "mute-microphone",
232232 if is_muted {
233− ui::Icon::MicMute
233+ ui::IconName::MicMute
234234 } else {
235− ui::Icon::Mic
235+ ui::IconName::Mic
236236 },
237237 )
238238 .tooltip(move |cx| {
@@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { · 17 unmodified lines
256256 IconButton::new(
257257 "mute-sound",
258258 if is_deafened {
259− ui::Icon::AudioOff
259+ ui::IconName::AudioOff
260260 } else {
261− ui::Icon::AudioOn
261+ ui::IconName::AudioOn
262262 },
263263 )
264264 .style(ButtonStyle::Subtle)
@@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { · 16 unmodified lines
281281 )
282282 .when(!read_only, |this| {
283283 this.child(
284− IconButton::new("screen-share", ui::Icon::Screen)
284+ IconButton::new("screen-share", ui::IconName::Screen)
285285 .style(ButtonStyle::Subtle)
286286 .icon_size(IconSize::Small)
287287 .selected(is_screen_sharing)
@@ -573,7 +573,7 @@ impl CollabTitlebarItem { · 285 unmodified lines
573573 | client::Status::ReconnectionError { .. } => Some(
574574 div()
575575 .id("disconnected")
576− .child(IconElement::new(Icon::Disconnected).size(IconSize::Small))
576+ .child(Icon::new(IconName::Disconnected).size(IconSize::Small))
577577 .tooltip(|cx| Tooltip::text("Disconnected", cx))
578578 .into_any_element(),
579579 ),
@@ -643,7 +643,7 @@ impl CollabTitlebarItem { · 63 unmodified lines
643643 h_stack()
644644 .gap_0p5()
645645 .child(Avatar::new(user.avatar_uri.clone()))
646− .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
646+ .child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
647647 )
648648 .style(ButtonStyle::Subtle)
649649 .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
@@ -665,7 +665,7 @@ impl CollabTitlebarItem { · 15 unmodified lines
665665 .child(
666666 h_stack()
667667 .gap_0p5()
668− .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
668+ .child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
669669 )
670670 .style(ButtonStyle::Subtle)
671671 .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
crates/collab_ui/src/notification_panel.rs+6−6
@@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; · 18 unmodified lines
1919 use settings::{Settings, SettingsStore};
2020 use std::{sync::Arc, time::Duration};
2121 use time::{OffsetDateTime, UtcOffset};
22−use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label};
22+use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label};
2323 use util::{ResultExt, TryFutureExt};
2424 use workspace::{
2525 dock::{DockPosition, Panel, PanelEvent},
@@ -553,7 +553,7 @@ impl Render for NotificationPanel { · 527 unmodified lines
553553 .border_b_1()
554554 .border_color(cx.theme().colors().border)
555555 .child(Label::new("Notifications"))
556− .child(IconElement::new(Icon::Envelope)),
556+ .child(Icon::new(IconName::Envelope)),
557557 )
558558 .map(|this| {
559559 if self.client.user_id().is_none() {
@@ -564,7 +564,7 @@ impl Render for NotificationPanel { · 4 unmodified lines
564564 .child(
565565 Button::new("sign_in_prompt_button", "Sign in")
566566 .icon_color(Color::Muted)
567− .icon(Icon::Github)
567+ .icon(IconName::Github)
568568 .icon_position(IconPosition::Start)
569569 .style(ButtonStyle::Filled)
570570 .full_width()
@@ -655,10 +655,10 @@ impl Panel for NotificationPanel { · 84 unmodified lines
655655 }
656656 }
657657
658− fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {
658+ fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
659659 (NotificationPanelSettings::get_global(cx).button
660660 && self.notification_store.read(cx).notification_count() > 0)
661− .then(|| Icon::Bell)
661+ .then(|| IconName::Bell)
662662 }
663663
664664 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -716,7 +716,7 @@ impl Render for NotificationToast { · 51 unmodified lines
716716 .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
717717 .child(Label::new(self.text.clone()))
718718 .child(
719− IconButton::new("close", Icon::Close)
719+ IconButton::new("close", IconName::Close)
720720 .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
721721 )
722722 .on_click(cx.listener(|this, _, cx| {
crates/collab_ui/src/notifications.rs+9−2
@@ -1,9 +1,16 @@
1+mod collab_notification;
2+pub mod incoming_call_notification;
3+pub mod project_shared_notification;
4+
5+#[cfg(feature = "stories")]
6+mod stories;
7+
18 use gpui::AppContext;
29 use std::sync::Arc;
310 use workspace::AppState;
411
5−pub mod incoming_call_notification;
6−pub mod project_shared_notification;
12+#[cfg(feature = "stories")]
13+pub use stories::*;
714
815 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
916 incoming_call_notification::init(app_state, cx);
crates/collab_ui/src/notifications/collab_notification.rs+52−0
@@ -0,0 +1,52 @@
1+use gpui::{img, prelude::*, AnyElement, SharedUrl};
2+use smallvec::SmallVec;
3+use ui::prelude::*;
4+
5+#[derive(IntoElement)]
6+pub struct CollabNotification {
7+ avatar_uri: SharedUrl,
8+ accept_button: Button,
9+ dismiss_button: Button,
10+ children: SmallVec<[AnyElement; 2]>,
11+}
12+
13+impl CollabNotification {
14+ pub fn new(
15+ avatar_uri: impl Into<SharedUrl>,
16+ accept_button: Button,
17+ dismiss_button: Button,
18+ ) -> Self {
19+ Self {
20+ avatar_uri: avatar_uri.into(),
21+ accept_button,
22+ dismiss_button,
23+ children: SmallVec::new(),
24+ }
25+ }
26+}
27+
28+impl ParentElement for CollabNotification {
29+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
30+ &mut self.children
31+ }
32+}
33+
34+impl RenderOnce for CollabNotification {
35+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
36+ h_stack()
37+ .text_ui()
38+ .justify_between()
39+ .size_full()
40+ .overflow_hidden()
41+ .elevation_3(cx)
42+ .p_2()
43+ .gap_2()
44+ .child(img(self.avatar_uri).w_12().h_12().rounded_full())
45+ .child(v_stack().overflow_hidden().children(self.children))
46+ .child(
47+ v_stack()
48+ .child(self.accept_button)
49+ .child(self.dismiss_button),
50+ )
51+ }
52+}
crates/collab_ui/src/notifications/incoming_call_notification.rs+18−34
@@ -1,15 +1,12 @@
11 use crate::notification_window_options;
2+use crate::notifications::collab_notification::CollabNotification;
23 use call::{ActiveCall, IncomingCall};
34 use futures::StreamExt;
4−use gpui::{
5− img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
6− VisualContext as _, WindowHandle,
7−};
5+use gpui::{prelude::*, AppContext, WindowHandle};
86 use settings::Settings;
97 use std::sync::{Arc, Weak};
108 use theme::ThemeSettings;
11−use ui::prelude::*;
12−use ui::{h_stack, v_stack, Button, Label};
9+use ui::{prelude::*, Button, Label};
1310 use util::ResultExt;
1411 use workspace::AppState;
1512
@@ -31,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 15 unmodified lines
3128 if let Some(incoming_call) = incoming_call {
3229 let unique_screens = cx.update(|cx| cx.displays()).unwrap();
3330 let window_size = gpui::Size {
34− width: px(380.),
35− height: px(64.),
31+ width: px(400.),
32+ height: px(72.),
3633 };
3734
3835 for screen in unique_screens {
@@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { · 90 unmodified lines
129126
130127 cx.set_rem_size(ui_font_size);
131128
132− h_stack()
133− .font(ui_font)
134− .text_ui()
135− .justify_between()
136− .size_full()
137− .overflow_hidden()
138− .elevation_3(cx)
139− .p_2()
140− .gap_2()
141− .child(
142− img(self.state.call.calling_user.avatar_uri.clone())
143− .w_12()
144− .h_12()
145− .rounded_full(),
129+ div().size_full().font(ui_font).child(
130+ CollabNotification::new(
131+ self.state.call.calling_user.avatar_uri.clone(),
132+ Button::new("accept", "Accept").on_click({
133+ let state = self.state.clone();
134+ move |_, cx| state.respond(true, cx)
135+ }),
136+ Button::new("decline", "Decline").on_click({
137+ let state = self.state.clone();
138+ move |_, cx| state.respond(false, cx)
139+ }),
146140 )
147141 .child(v_stack().overflow_hidden().child(Label::new(format!(
148142 "{} is sharing a project in Zed",
149143 self.state.call.calling_user.github_login
150− ))))
151− .child(
152− v_stack()
153− .child(Button::new("accept", "Accept").render(cx).on_click({
154− let state = self.state.clone();
155− move |_, cx| state.respond(true, cx)
156− }))
157− .child(Button::new("decline", "Decline").render(cx).on_click({
158− let state = self.state.clone();
159− move |_, cx| state.respond(false, cx)
160− })),
161− )
144+ )))),
145+ )
162146 }
163147 }
crates/collab_ui/src/notifications/project_shared_notification.rs+27−47
@@ -1,12 +1,13 @@
11 use crate::notification_window_options;
2+use crate::notifications::collab_notification::CollabNotification;
23 use call::{room, ActiveCall};
34 use client::User;
45 use collections::HashMap;
5−use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};
6+use gpui::{AppContext, Size};
67 use settings::Settings;
78 use std::sync::{Arc, Weak};
89 use theme::ThemeSettings;
9−use ui::{h_stack, prelude::*, v_stack, Button, Label};
10+use ui::{prelude::*, Button, Label};
1011 use workspace::AppState;
1112
1213 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification { · 117 unmodified lines
130131
131132 cx.set_rem_size(ui_font_size);
132133
133− h_stack()
134− .font(ui_font)
135− .text_ui()
136− .justify_between()
137− .size_full()
138− .overflow_hidden()
139− .elevation_3(cx)
140− .p_2()
141− .gap_2()
142− .child(
143− img(self.owner.avatar_uri.clone())
144− .w_12()
145− .h_12()
146− .rounded_full(),
147− )
148− .child(
149− v_stack()
150− .overflow_hidden()
151− .child(Label::new(self.owner.github_login.clone()))
152− .child(Label::new(format!(
153− "is sharing a project in Zed{}",
154− if self.worktree_root_names.is_empty() {
155− ""
156− } else {
157− ":"
158− }
159− )))
160− .children(if self.worktree_root_names.is_empty() {
161− None
162− } else {
163− Some(Label::new(self.worktree_root_names.join(", ")))
164− }),
165− )
166− .child(
167− v_stack()
168− .child(Button::new("open", "Open").on_click(cx.listener(
169− move |this, _event, cx| {
170− this.join(cx);
171− },
172− )))
173− .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
174− move |this, _event, cx| {
175− this.dismiss(cx);
176− },
177− ))),
134+ div().size_full().font(ui_font).child(
135+ CollabNotification::new(
136+ self.owner.avatar_uri.clone(),
137+ Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
138+ this.join(cx);
139+ })),
140+ Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
141+ this.dismiss(cx);
142+ })),
178143 )
144+ .child(Label::new(self.owner.github_login.clone()))
145+ .child(Label::new(format!(
146+ "is sharing a project in Zed{}",
147+ if self.worktree_root_names.is_empty() {
148+ ""
149+ } else {
150+ ":"
151+ }
152+ )))
153+ .children(if self.worktree_root_names.is_empty() {
154+ None
155+ } else {
156+ Some(Label::new(self.worktree_root_names.join(", ")))
157+ }),
158+ )
179159 }
180160 }
crates/collab_ui/src/notifications/stories.rs+3−0
@@ -0,0 +1,3 @@
1+mod collab_notification;
2+
3+pub use collab_notification::*;
crates/collab_ui/src/notifications/stories/collab_notification.rs+50−0
@@ -0,0 +1,50 @@
1+use gpui::prelude::*;
2+use story::{StoryContainer, StoryItem, StorySection};
3+use ui::prelude::*;
4+
5+use crate::notifications::collab_notification::CollabNotification;
6+
7+pub struct CollabNotificationStory;
8+
9+impl Render for CollabNotificationStory {
10+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
11+ let window_container = |width, height| div().w(px(width)).h(px(height));
12+
13+ StoryContainer::new(
14+ "CollabNotification Story",
15+ "crates/collab_ui/src/notifications/stories/collab_notification.rs",
16+ )
17+ .child(
18+ StorySection::new().child(StoryItem::new(
19+ "Incoming Call Notification",
20+ window_container(400., 72.).child(
21+ CollabNotification::new(
22+ "https://avatars.githubusercontent.com/u/1486634?v=4",
23+ Button::new("accept", "Accept"),
24+ Button::new("decline", "Decline"),
25+ )
26+ .child(
27+ v_stack()
28+ .overflow_hidden()
29+ .child(Label::new("maxdeviant is sharing a project in Zed")),
30+ ),
31+ ),
32+ )),
33+ )
34+ .child(
35+ StorySection::new().child(StoryItem::new(
36+ "Project Shared Notification",
37+ window_container(400., 72.).child(
38+ CollabNotification::new(
39+ "https://avatars.githubusercontent.com/u/1714999?v=4",
40+ Button::new("open", "Open"),
41+ Button::new("dismiss", "Dismiss"),
42+ )
43+ .child(Label::new("iamnbutler"))
44+ .child(Label::new("is sharing a project in Zed:"))
45+ .child(Label::new("zed")),
46+ ),
47+ )),
48+ )
49+ }
50+}
crates/copilot_ui/src/copilot_button.rs+7−5
@@ -17,7 +17,9 @@ use util::{paths, ResultExt}; · 16 unmodified lines
1717 use workspace::{
1818 create_and_open_local_file,
1919 item::ItemHandle,
20− ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip},
20+ ui::{
21+ popover_menu, ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, Tooltip,
22+ },
2123 StatusItemView, Toast, Workspace,
2224 };
2325 use zed_actions::OpenBrowser;
@@ -51,15 +53,15 @@ impl Render for CopilotButton { · 27 unmodified lines
5153 .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
5254
5355 let icon = match status {
54− Status::Error(_) => Icon::CopilotError,
56+ Status::Error(_) => IconName::CopilotError,
5557 Status::Authorized => {
5658 if enabled {
57− Icon::Copilot
59+ IconName::Copilot
5860 } else {
59− Icon::CopilotDisabled
61+ IconName::CopilotDisabled
6062 }
6163 }
62− _ => Icon::CopilotInit,
64+ _ => IconName::CopilotInit,
6365 };
6466
6567 if let Status::Error(e) = status {
crates/copilot_ui/src/sign_in.rs+2−2
@@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines
44 FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,
55 Subscription, ViewContext,
66 };
7−use ui::{prelude::*, Button, Icon, Label};
7+use ui::{prelude::*, Button, IconName, Label};
88 use workspace::ModalView;
99
1010 const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
@@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification { · 164 unmodified lines
175175 .w_32()
176176 .h_16()
177177 .flex_none()
178− .path(Icon::ZedXCopilot.path())
178+ .path(IconName::ZedXCopilot.path())
179179 .text_color(cx.theme().colors().icon),
180180 )
181181 .child(prompt)
crates/diagnostics/src/diagnostics.rs+5−7
@@ -36,7 +36,7 @@ use std::{ · 35 unmodified lines
3636 };
3737 use theme::ActiveTheme;
3838 pub use toolbar_controls::ToolbarControls;
39−use ui::{h_stack, prelude::*, Icon, IconElement, Label};
39+use ui::{h_stack, prelude::*, Icon, IconName, Label};
4040 use util::TryFutureExt;
4141 use workspace::{
4242 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { · 617 unmodified lines
660660 then.child(
661661 h_stack()
662662 .gap_1()
663− .child(IconElement::new(Icon::XCircle).color(Color::Error))
663+ .child(Icon::new(IconName::XCircle).color(Color::Error))
664664 .child(Label::new(self.summary.error_count.to_string()).color(
665665 if selected {
666666 Color::Default
@@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { · 7 unmodified lines
674674 then.child(
675675 h_stack()
676676 .gap_1()
677− .child(
678− IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
679− )
677+ .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
680678 .child(Label::new(self.summary.warning_count.to_string()).color(
681679 if selected {
682680 Color::Default
@@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { · 133 unmodified lines
816814 .flex_none()
817815 .map(|icon| {
818816 if diagnostic.severity == DiagnosticSeverity::ERROR {
819− icon.path(Icon::XCircle.path())
817+ icon.path(IconName::XCircle.path())
820818 .text_color(Color::Error.color(cx))
821819 } else {
822− icon.path(Icon::ExclamationTriangle.path())
820+ icon.path(IconName::ExclamationTriangle.path())
823821 .text_color(Color::Warning.color(cx))
824822 }
825823 }),
crates/diagnostics/src/items.rs+7−7
@@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines
66 };
77 use language::Diagnostic;
88 use lsp::LanguageServerId;
9−use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
9+use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
1010 use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
1111
1212 use crate::{Deploy, ProjectDiagnosticsEditor};
@@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { · 12 unmodified lines
2525 let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
2626 (0, 0) => h_stack().map(|this| {
2727 this.child(
28− IconElement::new(Icon::Check)
28+ Icon::new(IconName::Check)
2929 .size(IconSize::Small)
3030 .color(Color::Default),
3131 )
@@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
3333 (0, warning_count) => h_stack()
3434 .gap_1()
3535 .child(
36− IconElement::new(Icon::ExclamationTriangle)
36+ Icon::new(IconName::ExclamationTriangle)
3737 .size(IconSize::Small)
3838 .color(Color::Warning),
3939 )
@@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
4141 (error_count, 0) => h_stack()
4242 .gap_1()
4343 .child(
44− IconElement::new(Icon::XCircle)
44+ Icon::new(IconName::XCircle)
4545 .size(IconSize::Small)
4646 .color(Color::Error),
4747 )
@@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
4949 (error_count, warning_count) => h_stack()
5050 .gap_1()
5151 .child(
52− IconElement::new(Icon::XCircle)
52+ Icon::new(IconName::XCircle)
5353 .size(IconSize::Small)
5454 .color(Color::Error),
5555 )
5656 .child(Label::new(error_count.to_string()).size(LabelSize::Small))
5757 .child(
58− IconElement::new(Icon::ExclamationTriangle)
58+ Icon::new(IconName::ExclamationTriangle)
5959 .size(IconSize::Small)
6060 .color(Color::Warning),
6161 )
@@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { · 4 unmodified lines
6666 Some(
6767 h_stack()
6868 .gap_2()
69− .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small))
69+ .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small))
7070 .child(
7171 Label::new("Checking…")
7272 .size(LabelSize::Small)
crates/diagnostics/src/toolbar_controls.rs+2−2
@@ -1,7 +1,7 @@
11 use crate::ProjectDiagnosticsEditor;
22 use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
33 use ui::prelude::*;
4−use ui::{Icon, IconButton, Tooltip};
4+use ui::{IconButton, IconName, Tooltip};
55 use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
66
77 pub struct ToolbarControls {
@@ -24,7 +24,7 @@ impl Render for ToolbarControls { · 16 unmodified lines
2424 };
2525
2626 div().child(
27− IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
27+ IconButton::new("toggle-warnings", IconName::ExclamationTriangle)
2828 .tooltip(move |cx| Tooltip::text(tooltip, cx))
2929 .on_click(cx.listener(|this, _, cx| {
3030 if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
crates/editor/src/display_map.rs+0−1
@@ -1015,7 +1015,6 @@ pub mod tests { · 1014 unmodified lines
10151015 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
10161016 .unwrap_or(10);
10171017
1018− let _test_platform = &cx.test_platform;
10191018 let mut tab_size = rng.gen_range(1..=4);
10201019 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
10211020 let excerpt_header_height = rng.gen_range(1..=5);
crates/editor/src/editor.rs+25−7
@@ -99,8 +99,8 @@ use sum_tree::TreeMap; · 98 unmodified lines
9999 use text::{OffsetUtf16, Rope};
100100 use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
101101 use ui::{
102− h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover,
103− Tooltip,
102+ h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem,
103+ Popover, Tooltip,
104104 };
105105 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
106106 use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
@@ -507,7 +507,7 @@ pub enum SoftWrap { · 400 unmodified lines
507507 Column(u32),
508508 }
509509
510−#[derive(Clone, Default)]
510+#[derive(Clone)]
511511 pub struct EditorStyle {
512512 pub background: Hsla,
513513 pub local_player: PlayerColor,
@@ -519,6 +519,24 @@ pub struct EditorStyle { · 5 unmodified lines
519519 pub suggestions_style: HighlightStyle,
520520 }
521521
522+impl Default for EditorStyle {
523+ fn default() -> Self {
524+ Self {
525+ background: Hsla::default(),
526+ local_player: PlayerColor::default(),
527+ text: TextStyle::default(),
528+ scrollbar_width: Pixels::default(),
529+ syntax: Default::default(),
530+ // HACK: Status colors don't have a real default.
531+ // We should look into removing the status colors from the editor
532+ // style and retrieve them directly from the theme.
533+ status: StatusColors::dark(),
534+ inlays_style: HighlightStyle::default(),
535+ suggestions_style: HighlightStyle::default(),
536+ }
537+ }
538+}
539+
522540 type CompletionId = usize;
523541
524542 // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
@@ -4223,7 +4241,7 @@ impl Editor { · 3698 unmodified lines
42234241 ) -> Option<IconButton> {
42244242 if self.available_code_actions.is_some() {
42254243 Some(
4226− IconButton::new("code_actions_indicator", ui::Icon::Bolt)
4244+ IconButton::new("code_actions_indicator", ui::IconName::Bolt)
42274245 .icon_size(IconSize::Small)
42284246 .icon_color(Color::Muted)
42294247 .selected(is_active)
@@ -4257,7 +4275,7 @@ impl Editor { · 27 unmodified lines
42574275 fold_data
42584276 .map(|(fold_status, buffer_row, active)| {
42594277 (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
4260− IconButton::new(ix as usize, ui::Icon::ChevronDown)
4278+ IconButton::new(ix as usize, ui::IconName::ChevronDown)
42614279 .on_click(cx.listener(move |editor, _e, cx| match fold_status {
42624280 FoldStatus::Folded => {
42634281 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
@@ -4269,7 +4287,7 @@ impl Editor { · 5 unmodified lines
42694287 .icon_color(ui::Color::Muted)
42704288 .icon_size(ui::IconSize::Small)
42714289 .selected(fold_status == FoldStatus::Folded)
4272− .selected_icon(ui::Icon::ChevronRight)
4290+ .selected_icon(ui::IconName::ChevronRight)
42734291 .size(ui::ButtonSize::None)
42744292 })
42754293 })
@@ -9739,7 +9757,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren · 5463 unmodified lines
97399757 ),
97409758 )
97419759 .child(
9742− IconButton::new(("copy-block", cx.block_id), Icon::Copy)
9760+ IconButton::new(("copy-block", cx.block_id), IconName::Copy)
97439761 .icon_color(Color::Muted)
97449762 .size(ButtonSize::Compact)
97459763 .style(ButtonStyle::Transparent)
crates/editor/src/element.rs+55−46
@@ -28,7 +28,7 @@ use gpui::{ · 27 unmodified lines
2828 AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
2929 CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,
3030 InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
31− MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine,
31+ MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
3232 SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
3333 TextStyle, View, ViewContext, WindowContext,
3434 };
@@ -581,41 +581,6 @@ impl EditorElement { · 546 unmodified lines
581581 }
582582 }
583583
584− fn scroll(
585− editor: &mut Editor,
586− event: &ScrollWheelEvent,
587− position_map: &PositionMap,
588− bounds: &InteractiveBounds,
589− cx: &mut ViewContext<Editor>,
590− ) {
591− if !bounds.visibly_contains(&event.position, cx) {
592− return;
593− }
594−
595− let line_height = position_map.line_height;
596− let max_glyph_width = position_map.em_width;
597− let (delta, axis) = match event.delta {
598− gpui::ScrollDelta::Pixels(mut pixels) => {
599− //Trackpad
600− let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
601− (pixels, axis)
602− }
603−
604− gpui::ScrollDelta::Lines(lines) => {
605− //Not trackpad
606− let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
607− (pixels, None)
608− }
609− };
610−
611− let scroll_position = position_map.snapshot.scroll_position();
612− let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
613− let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
614− let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
615− editor.scroll(scroll_position, axis, cx);
616− cx.stop_propagation();
617− }
618−
619584 fn paint_background(
620585 &self,
621586 gutter_bounds: Bounds<Pixels>,
@@ -2450,34 +2415,78 @@ impl EditorElement { · 1828 unmodified lines
24502415 )
24512416 }
24522417
2453− fn paint_mouse_listeners(
2418+ fn paint_scroll_wheel_listener(
24542419 &mut self,
2455− bounds: Bounds<Pixels>,
2456− gutter_bounds: Bounds<Pixels>,
2457− text_bounds: Bounds<Pixels>,
2420+ interactive_bounds: &InteractiveBounds,
24582421 layout: &LayoutState,
24592422 cx: &mut WindowContext,
24602423 ) {
2461− let interactive_bounds = InteractiveBounds {
2462− bounds: bounds.intersect(&cx.content_mask().bounds),
2463− stacking_order: cx.stacking_order().clone(),
2464− };
2465−
24662424 cx.on_mouse_event({
24672425 let position_map = layout.position_map.clone();
24682426 let editor = self.editor.clone();
24692427 let interactive_bounds = interactive_bounds.clone();
2428+ let mut delta = ScrollDelta::default();
24702429
24712430 move |event: &ScrollWheelEvent, phase, cx| {
24722431 if phase == DispatchPhase::Bubble
24732432 && interactive_bounds.visibly_contains(&event.position, cx)
24742433 {
2434+ delta = delta.coalesce(event.delta);
24752435 editor.update(cx, |editor, cx| {
2476− Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
2436+ let position = event.position;
2437+ let position_map: &PositionMap = &position_map;
2438+ let bounds = &interactive_bounds;
2439+ if !bounds.visibly_contains(&position, cx) {
2440+ return;
2441+ }
2442+
2443+ let line_height = position_map.line_height;
2444+ let max_glyph_width = position_map.em_width;
2445+ let (delta, axis) = match delta {
2446+ gpui::ScrollDelta::Pixels(mut pixels) => {
2447+ //Trackpad
2448+ let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
2449+ (pixels, axis)
2450+ }
2451+
2452+ gpui::ScrollDelta::Lines(lines) => {
2453+ //Not trackpad
2454+ let pixels =
2455+ point(lines.x * max_glyph_width, lines.y * line_height);
2456+ (pixels, None)
2457+ }
2458+ };
2459+
2460+ let scroll_position = position_map.snapshot.scroll_position();
2461+ let x = f32::from(
2462+ (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width,
2463+ );
2464+ let y =
2465+ f32::from((scroll_position.y * line_height - delta.y) / line_height);
2466+ let scroll_position =
2467+ point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
2468+ editor.scroll(scroll_position, axis, cx);
2469+ cx.stop_propagation();
24772470 });
24782471 }
24792472 }
24802473 });
2474+ }
2475+
2476+ fn paint_mouse_listeners(
2477+ &mut self,
2478+ bounds: Bounds<Pixels>,
2479+ gutter_bounds: Bounds<Pixels>,
2480+ text_bounds: Bounds<Pixels>,
2481+ layout: &LayoutState,
2482+ cx: &mut WindowContext,
2483+ ) {
2484+ let interactive_bounds = InteractiveBounds {
2485+ bounds: bounds.intersect(&cx.content_mask().bounds),
2486+ stacking_order: cx.stacking_order().clone(),
2487+ };
2488+
2489+ self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx);
24812490
24822491 cx.on_mouse_event({
24832492 let position_map = layout.position_map.clone();
crates/editor/src/hover_popover.rs+13−11
@@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; · 15 unmodified lines
1616 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
1717 use settings::Settings;
1818 use std::{ops::Range, sync::Arc, time::Duration};
19−use ui::{StyledExt, Tooltip};
19+use ui::{prelude::*, Tooltip};
2020 use util::TryFutureExt;
2121 use workspace::Workspace;
2222
@@ -514,6 +514,8 @@ impl DiagnosticPopover { · 491 unmodified lines
514514 None => self.local_diagnostic.diagnostic.message.clone(),
515515 };
516516
517+ let status_colors = cx.theme().status();
518+
517519 struct DiagnosticColors {
518520 pub background: Hsla,
519521 pub border: Hsla,
@@ -521,24 +523,24 @@ impl DiagnosticPopover { · 1 unmodified line
521523
522524 let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
523525 DiagnosticSeverity::ERROR => DiagnosticColors {
524− background: style.status.error_background,
525− border: style.status.error_border,
526+ background: status_colors.error_background,
527+ border: status_colors.error_border,
526528 },
527529 DiagnosticSeverity::WARNING => DiagnosticColors {
528− background: style.status.warning_background,
529− border: style.status.warning_border,
530+ background: status_colors.warning_background,
531+ border: status_colors.warning_border,
530532 },
531533 DiagnosticSeverity::INFORMATION => DiagnosticColors {
532− background: style.status.info_background,
533− border: style.status.info_border,
534+ background: status_colors.info_background,
535+ border: status_colors.info_border,
534536 },
535537 DiagnosticSeverity::HINT => DiagnosticColors {
536− background: style.status.hint_background,
537− border: style.status.hint_border,
538+ background: status_colors.hint_background,
539+ border: status_colors.hint_border,
538540 },
539541 _ => DiagnosticColors {
540− background: style.status.ignored_background,
541− border: style.status.ignored_border,
542+ background: status_colors.ignored_background,
543+ border: status_colors.ignored_border,
542544 },
543545 };
544546
crates/editor/src/scroll.rs+6−4
@@ -384,10 +384,12 @@ impl Editor { · 383 unmodified lines
384384 ) {
385385 hide_hover(self, cx);
386386 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
387− let top_row = scroll_anchor
388− .anchor
389− .to_point(&self.buffer().read(cx).snapshot(cx))
390− .row;
387+ let snapshot = &self.buffer().read(cx).snapshot(cx);
388+ if !scroll_anchor.anchor.is_valid(snapshot) {
389+ log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
390+ return;
391+ }
392+ let top_row = scroll_anchor.anchor.to_point(snapshot).row;
391393 self.scroll_manager
392394 .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
393395 }
crates/editor/src/test.rs+2−4
@@ -60,8 +60,7 @@ pub fn assert_text_with_selections( · 59 unmodified lines
6060 #[allow(dead_code)]
6161 #[cfg(any(test, feature = "test-support"))]
6262 pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
63− // todo!()
64− Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
63+ Editor::new(EditorMode::Full, buffer, None, cx)
6564 }
6665
6766 pub(crate) fn build_editor_with_project(
@@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( · 1 unmodified line
6968 buffer: Model<MultiBuffer>,
7069 cx: &mut ViewContext<Editor>,
7170 ) -> Editor {
72− // todo!()
73− Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
71+ Editor::new(EditorMode::Full, buffer, Some(project), cx)
7472 }
crates/feedback/src/deploy_feedback_button.rs+2−2
@@ -1,5 +1,5 @@
11 use gpui::{Render, ViewContext, WeakView};
2−use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip};
2+use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip};
33 use workspace::{item::ItemHandle, StatusItemView, Workspace};
44
55 use crate::{feedback_modal::FeedbackModal, GiveFeedback};
@@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { · 21 unmodified lines
2727 })
2828 })
2929 .is_some();
30− IconButton::new("give-feedback", Icon::Envelope)
30+ IconButton::new("give-feedback", IconName::Envelope)
3131 .style(ui::ButtonStyle::Subtle)
3232 .icon_size(IconSize::Small)
3333 .selected(is_open)
crates/feedback/src/feedback_modal.rs+1−1
@@ -488,7 +488,7 @@ impl Render for FeedbackModal { · 487 unmodified lines
488488 .child(
489489 Button::new("community_repository", "Community Repository")
490490 .style(ButtonStyle::Transparent)
491− .icon(Icon::ExternalLink)
491+ .icon(IconName::ExternalLink)
492492 .icon_position(IconPosition::End)
493493 .icon_size(IconSize::Small)
494494 .on_click(open_community_repo),
crates/gpui/docs/key_dispatch.md+2−2
@@ -50,7 +50,7 @@ impl Render for Menu { · 49 unmodified lines
5050 .on_action(|this, move: &MoveDown, cx| {
5151 // ...
5252 })
53− .children(todo!())
53+ .children(unimplemented!())
5454 }
5555 }
5656 ```
@@ -68,7 +68,7 @@ impl Render for Menu { · 11 unmodified lines
6868 .on_action(|this, move: &MoveDown, cx| {
6969 // ...
7070 })
71− .children(todo!())
71+ .children(unimplemented!())
7272 }
7373 }
7474 ```
crates/gpui/src/action.rs+17−6
@@ -114,14 +114,26 @@ impl ActionRegistry { · 113 unmodified lines
114114 pub(crate) fn load_actions(&mut self) {
115115 for builder in __GPUI_ACTIONS {
116116 let action = builder();
117− //todo(remove)
118− let name: SharedString = action.name.into();
119− self.builders_by_name.insert(name.clone(), action.build);
120− self.names_by_type_id.insert(action.type_id, name.clone());
121− self.all_names.push(name);
117+ self.insert_action(action);
122118 }
123119 }
124120
121+ #[cfg(test)]
122+ pub(crate) fn load_action<A: Action>(&mut self) {
123+ self.insert_action(ActionData {
124+ name: A::debug_name(),
125+ type_id: TypeId::of::<A>(),
126+ build: A::build,
127+ });
128+ }
129+
130+ fn insert_action(&mut self, action: ActionData) {
131+ let name: SharedString = action.name.into();
132+ self.builders_by_name.insert(name.clone(), action.build);
133+ self.names_by_type_id.insert(action.type_id, name.clone());
134+ self.all_names.push(name);
135+ }
136+
125137 /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
126138 pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
127139 let name = self
@@ -203,7 +215,6 @@ macro_rules! __impl_action { · 75 unmodified lines
203215 )
204216 }
205217
206− // todo!() why is this needed in addition to name?
207218 fn debug_name() -> &'static str
208219 where
209220 Self: ::std::marker::Sized
crates/gpui/src/app.rs+28−13
@@ -45,11 +45,13 @@ use util::{ · 44 unmodified lines
4545
4646 /// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
4747 /// Strongly consider removing after stabilization.
48+#[doc(hidden)]
4849 pub struct AppCell {
4950 app: RefCell<AppContext>,
5051 }
5152
5253 impl AppCell {
54+ #[doc(hidden)]
5355 #[track_caller]
5456 pub fn borrow(&self) -> AppRef {
5557 if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -59,6 +61,7 @@ impl AppCell { · 3 unmodified lines
5961 AppRef(self.app.borrow())
6062 }
6163
64+ #[doc(hidden)]
6265 #[track_caller]
6366 pub fn borrow_mut(&self) -> AppRefMut {
6467 if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -69,6 +72,7 @@ impl AppCell { · 4 unmodified lines
6972 }
7073 }
7174
75+#[doc(hidden)]
7276 #[derive(Deref, DerefMut)]
7377 pub struct AppRef<'a>(Ref<'a, AppContext>);
7478
@@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> { · 6 unmodified lines
8185 }
8286 }
8387
88+#[doc(hidden)]
8489 #[derive(Deref, DerefMut)]
8590 pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
8691
@@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> { · 6 unmodified lines
9398 }
9499 }
95100
101+/// A reference to a GPUI application, typically constructed in the `main` function of your app.
102+/// You won't interact with this type much outside of initial configuration and startup.
96103 pub struct App(Rc<AppCell>);
97104
98105 /// Represents an application before it is fully launched. Once your app is
@@ -136,6 +143,8 @@ impl App { · 37 unmodified lines
136143 self
137144 }
138145
146+ /// Invokes a handler when an already-running application is launched.
147+ /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock.
139148 pub fn on_reopen<F>(&self, mut callback: F) -> &Self
140149 where
141150 F: 'static + FnMut(&mut AppContext),
@@ -149,18 +158,22 @@ impl App { · 7 unmodified lines
149158 self
150159 }
151160
161+ /// Returns metadata associated with the application
152162 pub fn metadata(&self) -> AppMetadata {
153163 self.0.borrow().app_metadata.clone()
154164 }
155165
166+ /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
156167 pub fn background_executor(&self) -> BackgroundExecutor {
157168 self.0.borrow().background_executor.clone()
158169 }
159170
171+ /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground.
160172 pub fn foreground_executor(&self) -> ForegroundExecutor {
161173 self.0.borrow().foreground_executor.clone()
162174 }
163175
176+ /// Returns a reference to the [`TextSystem`] associated with this app.
164177 pub fn text_system(&self) -> Arc<TextSystem> {
165178 self.0.borrow().text_system.clone()
166179 }
@@ -174,12 +187,6 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, () · 7 unmodified lines
174187 type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
175188 type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
176189
177−// struct FrameConsumer {
178−// next_frame_callbacks: Vec<FrameCallback>,
179−// task: Task<()>,
180−// display_linker
181−// }
182−
183190 pub struct AppContext {
184191 pub(crate) this: Weak<AppCell>,
185192 pub(crate) platform: Rc<dyn Platform>,
@@ -292,7 +299,7 @@ impl AppContext { · 106 unmodified lines
292299 app
293300 }
294301
295− /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
302+ /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
296303 /// will be given 100ms to complete before exiting.
297304 pub fn shutdown(&mut self) {
298305 let mut futures = Vec::new();
@@ -314,10 +321,12 @@ impl AppContext { · 15 unmodified lines
314321 }
315322 }
316323
324+ /// Gracefully quit the application via the platform's standard routine.
317325 pub fn quit(&mut self) {
318326 self.platform.quit();
319327 }
320328
329+ /// Get metadata about the app and platform.
321330 pub fn app_metadata(&self) -> AppMetadata {
322331 self.app_metadata.clone()
323332 }
@@ -340,6 +349,7 @@ impl AppContext { · 16 unmodified lines
340349 result
341350 }
342351
352+ /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context.
343353 pub fn observe<W, E>(
344354 &mut self,
345355 entity: &E,
@@ -355,7 +365,7 @@ impl AppContext { · 9 unmodified lines
355365 })
356366 }
357367
358− pub fn observe_internal<W, E>(
368+ pub(crate) fn observe_internal<W, E>(
359369 &mut self,
360370 entity: &E,
361371 mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
@@ -380,15 +390,17 @@ impl AppContext { · 18 unmodified lines
380390 subscription
381391 }
382392
383− pub fn subscribe<T, E, Evt>(
393+ /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type.
394+ /// The callback is provided a handle to the emitting entity and a reference to the emitted event.
395+ pub fn subscribe<T, E, Event>(
384396 &mut self,
385397 entity: &E,
386− mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
398+ mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static,
387399 ) -> Subscription
388400 where
389− T: 'static + EventEmitter<Evt>,
401+ T: 'static + EventEmitter<Event>,
390402 E: Entity<T>,
391− Evt: 'static,
403+ Event: 'static,
392404 {
393405 self.subscribe_internal(entity, move |entity, event, cx| {
394406 on_event(entity, event, cx);
@@ -426,6 +438,9 @@ impl AppContext { · 31 unmodified lines
426438 subscription
427439 }
428440
441+ /// Returns handles to all open windows in the application.
442+ /// Each handle could be downcast to a handle typed for the root view of that window.
443+ /// To find all windows of a given type, you could filter on
429444 pub fn windows(&self) -> Vec<AnyWindowHandle> {
430445 self.windows
431446 .values()
@@ -565,7 +580,7 @@ impl AppContext { · 133 unmodified lines
565580 self.pending_effects.push_back(effect);
566581 }
567582
568− /// Called at the end of AppContext::update to complete any side effects
583+ /// Called at the end of [`AppContext::update`] to complete any side effects
569584 /// such as notifying observers, emitting events, etc. Effects can themselves
570585 /// cause effects, so we continue looping until all effects are processed.
571586 fn flush_effects(&mut self) {
crates/gpui/src/app/async_context.rs+6−0
@@ -82,6 +82,7 @@ impl Context for AsyncAppContext { · 81 unmodified lines
8282 }
8383
8484 impl AsyncAppContext {
85+ /// Schedules all windows in the application to be redrawn.
8586 pub fn refresh(&mut self) -> Result<()> {
8687 let app = self
8788 .app
@@ -92,14 +93,17 @@ impl AsyncAppContext { · 4 unmodified lines
9293 Ok(())
9394 }
9495
96+ /// Get an executor which can be used to spawn futures in the background.
9597 pub fn background_executor(&self) -> &BackgroundExecutor {
9698 &self.background_executor
9799 }
98100
101+ /// Get an executor which can be used to spawn futures in the foreground.
99102 pub fn foreground_executor(&self) -> &ForegroundExecutor {
100103 &self.foreground_executor
101104 }
102105
106+ /// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
103107 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
104108 let app = self
105109 .app
@@ -109,6 +113,7 @@ impl AsyncAppContext { · 3 unmodified lines
109113 Ok(f(&mut lock))
110114 }
111115
116+ /// Open a window with the given options based on the root view returned by the given function.
112117 pub fn open_window<V>(
113118 &self,
114119 options: crate::WindowOptions,
@@ -125,6 +130,7 @@ impl AsyncAppContext { · 10 unmodified lines
125130 Ok(lock.open_window(options, build_root_view))
126131 }
127132
133+ /// Schedule a future to be polled in the background.
128134 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
129135 where
130136 Fut: Future<Output = R> + 'static,
crates/gpui/src/app/entity_map.rs+4−1
@@ -19,7 +19,10 @@ use std::{ · 18 unmodified lines
1919 #[cfg(any(test, feature = "test-support"))]
2020 use collections::HashMap;
2121
22−slotmap::new_key_type! { pub struct EntityId; }
22+slotmap::new_key_type! {
23+ /// A unique identifier for a model or view across the application.
24+ pub struct EntityId;
25+}
2326
2427 impl EntityId {
2528 pub fn as_u64(self) -> u64 {
crates/gpui/src/app/test_context.rs+76−6
@@ -1,3 +1,5 @@
1+#![deny(missing_docs)]
2+
13 use crate::{
24 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
35 BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
@@ -9,13 +11,19 @@ use anyhow::{anyhow, bail}; · 5 unmodified lines
911 use futures::{Stream, StreamExt};
1012 use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
1113
14+/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
15+/// an implementation of `Context` with additional methods that are useful in tests.
1216 #[derive(Clone)]
1317 pub struct TestAppContext {
18+ #[doc(hidden)]
1419 pub app: Rc<AppCell>,
20+ #[doc(hidden)]
1521 pub background_executor: BackgroundExecutor,
22+ #[doc(hidden)]
1623 pub foreground_executor: ForegroundExecutor,
24+ #[doc(hidden)]
1725 pub dispatcher: TestDispatcher,
18− pub test_platform: Rc<TestPlatform>,
26+ test_platform: Rc<TestPlatform>,
1927 text_system: Arc<TextSystem>,
2028 }
2129
@@ -76,6 +84,7 @@ impl Context for TestAppContext { · 54 unmodified lines
7684 }
7785
7886 impl TestAppContext {
87+ /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
7988 pub fn new(dispatcher: TestDispatcher) -> Self {
8089 let arc_dispatcher = Arc::new(dispatcher.clone());
8190 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
@@ -95,38 +104,47 @@ impl TestAppContext { · 13 unmodified lines
95104 }
96105 }
97106
107+ /// returns a new `TestAppContext` re-using the same executors to interleave tasks.
98108 pub fn new_app(&self) -> TestAppContext {
99109 Self::new(self.dispatcher.clone())
100110 }
101111
112+ /// Simulates quitting the app.
102113 pub fn quit(&self) {
103114 self.app.borrow_mut().shutdown();
104115 }
105116
117+ /// Schedules all windows to be redrawn on the next effect cycle.
106118 pub fn refresh(&mut self) -> Result<()> {
107119 let mut app = self.app.borrow_mut();
108120 app.refresh();
109121 Ok(())
110122 }
111123
124+ /// Returns an executor (for running tasks in the background)
112125 pub fn executor(&self) -> BackgroundExecutor {
113126 self.background_executor.clone()
114127 }
115128
129+ /// Returns an executor (for running tasks on the main thread)
116130 pub fn foreground_executor(&self) -> &ForegroundExecutor {
117131 &self.foreground_executor
118132 }
119133
134+ /// Gives you an `&mut AppContext` for the duration of the closure
120135 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
121136 let mut cx = self.app.borrow_mut();
122137 cx.update(f)
123138 }
124139
140+ /// Gives you an `&AppContext` for the duration of the closure
125141 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
126142 let cx = self.app.borrow();
127143 f(&*cx)
128144 }
129145
146+ /// Adds a new window. The Window will always be backed by a `TestWindow` which
147+ /// can be retrieved with `self.test_window(handle)`
130148 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
131149 where
132150 F: FnOnce(&mut ViewContext<V>) -> V,
@@ -136,12 +154,16 @@ impl TestAppContext { · 3 unmodified lines
136154 cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
137155 }
138156
157+ /// Adds a new window with no content.
139158 pub fn add_empty_window(&mut self) -> AnyWindowHandle {
140159 let mut cx = self.app.borrow_mut();
141160 cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
142161 .any_handle
143162 }
144163
164+ /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
165+ /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with
166+ /// the returned one. `let (view, cx) = cx.add_window_view(...);`
145167 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
146168 where
147169 F: FnOnce(&mut ViewContext<V>) -> V,
@@ -156,18 +178,23 @@ impl TestAppContext { · 8 unmodified lines
156178 (view, Box::leak(cx))
157179 }
158180
181+ /// returns the TextSystem
159182 pub fn text_system(&self) -> &Arc<TextSystem> {
160183 &self.text_system
161184 }
162185
186+ /// Simulates writing to the platform clipboard
163187 pub fn write_to_clipboard(&self, item: ClipboardItem) {
164188 self.test_platform.write_to_clipboard(item)
165189 }
166190
191+ /// Simulates reading from the platform clipboard.
192+ /// This will return the most recent value from `write_to_clipboard`.
167193 pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
168194 self.test_platform.read_from_clipboard()
169195 }
170196
197+ /// Simulates choosing a File in the platform's "Open" dialog.
171198 pub fn simulate_new_path_selection(
172199 &self,
173200 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
@@ -175,22 +202,27 @@ impl TestAppContext { · 1 unmodified line
175202 self.test_platform.simulate_new_path_selection(select_path);
176203 }
177204
205+ /// Simulates clicking a button in an platform-level alert dialog.
178206 pub fn simulate_prompt_answer(&self, button_ix: usize) {
179207 self.test_platform.simulate_prompt_answer(button_ix);
180208 }
181209
210+ /// Returns true if there's an alert dialog open.
182211 pub fn has_pending_prompt(&self) -> bool {
183212 self.test_platform.has_pending_prompt()
184213 }
185214
215+ /// Simulates the user resizing the window to the new size.
186216 pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
187217 self.test_window(window_handle).simulate_resize(size);
188218 }
189219
220+ /// Returns all windows open in the test.
190221 pub fn windows(&self) -> Vec<AnyWindowHandle> {
191222 self.app.borrow().windows().clone()
192223 }
193224
225+ /// Run the given task on the main thread.
194226 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
195227 where
196228 Fut: Future<Output = R> + 'static,
@@ -199,16 +231,20 @@ impl TestAppContext { · 2 unmodified lines
199231 self.foreground_executor.spawn(f(self.to_async()))
200232 }
201233
234+ /// true if the given global is defined
202235 pub fn has_global<G: 'static>(&self) -> bool {
203236 let app = self.app.borrow();
204237 app.has_global::<G>()
205238 }
206239
240+ /// runs the given closure with a reference to the global
241+ /// panics if `has_global` would return false.
207242 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
208243 let app = self.app.borrow();
209244 read(app.global(), &app)
210245 }
211246
247+ /// runs the given closure with a reference to the global (if set)
212248 pub fn try_read_global<G: 'static, R>(
213249 &self,
214250 read: impl FnOnce(&G, &AppContext) -> R,
@@ -217,11 +253,13 @@ impl TestAppContext { · 2 unmodified lines
217253 Some(read(lock.try_global()?, &lock))
218254 }
219255
256+ /// sets the global in this context.
220257 pub fn set_global<G: 'static>(&mut self, global: G) {
221258 let mut lock = self.app.borrow_mut();
222259 lock.set_global(global);
223260 }
224261
262+ /// updates the global in this context. (panics if `has_global` would return false)
225263 pub fn update_global<G: 'static, R>(
226264 &mut self,
227265 update: impl FnOnce(&mut G, &mut AppContext) -> R,
@@ -230,6 +268,8 @@ impl TestAppContext { · 2 unmodified lines
230268 lock.update_global(update)
231269 }
232270
271+ /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
272+ /// thread on the current thread in tests.
233273 pub fn to_async(&self) -> AsyncAppContext {
234274 AsyncAppContext {
235275 app: Rc::downgrade(&self.app),
@@ -238,10 +278,12 @@ impl TestAppContext { · 2 unmodified lines
238278 }
239279 }
240280
281+ /// Wait until there are no more pending tasks.
241282 pub fn run_until_parked(&mut self) {
242283 self.background_executor.run_until_parked()
243284 }
244285
286+ /// Simulate dispatching an action to the currently focused node in the window.
245287 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
246288 where
247289 A: Action,
@@ -255,7 +297,8 @@ impl TestAppContext { · 7 unmodified lines
255297
256298 /// simulate_keystrokes takes a space-separated list of keys to type.
257299 /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
258− /// will run backspace on the current editor through the command palette.
300+ /// in Zed, this will run backspace on the current editor through the command palette.
301+ /// This will also run the background executor until it's parked.
259302 pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
260303 for keystroke in keystrokes
261304 .split(" ")
@@ -270,7 +313,8 @@ impl TestAppContext { · 8 unmodified lines
270313
271314 /// simulate_input takes a string of text to type.
272315 /// cx.simulate_input("abc")
273− /// will type abc into your current editor.
316+ /// will type abc into your current editor
317+ /// This will also run the background executor until it's parked.
274318 pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
275319 for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
276320 self.dispatch_keystroke(window, keystroke.into(), false);
@@ -279,6 +323,7 @@ impl TestAppContext { · 2 unmodified lines
279323 self.background_executor.run_until_parked()
280324 }
281325
326+ /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
282327 pub fn dispatch_keystroke(
283328 &mut self,
284329 window: AnyWindowHandle,
@@ -289,6 +334,7 @@ impl TestAppContext { · 4 unmodified lines
289334 .simulate_keystroke(keystroke, is_held)
290335 }
291336
337+ /// Returns the `TestWindow` backing the given handle.
292338 pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
293339 self.app
294340 .borrow_mut()
@@ -303,6 +349,7 @@ impl TestAppContext { · 8 unmodified lines
303349 .clone()
304350 }
305351
352+ /// Returns a stream of notifications whenever the View or Model is updated.
306353 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
307354 let (tx, rx) = futures::channel::mpsc::unbounded();
308355 self.update(|cx| {
@@ -319,6 +366,7 @@ impl TestAppContext { · 10 unmodified lines
319366 rx
320367 }
321368
369+ /// Retuens a stream of events emitted by the given Model.
322370 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
323371 &mut self,
324372 entity: &Model<T>,
@@ -337,6 +385,8 @@ impl TestAppContext { · 12 unmodified lines
337385 rx
338386 }
339387
388+ /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
389+ /// don't need to jump in at a specific time).
340390 pub async fn condition<T: 'static>(
341391 &mut self,
342392 model: &Model<T>,
@@ -366,6 +416,7 @@ impl TestAppContext { · 23 unmodified lines
366416 }
367417
368418 impl<T: Send> Model<T> {
419+ /// Block until the next event is emitted by the model, then return it.
369420 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
370421 where
371422 Evt: Send + Clone + 'static,
@@ -395,6 +446,7 @@ impl<T: Send> Model<T> { · 23 unmodified lines
395446 }
396447
397448 impl<V: 'static> View<V> {
449+ /// Returns a future that resolves when the view is next updated.
398450 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
399451 use postage::prelude::{Sink as _, Stream as _};
400452
@@ -421,6 +473,7 @@ impl<V: 'static> View<V> { · 20 unmodified lines
421473 }
422474
423475 impl<V> View<V> {
476+ /// Returns a future that resolves when the condition becomes true.
424477 pub fn condition<Evt>(
425478 &self,
426479 cx: &TestAppContext,
@@ -471,12 +524,11 @@ impl<V> View<V> { · 44 unmodified lines
471524 }
472525 }
473526
474− // todo!(start_waiting)
475− // cx.borrow().foreground_executor().start_waiting();
527+ cx.borrow().background_executor().start_waiting();
476528 rx.recv()
477529 .await
478530 .expect("view dropped with pending condition");
479− // cx.borrow().foreground_executor().finish_waiting();
531+ cx.borrow().background_executor().finish_waiting();
480532 }
481533 })
482534 .await
@@ -488,6 +540,8 @@ impl<V> View<V> { · 5 unmodified lines
488540
489541 use derive_more::{Deref, DerefMut};
490542 #[derive(Deref, DerefMut, Clone)]
543+/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
544+/// run window-specific test code.
491545 pub struct VisualTestContext {
492546 #[deref]
493547 #[deref_mut]
@@ -496,10 +550,14 @@ pub struct VisualTestContext { · 2 unmodified lines
496550 }
497551
498552 impl<'a> VisualTestContext {
553+ /// Provides the `WindowContext` for the duration of the closure.
499554 pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
500555 self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
501556 }
502557
558+ /// Create a new VisualTestContext. You would typically shadow the passed in
559+ /// TestAppContext with this, as this is typically more useful.
560+ /// `let cx = VisualTestContext::from_window(window, cx);`
503561 pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
504562 Self {
505563 cx: cx.clone(),
@@ -507,10 +565,12 @@ impl<'a> VisualTestContext { · 1 unmodified line
507565 }
508566 }
509567
568+ /// Wait until there are no more pending tasks.
510569 pub fn run_until_parked(&self) {
511570 self.cx.background_executor.run_until_parked();
512571 }
513572
573+ /// Dispatch the action to the currently focused node.
514574 pub fn dispatch_action<A>(&mut self, action: A)
515575 where
516576 A: Action,
@@ -518,24 +578,32 @@ impl<'a> VisualTestContext { · 1 unmodified line
518578 self.cx.dispatch_action(self.window, action)
519579 }
520580
581+ /// Read the title off the window (set by `WindowContext#set_window_title`)
521582 pub fn window_title(&mut self) -> Option<String> {
522583 self.cx.test_window(self.window).0.lock().title.clone()
523584 }
524585
586+ /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
587+ /// Automatically runs until parked.
525588 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
526589 self.cx.simulate_keystrokes(self.window, keystrokes)
527590 }
528591
592+ /// Simulate typing text `cx.simulate_input("hello")`
593+ /// Automatically runs until parked.
529594 pub fn simulate_input(&mut self, input: &str) {
530595 self.cx.simulate_input(self.window, input)
531596 }
532597
598+ /// Simulates the user blurring the window.
533599 pub fn deactivate_window(&mut self) {
534600 if Some(self.window) == self.test_platform.active_window() {
535601 self.test_platform.set_active_window(None)
536602 }
537603 self.background_executor.run_until_parked();
538604 }
605+
606+ /// Simulates the user closing the window.
539607 /// Returns true if the window was closed.
540608 pub fn simulate_close(&mut self) -> bool {
541609 let handler = self
@@ -672,6 +740,7 @@ impl VisualContext for VisualTestContext { · 130 unmodified lines
672740 }
673741
674742 impl AnyWindowHandle {
743+ /// Creates the given view in this window.
675744 pub fn build_view<V: Render + 'static>(
676745 &self,
677746 cx: &mut TestAppContext,
@@ -681,6 +750,7 @@ impl AnyWindowHandle { · 3 unmodified lines
681750 }
682751 }
683752
753+/// An EmptyView for testing.
684754 pub struct EmptyView {}
685755
686756 impl Render for EmptyView {
crates/gpui/src/element.rs+5−5
@@ -31,14 +31,14 @@ pub trait IntoElement: Sized { · 30 unmodified lines
3131 /// The specific type of element into which the implementing type is converted.
3232 type Element: Element;
3333
34− /// The [ElementId] of self once converted into an [Element].
34+ /// The [`ElementId`] of self once converted into an [`Element`].
3535 /// If present, the resulting element's state will be carried across frames.
3636 fn element_id(&self) -> Option<ElementId>;
3737
38− /// Convert self into a type that implements [Element].
38+ /// Convert self into a type that implements [`Element`].
3939 fn into_element(self) -> Self::Element;
4040
41− /// Convert self into a dynamically-typed [AnyElement].
41+ /// Convert self into a dynamically-typed [`AnyElement`].
4242 fn into_any_element(self) -> AnyElement {
4343 self.into_element().into_any()
4444 }
@@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { · 70 unmodified lines
115115 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
116116 }
117117
118−/// You can derive [IntoElement] on any type that implements this trait.
118+/// You can derive [`IntoElement`] on any type that implements this trait.
119119 /// It is used to allow views to be expressed in terms of abstract data.
120120 pub trait RenderOnce: 'static {
121121 fn render(self, cx: &mut WindowContext) -> impl IntoElement;
@@ -224,7 +224,7 @@ enum ElementDrawPhase<S> { · 102 unmodified lines
224224 },
225225 }
226226
227−/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
227+/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
228228 impl<E: Element> DrawableElement<E> {
229229 fn new(element: E) -> Self {
230230 DrawableElement {
crates/gpui/src/elements/img.rs+4−4
@@ -2,7 +2,7 @@ use std::sync::Arc; · 1 unmodified line
22
33 use crate::{
44 point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
5− InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
5+ InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
66 StyleRefinement, Styled, WindowContext,
77 };
88 use futures::FutureExt;
@@ -12,13 +12,13 @@ use util::ResultExt; · 3 unmodified lines
1212 #[derive(Clone, Debug)]
1313 pub enum ImageSource {
1414 /// Image content will be loaded from provided URI at render time.
15− Uri(SharedString),
15+ Uri(SharedUrl),
1616 Data(Arc<ImageData>),
1717 Surface(CVImageBuffer),
1818 }
1919
20−impl From<SharedString> for ImageSource {
21− fn from(value: SharedString) -> Self {
20+impl From<SharedUrl> for ImageSource {
21+ fn from(value: SharedUrl) -> Self {
2222 Self::Uri(value)
2323 }
2424 }
crates/gpui/src/elements/overlay.rs+1−1
@@ -14,8 +14,8 @@ pub struct Overlay { · 13 unmodified lines
1414 children: SmallVec<[AnyElement; 2]>,
1515 anchor_corner: AnchorCorner,
1616 fit_mode: OverlayFitMode,
17− // todo!();
1817 anchor_position: Option<Point<Pixels>>,
18+ // todo!();
1919 // position_mode: OverlayPositionMode,
2020 }
2121
crates/gpui/src/executor.rs+42−2
@@ -32,6 +32,12 @@ pub struct ForegroundExecutor { · 31 unmodified lines
3232 not_send: PhantomData<Rc<()>>,
3333 }
3434
35+/// Task is a primitive that allows work to happen in the background.
36+///
37+/// It implements [`Future`] so you can `.await` on it.
38+///
39+/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
40+/// the task to continue running in the background, but with no way to return a value.
3541 #[must_use]
3642 #[derive(Debug)]
3743 pub enum Task<T> {
@@ -40,10 +46,12 @@ pub enum Task<T> { · 2 unmodified lines
4046 }
4147
4248 impl<T> Task<T> {
49+ /// Create a new task that will resolve with the value
4350 pub fn ready(val: T) -> Self {
4451 Task::Ready(Some(val))
4552 }
4653
54+ /// Detaching a task runs it to completion in the background
4755 pub fn detach(self) {
4856 match self {
4957 Task::Ready(_) => {}
@@ -57,6 +65,8 @@ where · 7 unmodified lines
5765 T: 'static,
5866 E: 'static + Debug,
5967 {
68+ /// Run the task to completion in the background and log any
69+ /// errors that occur.
6070 #[track_caller]
6171 pub fn detach_and_log_err(self, cx: &mut AppContext) {
6272 let location = core::panic::Location::caller();
@@ -97,6 +107,10 @@ type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>; · 34 unmodified lines
97107
98108 type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
99109
110+/// BackgroundExecutor lets you run things on background threads.
111+/// In production this is a thread pool with no ordering guarantees.
112+/// In tests this is simalated by running tasks one by one in a deterministic
113+/// (but arbitrary) order controlled by the `SEED` environment variable.
100114 impl BackgroundExecutor {
101115 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
102116 Self { dispatcher }
@@ -135,6 +149,7 @@ impl BackgroundExecutor { · 32 unmodified lines
135149 Task::Spawned(task)
136150 }
137151
152+ /// Used by the test harness to run an async test in a syncronous fashion.
138153 #[cfg(any(test, feature = "test-support"))]
139154 #[track_caller]
140155 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@@ -145,6 +160,8 @@ impl BackgroundExecutor { · 4 unmodified lines
145160 }
146161 }
147162
163+ /// Block the current thread until the given future resolves.
164+ /// Consider using `block_with_timeout` instead.
148165 pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
149166 if let Ok(value) = self.block_internal(true, future, usize::MAX) {
150167 value
@@ -206,6 +223,8 @@ impl BackgroundExecutor { · 55 unmodified lines
206223 }
207224 }
208225
226+ /// Block the current thread until the given future resolves
227+ /// or `duration` has elapsed.
209228 pub fn block_with_timeout<R>(
210229 &self,
211230 duration: Duration,
@@ -238,6 +257,8 @@ impl BackgroundExecutor { · 26 unmodified lines
238257 }
239258 }
240259
260+ /// Scoped lets you start a number of tasks and waits
261+ /// for all of them to complete before returning.
241262 pub async fn scoped<'scope, F>(&self, scheduler: F)
242263 where
243264 F: FnOnce(&mut Scope<'scope>),
@@ -253,6 +274,9 @@ impl BackgroundExecutor { · 9 unmodified lines
253274 }
254275 }
255276
277+ /// Returns a task that will complete after the given duration.
278+ /// Depending on other concurrent tasks the elapsed duration may be longer
279+ /// than reqested.
256280 pub fn timer(&self, duration: Duration) -> Task<()> {
257281 let (runnable, task) = async_task::spawn(async move {}, {
258282 let dispatcher = self.dispatcher.clone();
@@ -262,65 +286,81 @@ impl BackgroundExecutor { · 3 unmodified lines
262286 Task::Spawned(task)
263287 }
264288
289+ /// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
265290 #[cfg(any(test, feature = "test-support"))]
266291 pub fn start_waiting(&self) {
267292 self.dispatcher.as_test().unwrap().start_waiting();
268293 }
269294
295+ /// in tests, removes the debugging data added by start_waiting
270296 #[cfg(any(test, feature = "test-support"))]
271297 pub fn finish_waiting(&self) {
272298 self.dispatcher.as_test().unwrap().finish_waiting();
273299 }
274300
301+ /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
275302 #[cfg(any(test, feature = "test-support"))]
276303 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
277304 self.dispatcher.as_test().unwrap().simulate_random_delay()
278305 }
279306
307+ /// in tests, indicate that a given task from `spawn_labeled` should run after everything else
280308 #[cfg(any(test, feature = "test-support"))]
281309 pub fn deprioritize(&self, task_label: TaskLabel) {
282310 self.dispatcher.as_test().unwrap().deprioritize(task_label)
283311 }
284312
313+ /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
285314 #[cfg(any(test, feature = "test-support"))]
286315 pub fn advance_clock(&self, duration: Duration) {
287316 self.dispatcher.as_test().unwrap().advance_clock(duration)
288317 }
289318
319+ /// in tests, run one task.
290320 #[cfg(any(test, feature = "test-support"))]
291321 pub fn tick(&self) -> bool {
292322 self.dispatcher.as_test().unwrap().tick(false)
293323 }
294324
325+ /// in tests, run all tasks that are ready to run. If after doing so
326+ /// the test still has outstanding tasks, this will panic. (See also `allow_parking`)
295327 #[cfg(any(test, feature = "test-support"))]
296328 pub fn run_until_parked(&self) {
297329 self.dispatcher.as_test().unwrap().run_until_parked()
298330 }
299331
332+ /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
333+ /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that
334+ /// do take real async time to run.
300335 #[cfg(any(test, feature = "test-support"))]
301336 pub fn allow_parking(&self) {
302337 self.dispatcher.as_test().unwrap().allow_parking();
303338 }
304339
340+ /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
305341 #[cfg(any(test, feature = "test-support"))]
306342 pub fn rng(&self) -> StdRng {
307343 self.dispatcher.as_test().unwrap().rng()
308344 }
309345
346+ /// How many CPUs are available to the dispatcher
310347 pub fn num_cpus(&self) -> usize {
311348 num_cpus::get()
312349 }
313350
351+ /// Whether we're on the main thread.
314352 pub fn is_main_thread(&self) -> bool {
315353 self.dispatcher.is_main_thread()
316354 }
317355
318356 #[cfg(any(test, feature = "test-support"))]
357+ /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
319358 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
320359 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
321360 }
322361 }
323362
363+/// ForegroundExecutor runs things on the main thread.
324364 impl ForegroundExecutor {
325365 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
326366 Self {
@@ -329,8 +369,7 @@ impl ForegroundExecutor { · 2 unmodified lines
329369 }
330370 }
331371
332− /// Enqueues the given closure to be run on any thread. The closure returns
333− /// a future which will be run to completion on any available thread.
372+ /// Enqueues the given Task to run on the main thread at some point in the future.
334373 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
335374 where
336375 R: 'static,
@@ -350,6 +389,7 @@ impl ForegroundExecutor { · 13 unmodified lines
350389 }
351390 }
352391
392+/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
353393 pub struct Scope<'a> {
354394 executor: BackgroundExecutor,
355395 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
crates/gpui/src/gpui.rs+2−0
@@ -18,6 +18,7 @@ mod platform; · 17 unmodified lines
1818 pub mod prelude;
1919 mod scene;
2020 mod shared_string;
21+mod shared_url;
2122 mod style;
2223 mod styled;
2324 mod subscription;
@@ -67,6 +68,7 @@ pub use refineable::*; · 43 unmodified lines
6768 pub use scene::*;
6869 use seal::Sealed;
6970 pub use shared_string::*;
71+pub use shared_url::*;
7072 pub use smol::Timer;
7173 pub use style::*;
7274 pub use styled::*;
crates/gpui/src/image_cache.rs+3−3
@@ -1,4 +1,4 @@
1−use crate::{ImageData, ImageId, SharedString};
1+use crate::{ImageData, ImageId, SharedUrl};
22 use collections::HashMap;
33 use futures::{
44 future::{BoxFuture, Shared},
@@ -44,7 +44,7 @@ impl From<ImageError> for Error { · 39 unmodified lines
4444
4545 pub struct ImageCache {
4646 client: Arc<dyn HttpClient>,
47− images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
47+ images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
4848 }
4949
5050 type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@@ -59,7 +59,7 @@ impl ImageCache { · 8 unmodified lines
5959
6060 pub fn get(
6161 &self,
62− uri: impl Into<SharedString>,
62+ uri: impl Into<SharedUrl>,
6363 ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
6464 let uri = uri.into();
6565 let mut images = self.images.lock();
crates/gpui/src/input.rs+1−1
@@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized { · 33 unmodified lines
3434 ) -> Option<Bounds<Pixels>>;
3535 }
3636
37−/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input`
37+/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`]
3838 /// with an instance during your element's paint.
3939 pub struct ElementInputHandler<V> {
4040 view: View<V>,
crates/gpui/src/interactive.rs+14−0
@@ -178,6 +178,20 @@ impl ScrollDelta { · 177 unmodified lines
178178 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
179179 }
180180 }
181+
182+ pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
183+ match (self, other) {
184+ (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
185+ ScrollDelta::Pixels(px_a + px_b)
186+ }
187+
188+ (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
189+ ScrollDelta::Lines(lines_a + lines_b)
190+ }
191+
192+ _ => other,
193+ }
194+ }
181195 }
182196
183197 #[derive(Clone, Debug, Default)]
crates/gpui/src/key_dispatch.rs+75−2
@@ -192,8 +192,8 @@ impl DispatchTree { · 191 unmodified lines
192192 keymap
193193 .bindings_for_action(action)
194194 .filter(|binding| {
195− for i in 1..context_stack.len() {
196− let context = &context_stack[0..i];
195+ for i in 0..context_stack.len() {
196+ let context = &context_stack[0..=i];
197197 if keymap.binding_enabled(binding, context) {
198198 return true;
199199 }
@@ -283,3 +283,76 @@ impl DispatchTree { · 83 unmodified lines
283283 *self.node_stack.last().unwrap()
284284 }
285285 }
286+
287+#[cfg(test)]
288+mod tests {
289+ use std::{rc::Rc, sync::Arc};
290+
291+ use parking_lot::Mutex;
292+
293+ use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap};
294+
295+ #[derive(PartialEq, Eq)]
296+ struct TestAction;
297+
298+ impl Action for TestAction {
299+ fn name(&self) -> &'static str {
300+ "test::TestAction"
301+ }
302+
303+ fn debug_name() -> &'static str
304+ where
305+ Self: ::std::marker::Sized,
306+ {
307+ "test::TestAction"
308+ }
309+
310+ fn partial_eq(&self, action: &dyn Action) -> bool {
311+ action
312+ .as_any()
313+ .downcast_ref::<Self>()
314+ .map_or(false, |a| self == a)
315+ }
316+
317+ fn boxed_clone(&self) -> std::boxed::Box<dyn Action> {
318+ Box::new(TestAction)
319+ }
320+
321+ fn as_any(&self) -> &dyn ::std::any::Any {
322+ self
323+ }
324+
325+ fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
326+ where
327+ Self: Sized,
328+ {
329+ Ok(Box::new(TestAction))
330+ }
331+ }
332+
333+ #[test]
334+ fn test_keybinding_for_action_bounds() {
335+ let keymap = Keymap::new(vec![KeyBinding::new(
336+ "cmd-n",
337+ TestAction,
338+ Some("ProjectPanel"),
339+ )]);
340+
341+ let mut registry = ActionRegistry::default();
342+
343+ registry.load_action::<TestAction>();
344+
345+ let keymap = Arc::new(Mutex::new(keymap));
346+
347+ let tree = DispatchTree::new(keymap, Rc::new(registry));
348+
349+ let contexts = vec![
350+ KeyContext::parse("Workspace").unwrap(),
351+ KeyContext::parse("ProjectPanel").unwrap(),
352+ ];
353+
354+ let keybinding = tree.bindings_for_action(&TestAction, &contexts);
355+
356+ assert!(keybinding[0].action.partial_eq(&TestAction))
357+ }
358+}
crates/gpui/src/platform/mac/display.rs+2−2
@@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); · 13 unmodified lines
1414 unsafe impl Send for MacDisplay {}
1515
1616 impl MacDisplay {
17− /// Get the screen with the given [DisplayId].
17+ /// Get the screen with the given [`DisplayId`].
1818 pub fn find_by_id(id: DisplayId) -> Option<Self> {
1919 Self::all().find(|screen| screen.id() == id)
2020 }
2121
22− /// Get the screen with the given persistent [Uuid].
22+ /// Get the screen with the given persistent [`Uuid`].
2323 pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
2424 Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
2525 }
crates/gpui/src/platform/test/display.rs+1−1
@@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay { · 31 unmodified lines
3232 }
3333
3434 fn as_any(&self) -> &dyn std::any::Any {
35− todo!()
35+ unimplemented!()
3636 }
3737
3838 fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
crates/gpui/src/platform/test/platform.rs+1−1
@@ -15,6 +15,7 @@ use std::{ · 14 unmodified lines
1515 time::Duration,
1616 };
1717
18+/// TestPlatform implements the Platform trait for use in tests.
1819 pub struct TestPlatform {
1920 background_executor: BackgroundExecutor,
2021 foreground_executor: ForegroundExecutor,
@@ -103,7 +104,6 @@ impl TestPlatform { · 82 unmodified lines
103104 }
104105 }
105106
106−// todo!("implement out what our tests needed in GPUI 1")
107107 impl Platform for TestPlatform {
108108 fn background_executor(&self) -> BackgroundExecutor {
109109 self.background_executor.clone()
crates/gpui/src/shared_url.rs+25−0
@@ -0,0 +1,25 @@
1+use derive_more::{Deref, DerefMut};
2+
3+use crate::SharedString;
4+
5+/// A [`SharedString`] containing a URL.
6+#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)]
7+pub struct SharedUrl(SharedString);
8+
9+impl std::fmt::Debug for SharedUrl {
10+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11+ self.0.fmt(f)
12+ }
13+}
14+
15+impl std::fmt::Display for SharedUrl {
16+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+ write!(f, "{}", self.0.as_ref())
18+ }
19+}
20+
21+impl<T: Into<SharedString>> From<T> for SharedUrl {
22+ fn from(value: T) -> Self {
23+ Self(value.into())
24+ }
25+}
crates/gpui/src/subscription.rs+3−3
@@ -37,10 +37,10 @@ where · 36 unmodified lines
3737 })))
3838 }
3939
40− /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions
40+ /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions
4141 /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.
42− /// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter
43− /// to activate the `[Subscription]`.
42+ /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter
43+ /// to activate the [`Subscription`].
4444 #[must_use]
4545 pub fn insert(
4646 &self,
crates/gpui/src/test.rs+28−0
@@ -1,3 +1,30 @@
1+//! Test support for GPUI.
2+//!
3+//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context,
4+//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run
5+//! deterministically even in the face of arbitrary parallelism.
6+//!
7+//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test`
8+//! or `cargo-nextest`, or another runner of your choice.
9+//!
10+//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts
11+//! as you need.
12+//!
13+//! ## Example
14+//!
15+//! ```
16+//! use gpui;
17+//!
18+//! #[gpui::test]
19+//! async fn test_example(cx: &TestAppContext) {
20+//! assert!(true)
21+//! }
22+//!
23+//! #[gpui::test]
24+//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) {
25+//! assert!(true)
26+//! }
27+//! ```
128 use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
229 use futures::StreamExt as _;
330 use rand::prelude::*;
@@ -68,6 +95,7 @@ impl<T: 'static> futures::Stream for Observation<T> { · 64 unmodified lines
6895 }
6996 }
7097
98+/// observe returns a stream of the change events from the given `View` or `Model`
7199 pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
72100 let (tx, rx) = smol::channel::unbounded();
73101 let _subscription = cx.update(|cx| {
crates/gpui/src/window.rs+142−12
@@ -1,3 +1,5 @@
1+#![deny(missing_docs)]
2+
13 use crate::{
24 px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef,
35 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
@@ -85,10 +87,12 @@ pub enum DispatchPhase { · 81 unmodified lines
8587 }
8688
8789 impl DispatchPhase {
90+ /// Returns true if this represents the "bubble" phase.
8891 pub fn bubble(self) -> bool {
8992 self == DispatchPhase::Bubble
9093 }
9194
95+ /// Returns true if this represents the "capture" phase.
9296 pub fn capture(self) -> bool {
9397 self == DispatchPhase::Capture
9498 }
@@ -103,7 +107,10 @@ struct FocusEvent { · 8 unmodified lines
103107 current_focus_path: SmallVec<[FocusId; 8]>,
104108 }
105109
106−slotmap::new_key_type! { pub struct FocusId; }
110+slotmap::new_key_type! {
111+ /// A globally unique identifier for a focusable element.
112+ pub struct FocusId;
113+}
107114
108115 thread_local! {
109116 pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));
@@ -231,6 +238,7 @@ impl Drop for FocusHandle { · 121 unmodified lines
231238 /// FocusableView allows users of your view to easily
232239 /// focus it (using cx.focus_view(view))
233240 pub trait FocusableView: 'static + Render {
241+ /// Returns the focus handle associated with this view.
234242 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
235243 }
236244
@@ -240,9 +248,11 @@ pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {} · 3 unmodified lines
240248
241249 impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
242250
251+/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
243252 pub struct DismissEvent;
244253
245254 // Holds the state for a specific window.
255+#[doc(hidden)]
246256 pub struct Window {
247257 pub(crate) handle: AnyWindowHandle,
248258 pub(crate) removed: bool,
@@ -434,6 +444,7 @@ impl Window { · 185 unmodified lines
434444 #[derive(Clone, Debug, Default, PartialEq, Eq)]
435445 #[repr(C)]
436446 pub struct ContentMask<P: Clone + Default + Debug> {
447+ /// The bounds
437448 pub bounds: Bounds<P>,
438449 }
439450
@@ -453,8 +464,8 @@ impl ContentMask<Pixels> { · 13 unmodified lines
453464 }
454465
455466 /// Provides access to application state in the context of a single window. Derefs
456−/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes
457−/// an `AppContext` and call any `AppContext` methods.
467+/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes
468+/// an [`AppContext`] and call any [`AppContext`] methods.
458469 pub struct WindowContext<'a> {
459470 pub(crate) app: &'a mut AppContext,
460471 pub(crate) window: &'a mut Window,
@@ -482,20 +493,20 @@ impl<'a> WindowContext<'a> { · 21 unmodified lines
482493 self.window.removed = true;
483494 }
484495
485− /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus
496+ /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
486497 /// for elements rendered within this window.
487498 pub fn focus_handle(&mut self) -> FocusHandle {
488499 FocusHandle::new(&self.window.focus_handles)
489500 }
490501
491− /// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`.
502+ /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`.
492503 pub fn focused(&self) -> Option<FocusHandle> {
493504 self.window
494505 .focus
495506 .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))
496507 }
497508
498− /// Move focus to the element associated with the given `FocusHandle`.
509+ /// Move focus to the element associated with the given [`FocusHandle`].
499510 pub fn focus(&mut self, handle: &FocusHandle) {
500511 if !self.window.focus_enabled || self.window.focus == Some(handle.id) {
501512 return;
@@ -525,11 +536,13 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines
525536 self.notify();
526537 }
527538
539+ /// Blur the window and don't allow anything in it to be focused again.
528540 pub fn disable_focus(&mut self) {
529541 self.blur();
530542 self.window.focus_enabled = false;
531543 }
532544
545+ /// Dispatch the given action on the currently focused element.
533546 pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
534547 let focus_handle = self.focused();
535548
@@ -591,6 +604,9 @@ impl<'a> WindowContext<'a> { · 55 unmodified lines
591604 });
592605 }
593606
607+ /// Subscribe to events emitted by a model or view.
608+ /// The entity to which you're subscribing must implement the [`EventEmitter`] trait.
609+ /// The callback will be invoked a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a window context for the current window.
594610 pub fn subscribe<Emitter, E, Evt>(
595611 &mut self,
596612 entity: &E,
@@ -754,6 +770,9 @@ impl<'a> WindowContext<'a> { · 157 unmodified lines
754770 .request_measured_layout(style, rem_size, measure)
755771 }
756772
773+ /// Compute the layout for the given id within the given available space.
774+ /// This method is called for its side effect, typically by the framework prior to painting.
775+ /// After calling it, you can request the bounds of the given layout node id or any descendant.
757776 pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
758777 let mut layout_engine = self.window.layout_engine.take().unwrap();
759778 layout_engine.compute_layout(layout_id, available_space, self);
@@ -788,30 +807,37 @@ impl<'a> WindowContext<'a> { · 28 unmodified lines
788807 .retain(&(), |callback| callback(self));
789808 }
790809
810+ /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
791811 pub fn window_bounds(&self) -> WindowBounds {
792812 self.window.bounds
793813 }
794814
815+ /// Returns the size of the drawable area within the window.
795816 pub fn viewport_size(&self) -> Size<Pixels> {
796817 self.window.viewport_size
797818 }
798819
820+ /// Returns whether this window is focused by the operating system (receiving key events).
799821 pub fn is_window_active(&self) -> bool {
800822 self.window.active
801823 }
802824
825+ /// Toggle zoom on the window.
803826 pub fn zoom_window(&self) {
804827 self.window.platform_window.zoom();
805828 }
806829
830+ /// Update the window's title at the platform level.
807831 pub fn set_window_title(&mut self, title: &str) {
808832 self.window.platform_window.set_title(title);
809833 }
810834
835+ /// Mark the window as dirty at the platform level.
811836 pub fn set_window_edited(&mut self, edited: bool) {
812837 self.window.platform_window.set_edited(edited);
813838 }
814839
840+ /// Determine the display on which the window is visible.
815841 pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
816842 self.platform
817843 .displays()
@@ -819,6 +845,7 @@ impl<'a> WindowContext<'a> { · 1 unmodified line
819845 .find(|display| display.id() == self.window.display_id)
820846 }
821847
848+ /// Show the platform character palette.
822849 pub fn show_character_palette(&self) {
823850 self.window.platform_window.show_character_palette();
824851 }
@@ -936,6 +963,7 @@ impl<'a> WindowContext<'a> { · 111 unmodified lines
936963 .on_action(action_type, ArenaRef::from(listener));
937964 }
938965
966+ /// Determine whether the given action is available along the dispatch path to the currently focused element.
939967 pub fn is_action_available(&self, action: &dyn Action) -> bool {
940968 let target = self
941969 .focused()
@@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> { · 20 unmodified lines
962990 self.window.modifiers
963991 }
964992
993+ /// Update the cursor style at the platform level.
965994 pub fn set_cursor_style(&mut self, style: CursorStyle) {
966995 self.window.requested_cursor_style = Some(style)
967996 }
@@ -991,7 +1020,7 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines
9911020 true
9921021 }
9931022
994− pub fn was_top_layer_under_active_drag(
1023+ pub(crate) fn was_top_layer_under_active_drag(
9951024 &self,
9961025 point: &Point<Pixels>,
9971026 level: &StackingOrder,
@@ -1649,6 +1678,7 @@ impl<'a> WindowContext<'a> { · 651 unmodified lines
16491678 self.dispatch_keystroke_observers(event, None);
16501679 }
16511680
1681+ /// Determine whether a potential multi-stroke key binding is in progress on this window.
16521682 pub fn has_pending_keystrokes(&self) -> bool {
16531683 self.window
16541684 .rendered_frame
@@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> { · 60 unmodified lines
17151745 subscription
17161746 }
17171747
1748+ /// Focus the current window and bring it to the foreground at the platform level.
17181749 pub fn activate_window(&self) {
17191750 self.window.platform_window.activate();
17201751 }
17211752
1753+ /// Minimize the current window at the platform level.
17221754 pub fn minimize_window(&self) {
17231755 self.window.platform_window.minimize();
17241756 }
17251757
1758+ /// Toggle full screen status on the current window at the platform level.
17261759 pub fn toggle_full_screen(&self) {
17271760 self.window.platform_window.toggle_full_screen();
17281761 }
17291762
1763+ /// Present a platform dialog.
1764+ /// The provided message will be presented, along with buttons for each answer.
1765+ /// When a button is clicked, the returned Receiver will receive the index of the clicked button.
17301766 pub fn prompt(
17311767 &self,
17321768 level: PromptLevel,
1733− msg: &str,
1769+ message: &str,
17341770 answers: &[&str],
17351771 ) -> oneshot::Receiver<usize> {
1736− self.window.platform_window.prompt(level, msg, answers)
1772+ self.window.platform_window.prompt(level, message, answers)
17371773 }
17381774
1775+ /// Returns all available actions for the focused element.
17391776 pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
17401777 let node_id = self
17411778 .window
@@ -1754,6 +1791,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines
17541791 .available_actions(node_id)
17551792 }
17561793
1794+ /// Returns key bindings that invoke the given action on the currently focused element.
17571795 pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
17581796 self.window
17591797 .rendered_frame
@@ -1764,6 +1802,7 @@ impl<'a> WindowContext<'a> { · 4 unmodified lines
17641802 )
17651803 }
17661804
1805+ /// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
17671806 pub fn bindings_for_action_in(
17681807 &self,
17691808 action: &dyn Action,
@@ -1782,6 +1821,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines
17821821 dispatch_tree.bindings_for_action(action, &context_stack)
17831822 }
17841823
1824+ /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
17851825 pub fn listener_for<V: Render, E>(
17861826 &self,
17871827 view: &View<V>,
@@ -1793,6 +1833,7 @@ impl<'a> WindowContext<'a> { · 5 unmodified lines
17931833 }
17941834 }
17951835
1836+ /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
17961837 pub fn handler_for<V: Render>(
17971838 &self,
17981839 view: &View<V>,
@@ -1804,7 +1845,8 @@ impl<'a> WindowContext<'a> { · 5 unmodified lines
18041845 }
18051846 }
18061847
1807− //========== ELEMENT RELATED FUNCTIONS ===========
1848+ /// Invoke the given function with the given focus handle present on the key dispatch stack.
1849+ /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint.
18081850 pub fn with_key_dispatch<R>(
18091851 &mut self,
18101852 context: Option<KeyContext>,
@@ -1843,6 +1885,8 @@ impl<'a> WindowContext<'a> { · 32 unmodified lines
18431885 }
18441886 }
18451887
1888+ /// Register a callback that can interrupt the closing of the current window based the returned boolean.
1889+ /// If the callback returns false, the window won't be closed.
18461890 pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
18471891 let mut this = self.to_async();
18481892 self.window
@@ -2017,19 +2061,24 @@ impl<'a> BorrowMut<AppContext> for WindowContext<'a> { · 168 unmodified lines
20172061 }
20182062 }
20192063
2064+/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`]
20202065 pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
2066+ #[doc(hidden)]
20212067 fn app_mut(&mut self) -> &mut AppContext {
20222068 self.borrow_mut()
20232069 }
20242070
2071+ #[doc(hidden)]
20252072 fn app(&self) -> &AppContext {
20262073 self.borrow()
20272074 }
20282075
2076+ #[doc(hidden)]
20292077 fn window(&self) -> &Window {
20302078 self.borrow()
20312079 }
20322080
2081+ #[doc(hidden)]
20332082 fn window_mut(&mut self) -> &mut Window {
20342083 self.borrow_mut()
20352084 }
@@ -2279,6 +2328,10 @@ impl BorrowMut<Window> for WindowContext<'_> { · 243 unmodified lines
22792328
22802329 impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
22812330
2331+/// Provides access to application state that is specialized for a particular [`View`].
2332+/// Allows you to interact with focus, emit events, etc.
2333+/// ViewContext also derefs to [`WindowContext`], giving you access to all of its methods as well.
2334+/// When you call [`View::update`], you're passed a `&mut V` and an `&mut ViewContext<V>`.
22822335 pub struct ViewContext<'a, V> {
22832336 window_cx: WindowContext<'a>,
22842337 view: &'a View<V>,
@@ -2316,14 +2369,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 31 unmodified lines
23162369 }
23172370 }
23182371
2372+ /// Get the entity_id of this view.
23192373 pub fn entity_id(&self) -> EntityId {
23202374 self.view.entity_id()
23212375 }
23222376
2377+ /// Get the view pointer underlying this context.
23232378 pub fn view(&self) -> &View<V> {
23242379 self.view
23252380 }
23262381
2382+ /// Get the model underlying this view.
23272383 pub fn model(&self) -> &Model<V> {
23282384 &self.view.model
23292385 }
@@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines
23332389 &mut self.window_cx
23342390 }
23352391
2392+ /// Set a given callback to be run on the next frame.
23362393 pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
23372394 where
23382395 V: 'static,
@@ -2350,6 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 11 unmodified lines
23502407 });
23512408 }
23522409
2410+ /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`].
23532411 pub fn observe<V2, E>(
23542412 &mut self,
23552413 entity: &E,
@@ -2383,6 +2441,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 27 unmodified lines
23832441 subscription
23842442 }
23852443
2444+ /// Subscribe to events emitted by another model or view.
2445+ /// The entity to which you're subscribing must implement the [`EventEmitter`] trait.
2446+ /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a view context for the current view.
23862447 pub fn subscribe<V2, E, Evt>(
23872448 &mut self,
23882449 entity: &E,
@@ -2440,6 +2501,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 51 unmodified lines
24402501 subscription
24412502 }
24422503
2504+ /// Register a callback to be invoked when the given Model or View is released.
24432505 pub fn observe_release<V2, E>(
24442506 &mut self,
24452507 entity: &E,
@@ -2466,6 +2528,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 20 unmodified lines
24662528 subscription
24672529 }
24682530
2531+ /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty.
2532+ /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn.
24692533 pub fn notify(&mut self) {
24702534 if !self.window.drawing {
24712535 self.window_cx.notify();
@@ -2475,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines
24752539 }
24762540 }
24772541
2542+ /// Register a callback to be invoked when the window is resized.
24782543 pub fn observe_window_bounds(
24792544 &mut self,
24802545 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2488,6 +2553,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 7 unmodified lines
24882553 subscription
24892554 }
24902555
2556+ /// Register a callback to be invoked when the window is activated or deactivated.
24912557 pub fn observe_window_activation(
24922558 &mut self,
24932559 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2620,6 +2686,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 126 unmodified lines
26202686 subscription
26212687 }
26222688
2689+ /// Schedule a future to be run asynchronously.
2690+ /// The given callback is invoked with a [`WeakView<V>`] to avoid leaking the view for a long-running process.
2691+ /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points.
2692+ /// The returned future will be polled on the main thread.
26232693 pub fn spawn<Fut, R>(
26242694 &mut self,
26252695 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2632,6 +2702,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 6 unmodified lines
26322702 self.window_cx.spawn(|cx| f(view, cx))
26332703 }
26342704
2705+ /// Update the global state of the given type.
26352706 pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
26362707 where
26372708 G: 'static,
@@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 4 unmodified lines
26422713 result
26432714 }
26442715
2716+ /// Register a callback to be invoked when the given global state changes.
26452717 pub fn observe_global<G: 'static>(
26462718 &mut self,
26472719 mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,
@@ -2660,6 +2732,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 12 unmodified lines
26602732 subscription
26612733 }
26622734
2735+ /// Add a listener for any mouse event that occurs in the window.
2736+ /// This is a fairly low level method.
2737+ /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc.
26632738 pub fn on_mouse_event<Event: 'static>(
26642739 &mut self,
26652740 handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@@ -2672,6 +2747,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 6 unmodified lines
26722747 });
26732748 }
26742749
2750+ /// Register a callback to be invoked when the given Key Event is dispatched to the window.
26752751 pub fn on_key_event<Event: 'static>(
26762752 &mut self,
26772753 handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@@ -2684,6 +2760,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 6 unmodified lines
26842760 });
26852761 }
26862762
2763+ /// Register a callback to be invoked when the given Action type is dispatched to the window.
26872764 pub fn on_action(
26882765 &mut self,
26892766 action_type: TypeId,
@@ -2698,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 8 unmodified lines
26982775 });
26992776 }
27002777
2778+ /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe].
27012779 pub fn emit<Evt>(&mut self, event: Evt)
27022780 where
27032781 Evt: 'static,
@@ -2711,6 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 7 unmodified lines
27112789 });
27122790 }
27132791
2792+ /// Move focus to the current view, assuming it implements [`FocusableView`].
27142793 pub fn focus_self(&mut self)
27152794 where
27162795 V: FocusableView,
@@ -2718,6 +2797,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 1 unmodified line
27182797 self.defer(|view, cx| view.focus_handle(cx).focus(cx))
27192798 }
27202799
2800+ /// Convenience method for accessing view state in an event callback.
2801+ ///
2802+ /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`,
2803+ /// but it's often useful to be able to access view state in these
2804+ /// callbacks. This method provides a convenient way to do so.
27212805 pub fn listener<E>(
27222806 &self,
27232807 f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
@@ -2827,14 +2911,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { · 103 unmodified lines
28272911 }
28282912
28292913 // #[derive(Clone, Copy, Eq, PartialEq, Hash)]
2830−slotmap::new_key_type! { pub struct WindowId; }
2914+slotmap::new_key_type! {
2915+ /// A unique identifier for a window.
2916+ pub struct WindowId;
2917+}
28312918
28322919 impl WindowId {
2920+ /// Converts this window ID to a `u64`.
28332921 pub fn as_u64(&self) -> u64 {
28342922 self.0.as_ffi()
28352923 }
28362924 }
28372925
2926+/// A handle to a window with a specific root view type.
2927+/// Note that this does not keep the window alive on its own.
28382928 #[derive(Deref, DerefMut)]
28392929 pub struct WindowHandle<V> {
28402930 #[deref]
@@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> { · 3 unmodified lines
28442934 }
28452935
28462936 impl<V: 'static + Render> WindowHandle<V> {
2937+ /// Create a new handle from a window ID.
2938+ /// This does not check if the root type of the window is `V`.
28472939 pub fn new(id: WindowId) -> Self {
28482940 WindowHandle {
28492941 any_handle: AnyWindowHandle {
@@ -2854,6 +2946,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 4 unmodified lines
28542946 }
28552947 }
28562948
2949+ /// Get the root view out of this window.
2950+ ///
2951+ /// This will fail if the window is closed or if the root view's type does not match `V`.
28572952 pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>
28582953 where
28592954 C: Context,
@@ -2865,6 +2960,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 5 unmodified lines
28652960 }))
28662961 }
28672962
2963+ /// Update the root view of this window.
2964+ ///
2965+ /// This will fail if the window has been closed or if the root view's type does not match
28682966 pub fn update<C, R>(
28692967 &self,
28702968 cx: &mut C,
@@ -2881,6 +2979,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 10 unmodified lines
28812979 })?
28822980 }
28832981
2982+ /// Read the root view out of this window.
2983+ ///
2984+ /// This will fail if the window is closed or if the root view's type does not match `V`.
28842985 pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
28852986 let x = cx
28862987 .windows
@@ -2897,6 +2998,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 10 unmodified lines
28972998 Ok(x.read(cx))
28982999 }
28993000
3001+ /// Read the root view out of this window, with a callback
3002+ ///
3003+ /// This will fail if the window is closed or if the root view's type does not match `V`.
29003004 pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
29013005 where
29023006 C: Context,
@@ -2904,6 +3008,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line
29043008 cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
29053009 }
29063010
3011+ /// Read the root view pointer off of this window.
3012+ ///
3013+ /// This will fail if the window is closed or if the root view's type does not match `V`.
29073014 pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
29083015 where
29093016 C: Context,
@@ -2911,6 +3018,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line
29113018 cx.read_window(self, |root_view, _cx| root_view.clone())
29123019 }
29133020
3021+ /// Check if this window is 'active'.
3022+ ///
3023+ /// Will return `None` if the window is closed.
29143024 pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
29153025 cx.windows
29163026 .get(self.id)
@@ -2946,6 +3056,7 @@ impl<V: 'static> From<WindowHandle<V>> for AnyWindowHandle { · 29 unmodified lines
29463056 }
29473057 }
29483058
3059+/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type.
29493060 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
29503061 pub struct AnyWindowHandle {
29513062 pub(crate) id: WindowId,
@@ -2953,10 +3064,13 @@ pub struct AnyWindowHandle { · 1 unmodified line
29533064 }
29543065
29553066 impl AnyWindowHandle {
3067+ /// Get the ID of this window.
29563068 pub fn window_id(&self) -> WindowId {
29573069 self.id
29583070 }
29593071
3072+ /// Attempt to convert this handle to a window handle with a specific root view type.
3073+ /// If the types do not match, this will return `None`.
29603074 pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {
29613075 if TypeId::of::<T>() == self.state_type {
29623076 Some(WindowHandle {
@@ -2968,6 +3082,9 @@ impl AnyWindowHandle { · 5 unmodified lines
29683082 }
29693083 }
29703084
3085+ /// Update the state of the root view of this window.
3086+ ///
3087+ /// This will fail if the window has been closed.
29713088 pub fn update<C, R>(
29723089 self,
29733090 cx: &mut C,
@@ -2979,6 +3096,9 @@ impl AnyWindowHandle { · 5 unmodified lines
29793096 cx.update_window(self, update)
29803097 }
29813098
3099+ /// Read the state of the root view of this window.
3100+ ///
3101+ /// This will fail if the window has been closed.
29823102 pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
29833103 where
29843104 C: Context,
@@ -2999,12 +3119,21 @@ impl AnyWindowHandle { · 14 unmodified lines
29993119 // }
30003120 // }
30013121
3122+/// An identifier for an [`Element`](crate::Element).
3123+///
3124+/// Can be constructed with a string, a number, or both, as well
3125+/// as other internal representations.
30023126 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
30033127 pub enum ElementId {
3128+ /// The ID of a View element
30043129 View(EntityId),
3130+ /// An integer ID.
30053131 Integer(usize),
3132+ /// A string based ID.
30063133 Name(SharedString),
3134+ /// An ID that's equated with a focus handle.
30073135 FocusHandle(FocusId),
3136+ /// A combination of a name and an integer.
30083137 NamedInteger(SharedString, usize),
30093138 }
30103139
@@ -3074,7 +3203,8 @@ impl From<(&'static str, u64)> for ElementId { · 63 unmodified lines
30743203 }
30753204 }
30763205
3077−/// A rectangle, to be rendered on the screen by GPUI at the given position and size.
3206+/// A rectangle to be rendered in the window at the given position and size.
3207+/// Passed as an argument [`WindowContext::paint_quad`].
30783208 #[derive(Clone)]
30793209 pub struct PaintQuad {
30803210 bounds: Bounds<Pixels>,
crates/gpui/tests/action_macros.rs+6−6
@@ -18,33 +18,33 @@ fn test_action_macros() { · 17 unmodified lines
1818
1919 impl gpui::Action for RegisterableAction {
2020 fn boxed_clone(&self) -> Box<dyn gpui::Action> {
21− todo!()
21+ unimplemented!()
2222 }
2323
2424 fn as_any(&self) -> &dyn std::any::Any {
25− todo!()
25+ unimplemented!()
2626 }
2727
2828 fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
29− todo!()
29+ unimplemented!()
3030 }
3131
3232 fn name(&self) -> &str {
33− todo!()
33+ unimplemented!()
3434 }
3535
3636 fn debug_name() -> &'static str
3737 where
3838 Self: Sized,
3939 {
40− todo!()
40+ unimplemented!()
4141 }
4242
4343 fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>>
4444 where
4545 Self: Sized,
4646 {
47− todo!()
47+ unimplemented!()
4848 }
4949 }
5050 }
crates/gpui_macros/src/gpui_macros.rs+30−0
@@ -7,26 +7,56 @@ mod test; · 6 unmodified lines
77 use proc_macro::TokenStream;
88
99 #[proc_macro]
10+/// register_action! can be used to register an action with the GPUI runtime.
11+/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead,
12+/// but this can be used for fine grained customization.
1013 pub fn register_action(ident: TokenStream) -> TokenStream {
1114 register_action::register_action_macro(ident)
1215 }
1316
1417 #[proc_macro_derive(IntoElement)]
18+// #[derive(IntoElement)] is used to create a Component out of anything that implements
19+// the `RenderOnce` trait.
1520 pub fn derive_into_element(input: TokenStream) -> TokenStream {
1621 derive_into_element::derive_into_element(input)
1722 }
1823
1924 #[proc_macro_derive(Render)]
25+#[doc(hidden)]
2026 pub fn derive_render(input: TokenStream) -> TokenStream {
2127 derive_render::derive_render(input)
2228 }
2329
30+// Used by gpui to generate the style helpers.
2431 #[proc_macro]
32+#[doc(hidden)]
2533 pub fn style_helpers(input: TokenStream) -> TokenStream {
2634 style_helpers::style_helpers(input)
2735 }
2836
2937 #[proc_macro_attribute]
38+/// #[gpui::test] can be used to annotate test functions that run with GPUI support.
39+/// it supports both synchronous and asynchronous tests, and can provide you with
40+/// as many `TestAppContext` instances as you need.
41+/// The output contains a `#[test]` annotation so this can be used with any existing
42+/// test harness (`cargo test` or `cargo-nextest`).
43+///
44+/// ```
45+/// #[gpui::test]
46+/// async fn test_foo(mut cx: &TestAppContext) { }
47+/// ```
48+///
49+/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance.
50+/// this will be seeded with the `SEED` environment variable and is used internally by
51+/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests.
52+/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide
53+/// variety of scenarios and interleavings just by changing the seed.
54+///
55+/// #[gpui::test] also takes three different arguments:
56+/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED.
57+/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass.
58+/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the
59+/// tests fail so that you can write out more detail about the failure.
3060 pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
3161 test::test(args, function)
3262 }
crates/language/src/syntax_map/syntax_map_tests.rs+14−14
@@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { · 257 unmodified lines
258258 let (buffer, syntax_map) = test_edit_sequence(
259259 "Rust",
260260 &[
261− "fn a() { dbg }",
262− "fn a() { dbg«!» }",
263− "fn a() { dbg!«()» }",
264− "fn a() { dbg!(«b») }",
265− "fn a() { dbg!(b«.») }",
266− "fn a() { dbg!(b.«c») }",
267− "fn a() { dbg!(b.c«()») }",
268− "fn a() { dbg!(b.c(«vec»)) }",
269− "fn a() { dbg!(b.c(vec«!»)) }",
270− "fn a() { dbg!(b.c(vec!«[]»)) }",
271− "fn a() { dbg!(b.c(vec![«d»])) }",
272− "fn a() { dbg!(b.c(vec![d«.»])) }",
273− "fn a() { dbg!(b.c(vec![d.«e»])) }",
261+ "fn a() { test_macro }",
262+ "fn a() { test_macro«!» }",
263+ "fn a() { test_macro!«()» }",
264+ "fn a() { test_macro!(«b») }",
265+ "fn a() { test_macro!(b«.») }",
266+ "fn a() { test_macro!(b.«c») }",
267+ "fn a() { test_macro!(b.c«()») }",
268+ "fn a() { test_macro!(b.c(«vec»)) }",
269+ "fn a() { test_macro!(b.c(vec«!»)) }",
270+ "fn a() { test_macro!(b.c(vec!«[]»)) }",
271+ "fn a() { test_macro!(b.c(vec![«d»])) }",
272+ "fn a() { test_macro!(b.c(vec![d«.»])) }",
273+ "fn a() { test_macro!(b.c(vec![d.«e»])) }",
274274 ],
275275 );
276276
@@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { · 1 unmodified line
278278 &syntax_map,
279279 &buffer,
280280 &["field"],
281− "fn a() { dbg!(b.«c»(vec![d.«e»])) }",
281+ "fn a() { test_macro!(b.«c»(vec![d.«e»])) }",
282282 );
283283 }
284284
crates/project_panel/src/project_panel.rs+8−5
@@ -30,7 +30,7 @@ use std::{ · 29 unmodified lines
3030 sync::Arc,
3131 };
3232 use theme::ThemeSettings;
33−use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem};
33+use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem};
3434 use unicase::UniCase;
3535 use util::{maybe, ResultExt, TryFutureExt};
3636 use workspace::{
@@ -1403,7 +1403,7 @@ impl ProjectPanel { · 1366 unmodified lines
14031403 .indent_step_size(px(settings.indent_size))
14041404 .selected(is_selected)
14051405 .child(if let Some(icon) = &icon {
1406− div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
1406+ div().child(Icon::from_path(icon.to_string()).color(Color::Muted))
14071407 } else {
14081408 div().size(IconSize::default().rems()).invisible()
14091409 })
@@ -1433,6 +1433,9 @@ impl ProjectPanel { · 23 unmodified lines
14331433 }))
14341434 .on_secondary_mouse_down(cx.listener(
14351435 move |this, event: &MouseDownEvent, cx| {
1436+ // Stop propagation to prevent the catch-all context menu for the project
1437+ // panel from being deployed.
1438+ cx.stop_propagation();
14361439 this.deploy_context_menu(event.position, entry_id, cx);
14371440 },
14381441 )),
@@ -1587,7 +1590,7 @@ impl Render for DraggedProjectEntryView { · 148 unmodified lines
15871590 .indent_level(self.details.depth)
15881591 .indent_step_size(px(settings.indent_size))
15891592 .child(if let Some(icon) = &self.details.icon {
1590− div().child(IconElement::from_path(icon.to_string()))
1593+ div().child(Icon::from_path(icon.to_string()))
15911594 } else {
15921595 div()
15931596 })
@@ -1637,8 +1640,8 @@ impl Panel for ProjectPanel { · 43 unmodified lines
16371640 cx.notify();
16381641 }
16391642
1640− fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
1641− Some(ui::Icon::FileTree)
1643+ fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
1644+ Some(ui::IconName::FileTree)
16421645 }
16431646
16441647 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
crates/quick_action_bar/src/quick_action_bar.rs+6−6
@@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines
66 Subscription, View, ViewContext, WeakView,
77 };
88 use search::{buffer_search, BufferSearchBar};
9−use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
9+use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
1010 use workspace::{
1111 item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
1212 };
@@ -43,7 +43,7 @@ impl Render for QuickActionBar { · 30 unmodified lines
4343
4444 let inlay_hints_button = Some(QuickActionBarButton::new(
4545 "toggle inlay hints",
46− Icon::InlayHint,
46+ IconName::InlayHint,
4747 editor.read(cx).inlay_hints_enabled(),
4848 Box::new(editor::ToggleInlayHints),
4949 "Toggle Inlay Hints",
@@ -60,7 +60,7 @@ impl Render for QuickActionBar { · 10 unmodified lines
6060
6161 let search_button = Some(QuickActionBarButton::new(
6262 "toggle buffer search",
63− Icon::MagnifyingGlass,
63+ IconName::MagnifyingGlass,
6464 !self.buffer_search_bar.read(cx).is_dismissed(),
6565 Box::new(buffer_search::Deploy { focus: false }),
6666 "Buffer Search",
@@ -77,7 +77,7 @@ impl Render for QuickActionBar { · 10 unmodified lines
7777
7878 let assistant_button = QuickActionBarButton::new(
7979 "toggle inline assistant",
80− Icon::MagicWand,
80+ IconName::MagicWand,
8181 false,
8282 Box::new(InlineAssist),
8383 "Inline Assist",
@@ -107,7 +107,7 @@ impl EventEmitter<ToolbarItemEvent> for QuickActionBar {} · 23 unmodified lines
107107 #[derive(IntoElement)]
108108 struct QuickActionBarButton {
109109 id: ElementId,
110− icon: Icon,
110+ icon: IconName,
111111 toggled: bool,
112112 action: Box<dyn Action>,
113113 tooltip: SharedString,
@@ -117,7 +117,7 @@ struct QuickActionBarButton { · 3 unmodified lines
117117 impl QuickActionBarButton {
118118 fn new(
119119 id: impl Into<ElementId>,
120− icon: Icon,
120+ icon: IconName,
121121 toggled: bool,
122122 action: Box<dyn Action>,
123123 tooltip: impl Into<SharedString>,
crates/rope/src/rope.rs+3−3
@@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; · 24 unmodified lines
2525 #[cfg(not(test))]
2626 const CHUNK_BASE: usize = 16;
2727
28−/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances
28+/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances
2929 /// containing the same text will produce the same fingerprint. This hash function is special in that
30−/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting
31−/// hash being equivalent to hashing all the text contained in the [Rope] at once.
30+/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting
31+/// hash being equivalent to hashing all the text contained in the [`Rope`] at once.
3232 pub type RopeFingerprint = HashMatrix;
3333
3434 #[derive(Clone, Default)]
crates/rpc/src/notification.rs+30−25
@@ -76,30 +76,35 @@ impl Notification { · 75 unmodified lines
7676 }
7777 }
7878
79−#[test]
80−fn test_notification() {
81− // Notifications can be serialized and deserialized.
82− for notification in [
83− Notification::ContactRequest { sender_id: 1 },
84− Notification::ContactRequestAccepted { responder_id: 2 },
85− Notification::ChannelInvitation {
86− channel_id: 100,
87− channel_name: "the-channel".into(),
88− inviter_id: 50,
89− },
90− Notification::ChannelMessageMention {
91− sender_id: 200,
92− channel_id: 30,
93− message_id: 1,
94− },
95− ] {
96− let message = notification.to_proto();
97− let deserialized = Notification::from_proto(&message).unwrap();
98− assert_eq!(deserialized, notification);
99− }
79+#[cfg(test)]
80+mod tests {
81+ use crate::Notification;
82+
83+ #[test]
84+ fn test_notification() {
85+ // Notifications can be serialized and deserialized.
86+ for notification in [
87+ Notification::ContactRequest { sender_id: 1 },
88+ Notification::ContactRequestAccepted { responder_id: 2 },
89+ Notification::ChannelInvitation {
90+ channel_id: 100,
91+ channel_name: "the-channel".into(),
92+ inviter_id: 50,
93+ },
94+ Notification::ChannelMessageMention {
95+ sender_id: 200,
96+ channel_id: 30,
97+ message_id: 1,
98+ },
99+ ] {
100+ let message = notification.to_proto();
101+ let deserialized = Notification::from_proto(&message).unwrap();
102+ assert_eq!(deserialized, notification);
103+ }
100104
101− // When notifications are serialized, the `kind` and `actor_id` fields are
102− // stored separately, and do not appear redundantly in the JSON.
103− let notification = Notification::ContactRequest { sender_id: 1 };
104− assert_eq!(notification.to_proto().content, "{}");
105+ // When notifications are serialized, the `kind` and `actor_id` fields are
106+ // stored separately, and do not appear redundantly in the JSON.
107+ let notification = Notification::ContactRequest { sender_id: 1 };
108+ assert_eq!(notification.to_proto().content, "{}");
109+ }
105110 }
crates/search/src/buffer_search.rs+17−12
@@ -21,7 +21,7 @@ use settings::Settings; · 20 unmodified lines
2121 use std::{any::Any, sync::Arc};
2222 use theme::ThemeSettings;
2323
24−use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip};
24+use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip};
2525 use util::ResultExt;
2626 use workspace::{
2727 item::ItemHandle,
@@ -43,7 +43,7 @@ pub enum Event { · 15 unmodified lines
4343 }
4444
4545 pub fn init(cx: &mut AppContext) {
46− cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor))
46+ cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
4747 .detach();
4848 }
4949
@@ -225,7 +225,7 @@ impl Render for BufferSearchBar { · 175 unmodified lines
225225 .border_color(editor_border)
226226 .min_w(rems(384. / 16.))
227227 .rounded_lg()
228− .child(IconElement::new(Icon::MagnifyingGlass))
228+ .child(Icon::new(IconName::MagnifyingGlass))
229229 .child(self.render_text_input(&self.query_editor, cx))
230230 .children(supported_options.case.then(|| {
231231 self.render_search_option_button(
@@ -287,7 +287,7 @@ impl Render for BufferSearchBar { · 55 unmodified lines
287287 this.child(
288288 IconButton::new(
289289 "buffer-search-bar-toggle-replace-button",
290− Icon::Replace,
290+ IconName::Replace,
291291 )
292292 .style(ButtonStyle::Subtle)
293293 .when(self.replace_enabled, |button| {
@@ -323,7 +323,7 @@ impl Render for BufferSearchBar { · 29 unmodified lines
323323 )
324324 .when(should_show_replace_input, |this| {
325325 this.child(
326− IconButton::new("search-replace-next", ui::Icon::ReplaceNext)
326+ IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
327327 .tooltip(move |cx| {
328328 Tooltip::for_action("Replace next", &ReplaceNext, cx)
329329 })
@@ -332,7 +332,7 @@ impl Render for BufferSearchBar { · 2 unmodified lines
332332 })),
333333 )
334334 .child(
335− IconButton::new("search-replace-all", ui::Icon::ReplaceAll)
335+ IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
336336 .tooltip(move |cx| {
337337 Tooltip::for_action("Replace all", &ReplaceAll, cx)
338338 })
@@ -350,7 +350,7 @@ impl Render for BufferSearchBar { · 11 unmodified lines
350350 .gap_0p5()
351351 .flex_none()
352352 .child(
353− IconButton::new("select-all", ui::Icon::SelectAll)
353+ IconButton::new("select-all", ui::IconName::SelectAll)
354354 .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
355355 .tooltip(|cx| {
356356 Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
@@ -358,13 +358,13 @@ impl Render for BufferSearchBar { · 1 unmodified line
358358 )
359359 .children(match_count)
360360 .child(render_nav_button(
361− ui::Icon::ChevronLeft,
361+ ui::IconName::ChevronLeft,
362362 self.active_match_index.is_some(),
363363 "Select previous match",
364364 &SelectPrevMatch,
365365 ))
366366 .child(render_nav_button(
367− ui::Icon::ChevronRight,
367+ ui::IconName::ChevronRight,
368368 self.active_match_index.is_some(),
369369 "Select next match",
370370 &SelectNextMatch,
@@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { · 108 unmodified lines
479479 callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
480480 ) {
481481 self.register_action(move |workspace, action: &A, cx| {
482+ if workspace.has_active_modal(cx) {
483+ cx.propagate();
484+ return;
485+ }
486+
482487 let pane = workspace.active_pane();
483488 pane.update(cx, move |this, cx| {
484489 this.toolbar().update(cx, move |this, cx| {
@@ -539,11 +544,11 @@ impl BufferSearchBar { · 54 unmodified lines
539544 this.select_all_matches(action, cx);
540545 });
541546 registrar.register_handler(|this, _: &editor::Cancel, cx| {
542− if !this.dismissed {
547+ if this.dismissed {
548+ cx.propagate();
549+ } else {
543550 this.dismiss(&Dismiss, cx);
544− return;
545551 }
546− cx.propagate();
547552 });
548553 registrar.register_handler(|this, deploy, cx| {
549554 this.deploy(deploy, cx);
crates/search/src/project_search.rs+14−13
@@ -38,7 +38,7 @@ use std::{ · 37 unmodified lines
3838 use theme::ThemeSettings;
3939
4040 use ui::{
41− h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize,
41+ h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
4242 Selectable, ToggleButton, Tooltip,
4343 };
4444 use util::{paths::PathMatcher, ResultExt as _};
@@ -424,7 +424,8 @@ impl Item for ProjectSearchView { · 379 unmodified lines
424424 .current()
425425 .as_ref()
426426 .map(|query| {
427− let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
427+ let query = query.replace('\n', "");
428+ let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN);
428429 query_text.into()
429430 });
430431 let tab_name = last_query
@@ -432,7 +433,7 @@ impl Item for ProjectSearchView { · 1 unmodified line
432433 .unwrap_or_else(|| "Project search".into());
433434 h_stack()
434435 .gap_2()
435− .child(IconElement::new(Icon::MagnifyingGlass).color(if selected {
436+ .child(Icon::new(IconName::MagnifyingGlass).color(if selected {
436437 Color::Default
437438 } else {
438439 Color::Muted
@@ -1616,12 +1617,12 @@ impl Render for ProjectSearchBar { · 1177 unmodified lines
16161617 .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
16171618 .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))
16181619 .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx)))
1619− .child(IconElement::new(Icon::MagnifyingGlass))
1620+ .child(Icon::new(IconName::MagnifyingGlass))
16201621 .child(self.render_text_input(&search.query_editor, cx))
16211622 .child(
16221623 h_stack()
16231624 .child(
1624− IconButton::new("project-search-filter-button", Icon::Filter)
1625+ IconButton::new("project-search-filter-button", IconName::Filter)
16251626 .tooltip(|cx| {
16261627 Tooltip::for_action("Toggle filters", &ToggleFilters, cx)
16271628 })
@@ -1639,7 +1640,7 @@ impl Render for ProjectSearchBar { · 11 unmodified lines
16391640 this.child(
16401641 IconButton::new(
16411642 "project-search-case-sensitive",
1642− Icon::CaseSensitive,
1643+ IconName::CaseSensitive,
16431644 )
16441645 .tooltip(|cx| {
16451646 Tooltip::for_action(
@@ -1659,7 +1660,7 @@ impl Render for ProjectSearchBar { · 13 unmodified lines
16591660 )),
16601661 )
16611662 .child(
1662− IconButton::new("project-search-whole-word", Icon::WholeWord)
1663+ IconButton::new("project-search-whole-word", IconName::WholeWord)
16631664 .tooltip(|cx| {
16641665 Tooltip::for_action(
16651666 "Toggle whole word",
@@ -1738,7 +1739,7 @@ impl Render for ProjectSearchBar { · 72 unmodified lines
17381739 }),
17391740 )
17401741 .child(
1741− IconButton::new("project-search-toggle-replace", Icon::Replace)
1742+ IconButton::new("project-search-toggle-replace", IconName::Replace)
17421743 .on_click(cx.listener(|this, _, cx| {
17431744 this.toggle_replace(&ToggleReplace, cx);
17441745 }))
@@ -1755,7 +1756,7 @@ impl Render for ProjectSearchBar { · 10 unmodified lines
17551756 .border_1()
17561757 .border_color(cx.theme().colors().border)
17571758 .rounded_lg()
1758− .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small))
1759+ .child(Icon::new(IconName::Replace).size(ui::IconSize::Small))
17591760 .child(self.render_text_input(&search.replacement_editor, cx))
17601761 } else {
17611762 // Fill out the space if we don't have a replacement editor.
@@ -1764,7 +1765,7 @@ impl Render for ProjectSearchBar { · 2 unmodified lines
17641765 let actions_column = h_stack()
17651766 .when(search.replace_enabled, |this| {
17661767 this.child(
1767− IconButton::new("project-search-replace-next", Icon::ReplaceNext)
1768+ IconButton::new("project-search-replace-next", IconName::ReplaceNext)
17681769 .on_click(cx.listener(|this, _, cx| {
17691770 if let Some(search) = this.active_project_search.as_ref() {
17701771 search.update(cx, |this, cx| {
@@ -1775,7 +1776,7 @@ impl Render for ProjectSearchBar { · 4 unmodified lines
17751776 .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
17761777 )
17771778 .child(
1778− IconButton::new("project-search-replace-all", Icon::ReplaceAll)
1779+ IconButton::new("project-search-replace-all", IconName::ReplaceAll)
17791780 .on_click(cx.listener(|this, _, cx| {
17801781 if let Some(search) = this.active_project_search.as_ref() {
17811782 search.update(cx, |this, cx| {
@@ -1796,7 +1797,7 @@ impl Render for ProjectSearchBar { · 14 unmodified lines
17961797 this
17971798 })
17981799 .child(
1799− IconButton::new("project-search-prev-match", Icon::ChevronLeft)
1800+ IconButton::new("project-search-prev-match", IconName::ChevronLeft)
18001801 .disabled(search.active_match_index.is_none())
18011802 .on_click(cx.listener(|this, _, cx| {
18021803 if let Some(search) = this.active_project_search.as_ref() {
@@ -1810,7 +1811,7 @@ impl Render for ProjectSearchBar { · 7 unmodified lines
18101811 }),
18111812 )
18121813 .child(
1813− IconButton::new("project-search-next-match", Icon::ChevronRight)
1814+ IconButton::new("project-search-next-match", IconName::ChevronRight)
18141815 .disabled(search.active_match_index.is_none())
18151816 .on_click(cx.listener(|this, _, cx| {
18161817 if let Some(search) = this.active_project_search.as_ref() {
crates/search/src/search.rs+4−4
@@ -60,11 +60,11 @@ impl SearchOptions { · 59 unmodified lines
6060 }
6161 }
6262
63− pub fn icon(&self) -> ui::Icon {
63+ pub fn icon(&self) -> ui::IconName {
6464 match *self {
65− SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
66− SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
67− SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,
65+ SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
66+ SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
67+ SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit,
6868 _ => panic!("{:?} is not a named SearchOption", self),
6969 }
7070 }
crates/search/src/search_bar.rs+1−1
@@ -3,7 +3,7 @@ use ui::IconButton; · 2 unmodified lines
33 use ui::{prelude::*, Tooltip};
44
55 pub(super) fn render_nav_button(
6− icon: ui::Icon,
6+ icon: ui::IconName,
77 active: bool,
88 tooltip: &'static str,
99 action: &'static dyn Action,
crates/storybook/Cargo.toml+1−0
@@ -14,6 +14,7 @@ anyhow.workspace = true · 13 unmodified lines
1414 backtrace-on-stack-overflow = "0.3.0"
1515 chrono = "0.4"
1616 clap = { version = "4.4", features = ["derive", "string"] }
17+collab_ui = { path = "../collab_ui", features = ["stories"] }
1718 strum = { version = "0.25.0", features = ["derive"] }
1819 dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
1920 editor = { path = "../editor" }
crates/storybook/src/story_selector.rs+4−0
@@ -16,6 +16,7 @@ pub enum ComponentStory { · 15 unmodified lines
1616 Avatar,
1717 Button,
1818 Checkbox,
19+ CollabNotification,
1920 ContextMenu,
2021 Cursor,
2122 Disclosure,
@@ -45,6 +46,9 @@ impl ComponentStory { · 23 unmodified lines
4546 Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
4647 Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
4748 Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
49+ Self::CollabNotification => cx
50+ .new_view(|_| collab_ui::notifications::CollabNotificationStory)
51+ .into(),
4852 Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
4953 Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
5054 Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),
crates/terminal_view/src/terminal_element.rs+12−0
@@ -451,6 +451,18 @@ impl TerminalElement { · 450 unmodified lines
451451 }
452452 });
453453
454+ let interactive_text_bounds = InteractiveBounds {
455+ bounds,
456+ stacking_order: cx.stacking_order().clone(),
457+ };
458+ if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
459+ if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
460+ cx.set_cursor_style(gpui::CursorStyle::PointingHand)
461+ } else {
462+ cx.set_cursor_style(gpui::CursorStyle::IBeam)
463+ }
464+ }
465+
454466 let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
455467 div()
456468 .size_full()
crates/terminal_view/src/terminal_panel.rs+6−6
@@ -19,7 +19,7 @@ use workspace::{ · 18 unmodified lines
1919 dock::{DockPosition, Panel, PanelEvent},
2020 item::Item,
2121 pane,
22− ui::Icon,
22+ ui::IconName,
2323 DraggedTab, Pane, Workspace,
2424 };
2525
@@ -71,7 +71,7 @@ impl TerminalPanel { · 45 unmodified lines
7171 h_stack()
7272 .gap_2()
7373 .child(
74− IconButton::new("plus", Icon::Plus)
74+ IconButton::new("plus", IconName::Plus)
7575 .icon_size(IconSize::Small)
7676 .on_click(move |_, cx| {
7777 terminal_panel
@@ -82,10 +82,10 @@ impl TerminalPanel { · 4 unmodified lines
8282 )
8383 .child({
8484 let zoomed = pane.is_zoomed();
85− IconButton::new("toggle_zoom", Icon::Maximize)
85+ IconButton::new("toggle_zoom", IconName::Maximize)
8686 .icon_size(IconSize::Small)
8787 .selected(zoomed)
88− .selected_icon(Icon::Minimize)
88+ .selected_icon(IconName::Minimize)
8989 .on_click(cx.listener(|pane, _, cx| {
9090 pane.toggle_zoom(&workspace::ToggleZoom, cx);
9191 }))
@@ -477,8 +477,8 @@ impl Panel for TerminalPanel { · 385 unmodified lines
477477 "TerminalPanel"
478478 }
479479
480− fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
481− Some(Icon::Terminal)
480+ fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
481+ Some(IconName::Terminal)
482482 }
483483
484484 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
crates/terminal_view/src/terminal_view.rs+2−2
@@ -20,7 +20,7 @@ use terminal::{ · 19 unmodified lines
2020 Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
2121 };
2222 use terminal_element::TerminalElement;
23−use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
23+use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label};
2424 use util::{paths::PathLikeWithPosition, ResultExt};
2525 use workspace::{
2626 item::{BreadcrumbText, Item, ItemEvent},
@@ -690,7 +690,7 @@ impl Item for TerminalView { · 663 unmodified lines
690690 let title = self.terminal().read(cx).title(true);
691691 h_stack()
692692 .gap_2()
693− .child(IconElement::new(Icon::Terminal))
693+ .child(Icon::new(IconName::Terminal))
694694 .child(Label::new(title).color(if selected {
695695 Color::Default
696696 } else {
crates/text/src/anchor.rs+1−1
@@ -90,7 +90,7 @@ impl Anchor { · 89 unmodified lines
9090 content.summary_for_anchor(self)
9191 }
9292
93− /// Returns true when the [Anchor] is located inside a visible fragment.
93+ /// Returns true when the [`Anchor`] is located inside a visible fragment.
9494 pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
9595 if *self == Anchor::MIN || *self == Anchor::MAX {
9696 true
crates/theme/src/scale.rs+8−1
@@ -2,7 +2,9 @@ use gpui::{AppContext, Hsla, SharedString}; · 1 unmodified line
22
33 use crate::{ActiveTheme, Appearance};
44
5−/// A one-based step in a [`ColorScale`].
5+/// A collection of colors that are used to style the UI.
6+///
7+/// Each step has a semantic meaning, and is used to style different parts of the UI.
68 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
79 pub struct ColorScaleStep(usize);
810
@@ -37,6 +39,10 @@ impl ColorScaleStep { · 28 unmodified lines
3739 ];
3840 }
3941
42+/// A scale of colors for a given [`ColorScaleSet`].
43+///
44+/// Each [`ColorScale`] contains exactly 12 colors. Refer to
45+/// [`ColorScaleStep`] for a reference of what each step is used for.
4046 pub struct ColorScale(Vec<Hsla>);
4147
4248 impl FromIterator<Hsla> for ColorScale {
@@ -229,6 +235,7 @@ impl IntoIterator for ColorScales { · 186 unmodified lines
229235 }
230236 }
231237
238+/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
232239 pub struct ColorScaleSet {
233240 name: SharedString,
234241 light: ColorScale,
crates/theme/src/settings.rs+1−1
@@ -27,7 +27,7 @@ pub struct ThemeSettings { · 26 unmodified lines
2727 }
2828
2929 #[derive(Default)]
30−pub struct AdjustedBufferFontSize(Pixels);
30+pub(crate) struct AdjustedBufferFontSize(Pixels);
3131
3232 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
3333 pub struct ThemeSettingsContent {
crates/theme/src/styles/colors.rs+4−1
@@ -2,11 +2,12 @@ use gpui::Hsla; · 1 unmodified line
22 use refineable::Refineable;
33 use std::sync::Arc;
44
5−use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
5+use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors};
66
77 #[derive(Refineable, Clone, Debug)]
88 #[refineable(Debug, serde::Deserialize)]
99 pub struct ThemeColors {
10+ /// Border color. Used for most borders, is usually a high contrast color.
1011 pub border: Hsla,
1112 /// Border color. Used for deemphasized borders, like a visual divider between two sections
1213 pub border_variant: Hsla,
@@ -219,6 +220,8 @@ pub struct ThemeStyles { · 206 unmodified lines
219220
220221 #[refineable]
221222 pub colors: ThemeColors,
223+
224+ #[refineable]
222225 pub status: StatusColors,
223226 pub player: PlayerColors,
224227 pub syntax: Arc<SyntaxTheme>,
crates/theme/src/styles/status.rs+0−9
@@ -78,15 +78,6 @@ pub struct StatusColors { · 77 unmodified lines
7878 pub warning_border: Hsla,
7979 }
8080
81−impl Default for StatusColors {
82− /// Don't use this!
83− /// We have to have a default to be `[refineable::Refinable]`.
84− /// todo!("Find a way to not need this for Refinable")
85− fn default() -> Self {
86− Self::dark()
87− }
88−}
89−
9081 pub struct DiagnosticColors {
9182 pub error: Hsla,
9283 pub warning: Hsla,
crates/theme/src/theme.rs+8−0
@@ -1,3 +1,11 @@
1+//! # Theme
2+//!
3+//! This crate provides the theme system for Zed.
4+//!
5+//! ## Overview
6+//!
7+//! A theme is a collection of colors used to build a consistent appearance for UI components across the application.
8+
19 mod default_colors;
210 mod default_theme;
311 mod one_themes;
crates/theme/theme.md+15−0
@@ -0,0 +1,15 @@
1+ # Theme
2+
3+ This crate provides the theme system for Zed.
4+
5+ ## Overview
6+
7+ A theme is a collection of colors used to build a consistent appearance for UI components across the application.
8+ To produce a theme in Zed,
9+
10+ A theme is made of of two parts: A [ThemeFamily] and one or more [Theme]s.
11+
12+//
13+ A [ThemeFamily] contains metadata like theme name, author, and theme-specific [ColorScales] as well as a series of themes.
14+
15+ - [ThemeColors] - A set of colors that are used to style the UI. Refer to the [ThemeColors] documentation for more information.
crates/ui/src/clickable.rs+1−1
@@ -1,6 +1,6 @@
11 use gpui::{ClickEvent, WindowContext};
22
3−/// A trait for elements that can be clicked.
3+/// A trait for elements that can be clicked. Enables the use of the `on_click` method.
44 pub trait Clickable {
55 /// Sets the click handler that will fire whenever the element is clicked.
66 fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;
crates/ui/src/components/avatar.rs+35−5
@@ -1,13 +1,26 @@
11 use crate::prelude::*;
22 use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};
33
4+/// The shape of an [`Avatar`].
45 #[derive(Debug, Default, PartialEq, Clone)]
5−pub enum Shape {
6+pub enum AvatarShape {
7+ /// The avatar is shown in a circle.
68 #[default]
79 Circle,
10+ /// The avatar is shown in a rectangle with rounded corners.
811 RoundedRectangle,
912 }
1013
14+/// An element that renders a user avatar with customizable appearance options.
15+///
16+/// # Examples
17+///
18+/// ```
19+/// Avatar::new("path/to/image.png")
20+/// .shape(AvatarShape::Circle)
21+/// .grayscale(true)
22+/// .border_color(cx.theme().colors().border)
23+/// ```
1124 #[derive(IntoElement)]
1225 pub struct Avatar {
1326 image: Img,
@@ -18,7 +31,7 @@ pub struct Avatar { · 4 unmodified lines
1831 impl RenderOnce for Avatar {
1932 fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
2033 if self.image.style().corner_radii.top_left.is_none() {
21− self = self.shape(Shape::Circle);
34+ self = self.shape(AvatarShape::Circle);
2235 }
2336
2437 let size = cx.rem_size();
@@ -66,14 +79,31 @@ impl Avatar { · 41 unmodified lines
6679 }
6780 }
6881
69− pub fn shape(mut self, shape: Shape) -> Self {
82+ /// Sets the shape of the avatar image.
83+ ///
84+ /// This method allows the shape of the avatar to be specified using a [`Shape`].
85+ /// It modifies the corner radius of the image to match the specified shape.
86+ ///
87+ /// # Examples
88+ ///
89+ /// ```
90+ /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle);
91+ /// ```
92+ pub fn shape(mut self, shape: AvatarShape) -> Self {
7093 self.image = match shape {
71− Shape::Circle => self.image.rounded_full(),
72− Shape::RoundedRectangle => self.image.rounded_md(),
94+ AvatarShape::Circle => self.image.rounded_full(),
95+ AvatarShape::RoundedRectangle => self.image.rounded_md(),
7396 };
7497 self
7598 }
7699
100+ /// Applies a grayscale filter to the avatar image.
101+ ///
102+ /// # Examples
103+ ///
104+ /// ```
105+ /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
106+ /// ```
77107 pub fn grayscale(mut self, grayscale: bool) -> Self {
78108 self.image = self.image.grayscale(grayscale);
79109 self
crates/ui/src/components/button/button.rs+175−5
@@ -2,11 +2,69 @@ use gpui::{AnyView, DefiniteLength}; · 1 unmodified line
22
33 use crate::{prelude::*, IconPosition, KeyBinding};
44 use crate::{
5− ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
5+ ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
66 };
77
88 use super::button_icon::ButtonIcon;
99
10+/// An element that creates a button with a label and an optional icon.
11+///
12+/// Common buttons:
13+/// - Label, Icon + Label: [`Button`] (this component)
14+/// - Icon only: [`IconButton`]
15+/// - Custom: [`ButtonLike`]
16+///
17+/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use
18+/// [`ButtonLike`] directly.
19+///
20+/// # Examples
21+///
22+/// **A button with a label**, is typically used in scenarios such as a form, where the button's label
23+/// indicates what action will be performed when the button is clicked.
24+///
25+/// ```
26+/// Button::new("button_id", "Click me!")
27+/// .on_click(|event, cx| {
28+/// // Handle click event
29+/// });
30+/// ```
31+///
32+/// **A toggleable button**, is typically used in scenarios such as a toolbar,
33+/// where the button's state indicates whether a feature is enabled or not, or
34+/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu.
35+///
36+/// ```
37+/// Button::new("button_id", "Click me!")
38+/// .icon(IconName::Check)
39+/// .selected(some_bool)
40+/// .on_click(|event, cx| {
41+/// // Handle click event
42+/// });
43+/// ```
44+///
45+/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method.
46+///
47+/// ```
48+/// Button::new("button_id", "Click me!")
49+/// .selected(some_bool)
50+/// .selected_style(ButtonStyle::Tinted(TintColor::Accent))
51+/// .on_click(|event, cx| {
52+/// // Handle click event
53+/// });
54+/// ```
55+/// This will create a button with a blue tinted background when selected.
56+///
57+/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container.
58+/// The button's content, including text and icons, is centered by default.
59+///
60+/// ```
61+/// let button = Button::new("button_id", "Click me!")
62+/// .full_width()
63+/// .on_click(|event, cx| {
64+/// // Handle click event
65+/// });
66+/// ```
67+///
1068 #[derive(IntoElement)]
1169 pub struct Button {
1270 base: ButtonLike,
@@ -14,15 +72,21 @@ pub struct Button { · 1 unmodified line
1472 label_color: Option<Color>,
1573 label_size: Option<LabelSize>,
1674 selected_label: Option<SharedString>,
17− icon: Option<Icon>,
75+ icon: Option<IconName>,
1876 icon_position: Option<IconPosition>,
1977 icon_size: Option<IconSize>,
2078 icon_color: Option<Color>,
21− selected_icon: Option<Icon>,
79+ selected_icon: Option<IconName>,
2280 key_binding: Option<KeyBinding>,
2381 }
2482
2583 impl Button {
84+ /// Creates a new [`Button`] with a specified identifier and label.
85+ ///
86+ /// This is the primary constructor for a [`Button`] component. It initializes
87+ /// the button with the provided identifier and label text, setting all other
88+ /// properties to their default values, which can be customized using the
89+ /// builder pattern methods provided by this struct.
2690 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
2791 Self {
2892 base: ButtonLike::new(id),
@@ -39,46 +103,55 @@ impl Button { · 10 unmodified lines
39103 }
40104 }
41105
106+ /// Sets the color of the button's label.
42107 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
43108 self.label_color = label_color.into();
44109 self
45110 }
46111
112+ /// Defines the size of the button's label.
47113 pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
48114 self.label_size = label_size.into();
49115 self
50116 }
51117
118+ /// Sets the label used when the button is in a selected state.
52119 pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
53120 self.selected_label = label.into().map(Into::into);
54121 self
55122 }
56123
57− pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
124+ /// Assigns an icon to the button.
125+ pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
58126 self.icon = icon.into();
59127 self
60128 }
61129
130+ /// Sets the position of the icon relative to the label.
62131 pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
63132 self.icon_position = icon_position.into();
64133 self
65134 }
66135
136+ /// Specifies the size of the button's icon.
67137 pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
68138 self.icon_size = icon_size.into();
69139 self
70140 }
71141
142+ /// Sets the color of the button's icon.
72143 pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
73144 self.icon_color = icon_color.into();
74145 self
75146 }
76147
77− pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
148+ /// Chooses an icon to display when the button is in a selected state.
149+ pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
78150 self.selected_icon = icon.into();
79151 self
80152 }
81153
154+ /// Binds a key combination to the button for keyboard shortcuts.
82155 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
83156 self.key_binding = key_binding.into();
84157 self
@@ -86,6 +159,22 @@ impl Button { · 1 unmodified line
86159 }
87160
88161 impl Selectable for Button {
162+ /// Sets the selected state of the button.
163+ ///
164+ /// This method allows the selection state of the button to be specified.
165+ /// It modifies the button's appearance to reflect its selected state.
166+ ///
167+ /// # Examples
168+ ///
169+ /// ```
170+ /// Button::new("button_id", "Click me!")
171+ /// .selected(true)
172+ /// .on_click(|event, cx| {
173+ /// // Handle click event
174+ /// });
175+ /// ```
176+ ///
177+ /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected.
89178 fn selected(mut self, selected: bool) -> Self {
90179 self.base = self.base.selected(selected);
91180 self
@@ -93,6 +182,19 @@ impl Selectable for Button { · 1 unmodified line
93182 }
94183
95184 impl SelectableButton for Button {
185+ /// Sets the style for the button when selected.
186+ ///
187+ /// # Examples
188+ ///
189+ /// ```
190+ /// Button::new("button_id", "Click me!")
191+ /// .selected(true)
192+ /// .selected_style(ButtonStyle::Tinted(TintColor::Accent))
193+ /// .on_click(|event, cx| {
194+ /// // Handle click event
195+ /// });
196+ /// ```
197+ /// This results in a button with a blue tinted background when selected.
96198 fn selected_style(mut self, style: ButtonStyle) -> Self {
97199 self.base = self.base.selected_style(style);
98200 self
@@ -100,6 +202,22 @@ impl SelectableButton for Button { · 1 unmodified line
100202 }
101203
102204 impl Disableable for Button {
205+ /// Disables the button.
206+ ///
207+ /// This method allows the button to be disabled. When a button is disabled,
208+ /// it doesn't react to user interactions and its appearance is updated to reflect this.
209+ ///
210+ /// # Examples
211+ ///
212+ /// ```
213+ /// Button::new("button_id", "Click me!")
214+ /// .disabled(true)
215+ /// .on_click(|event, cx| {
216+ /// // Handle click event
217+ /// });
218+ /// ```
219+ ///
220+ /// This results in a button that is disabled and does not respond to click events.
103221 fn disabled(mut self, disabled: bool) -> Self {
104222 self.base = self.base.disabled(disabled);
105223 self
@@ -107,6 +225,7 @@ impl Disableable for Button { · 1 unmodified line
107225 }
108226
109227 impl Clickable for Button {
228+ /// Sets the click event handler for the button.
110229 fn on_click(
111230 mut self,
112231 handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
@@ -117,11 +236,40 @@ impl Clickable for Button { · 4 unmodified lines
117236 }
118237
119238 impl FixedWidth for Button {
239+ /// Sets a fixed width for the button.
240+ ///
241+ /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
242+ /// Sets a fixed width for the button.
243+ ///
244+ /// # Examples
245+ ///
246+ /// ```
247+ /// Button::new("button_id", "Click me!")
248+ /// .width(DefiniteLength::Pixels(100))
249+ /// .on_click(|event, cx| {
250+ /// // Handle click event
251+ /// });
252+ /// ```
253+ ///
254+ /// This sets the button's width to be exactly 100 pixels.
120255 fn width(mut self, width: DefiniteLength) -> Self {
121256 self.base = self.base.width(width);
122257 self
123258 }
124259
260+ /// Sets the button to occupy the full width of its container.
261+ ///
262+ /// # Examples
263+ ///
264+ /// ```
265+ /// Button::new("button_id", "Click me!")
266+ /// .full_width()
267+ /// .on_click(|event, cx| {
268+ /// // Handle click event
269+ /// });
270+ /// ```
271+ ///
272+ /// This stretches the button to the full width of its container.
125273 fn full_width(mut self) -> Self {
126274 self.base = self.base.full_width();
127275 self
@@ -129,20 +277,42 @@ impl FixedWidth for Button { · 1 unmodified line
129277 }
130278
131279 impl ButtonCommon for Button {
280+ /// Sets the button's id.
132281 fn id(&self) -> &ElementId {
133282 self.base.id()
134283 }
135284
285+ /// Sets the visual style of the button using a [`ButtonStyle`].
136286 fn style(mut self, style: ButtonStyle) -> Self {
137287 self.base = self.base.style(style);
138288 self
139289 }
140290
291+ /// Sets the button's size using a [`ButtonSize`].
141292 fn size(mut self, size: ButtonSize) -> Self {
142293 self.base = self.base.size(size);
143294 self
144295 }
145296
297+ /// Sets a tooltip for the button.
298+ ///
299+ /// This method allows a tooltip to be set for the button. The tooltip is a function that
300+ /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
301+ /// is displayed when the user hovers over the button.
302+ ///
303+ /// # Examples
304+ ///
305+ /// ```
306+ /// Button::new("button_id", "Click me!")
307+ /// .tooltip(|cx| {
308+ /// Text::new("This is a tooltip").into()
309+ /// })
310+ /// .on_click(|event, cx| {
311+ /// // Handle click event
312+ /// });
313+ /// ```
314+ ///
315+ /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
146316 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
147317 self.base = self.base.tooltip(tooltip);
148318 self
crates/ui/src/components/button/button_icon.rs+6−6
@@ -1,4 +1,4 @@
1−use crate::{prelude::*, Icon, IconElement, IconSize};
1+use crate::{prelude::*, Icon, IconName, IconSize};
22
33 /// An icon that appears within a button.
44 ///
@@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; · 1 unmodified line
66 /// or as a standalone icon, like in [`IconButton`](crate::IconButton).
77 #[derive(IntoElement)]
88 pub(super) struct ButtonIcon {
9− icon: Icon,
9+ icon: IconName,
1010 size: IconSize,
1111 color: Color,
1212 disabled: bool,
1313 selected: bool,
14− selected_icon: Option<Icon>,
14+ selected_icon: Option<IconName>,
1515 selected_style: Option<ButtonStyle>,
1616 }
1717
1818 impl ButtonIcon {
19− pub fn new(icon: Icon) -> Self {
19+ pub fn new(icon: IconName) -> Self {
2020 Self {
2121 icon,
2222 size: IconSize::default(),
@@ -44,7 +44,7 @@ impl ButtonIcon { · 21 unmodified lines
4444 self
4545 }
4646
47− pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
47+ pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
4848 self.selected_icon = icon.into();
4949 self
5050 }
@@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { · 37 unmodified lines
8888 self.color
8989 };
9090
91− IconElement::new(icon).size(self.size).color(icon_color)
91+ Icon::new(icon).size(self.size).color(icon_color)
9292 }
9393 }
crates/ui/src/components/button/button_like.rs+6−2
@@ -4,10 +4,12 @@ use smallvec::SmallVec; · 3 unmodified lines
44
55 use crate::prelude::*;
66
7+/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
78 pub trait SelectableButton: Selectable {
89 fn selected_style(self, style: ButtonStyle) -> Self;
910 }
1011
12+/// A common set of traits all buttons must implement.
1113 pub trait ButtonCommon: Clickable + Disableable {
1214 /// A unique element ID to identify the button.
1315 fn id(&self) -> &ElementId;
@@ -93,6 +95,7 @@ impl From<ButtonStyle> for Color { · 79 unmodified lines
9395 }
9496 }
9597
98+/// The visual appearance of a button.
9699 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
97100 pub enum ButtonStyle {
98101 /// A filled button with a solid background color. Provides emphasis versus
@@ -260,8 +263,9 @@ impl ButtonStyle { · 161 unmodified lines
260263 }
261264 }
262265
263−/// ButtonSize can also be used to help build non-button elements
264−/// that are consistently sized with buttons.
266+/// The height of a button.
267+///
268+/// Can also be used to size non-button elements to align with [`Button`]s.
265269 #[derive(Default, PartialEq, Clone, Copy)]
266270 pub enum ButtonSize {
267271 Large,
crates/ui/src/components/button/icon_button.rs+5−5
@@ -1,21 +1,21 @@
11 use gpui::{AnyView, DefiniteLength};
22
33 use crate::{prelude::*, SelectableButton};
4−use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
4+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
55
66 use super::button_icon::ButtonIcon;
77
88 #[derive(IntoElement)]
99 pub struct IconButton {
1010 base: ButtonLike,
11− icon: Icon,
11+ icon: IconName,
1212 icon_size: IconSize,
1313 icon_color: Color,
14− selected_icon: Option<Icon>,
14+ selected_icon: Option<IconName>,
1515 }
1616
1717 impl IconButton {
18− pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
18+ pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
1919 Self {
2020 base: ButtonLike::new(id),
2121 icon,
@@ -35,7 +35,7 @@ impl IconButton { · 13 unmodified lines
3535 self
3636 }
3737
38− pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
38+ pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
3939 self.selected_icon = icon.into();
4040 self
4141 }
crates/ui/src/components/checkbox.rs+3−3
@@ -1,7 +1,7 @@
11 use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
22
33 use crate::prelude::*;
4−use crate::{Color, Icon, IconElement, Selection};
4+use crate::{Color, Icon, IconName, Selection};
55
66 pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
77
@@ -47,7 +47,7 @@ impl RenderOnce for Checkbox { · 39 unmodified lines
4747 let group_id = format!("checkbox_group_{:?}", self.id);
4848
4949 let icon = match self.checked {
50− Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color(
50+ Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
5151 if self.disabled {
5252 Color::Disabled
5353 } else {
@@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { · 1 unmodified line
5555 },
5656 )),
5757 Selection::Indeterminate => Some(
58− IconElement::new(Icon::Dash)
58+ Icon::new(IconName::Dash)
5959 .size(IconSize::Small)
6060 .color(if self.disabled {
6161 Color::Disabled
crates/ui/src/components/context_menu.rs+5−5
@@ -1,6 +1,6 @@
11 use crate::{
2− h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,
3− ListSeparator, ListSubHeader,
2+ h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator,
3+ ListSubHeader,
44 };
55 use gpui::{
66 px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
@@ -14,7 +14,7 @@ enum ContextMenuItem { · 7 unmodified lines
1414 Header(SharedString),
1515 Entry {
1616 label: SharedString,
17− icon: Option<Icon>,
17+ icon: Option<IconName>,
1818 handler: Rc<dyn Fn(&mut WindowContext)>,
1919 action: Option<Box<dyn Action>>,
2020 },
@@ -117,7 +117,7 @@ impl ContextMenu { · 96 unmodified lines
117117 label: label.into(),
118118 action: Some(action.boxed_clone()),
119119 handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
120− icon: Some(Icon::Link),
120+ icon: Some(IconName::Link),
121121 });
122122 self
123123 }
@@ -280,7 +280,7 @@ impl Render for ContextMenu { · 156 unmodified lines
280280 h_stack()
281281 .gap_1()
282282 .child(Label::new(label.clone()))
283− .child(IconElement::new(*icon))
283+ .child(Icon::new(*icon))
284284 .into_any_element()
285285 } else {
286286 Label::new(label.clone()).into_any_element()
crates/ui/src/components/disclosure.rs+3−3
@@ -1,6 +1,6 @@
11 use gpui::ClickEvent;
22
3−use crate::{prelude::*, Color, Icon, IconButton, IconSize};
3+use crate::{prelude::*, Color, IconButton, IconName, IconSize};
44
55 #[derive(IntoElement)]
66 pub struct Disclosure {
@@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { · 25 unmodified lines
3232 IconButton::new(
3333 self.id,
3434 match self.is_open {
35− true => Icon::ChevronDown,
36− false => Icon::ChevronRight,
35+ true => IconName::ChevronDown,
36+ false => IconName::ChevronRight,
3737 },
3838 )
3939 .icon_color(Color::Muted)
crates/ui/src/components/divider.rs+1−0
@@ -7,6 +7,7 @@ enum DividerDirection { · 6 unmodified lines
77 Vertical,
88 }
99
10+/// The color of a [`Divider`].
1011 #[derive(Default)]
1112 pub enum DividerColor {
1213 Border,
crates/ui/src/components/icon.rs+101−101
@@ -22,7 +22,7 @@ impl IconSize { · 21 unmodified lines
2222 }
2323
2424 #[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
25−pub enum Icon {
25+pub enum IconName {
2626 Ai,
2727 ArrowDown,
2828 ArrowLeft,
@@ -111,118 +111,108 @@ pub enum Icon { · 82 unmodified lines
111111 ZedXCopilot,
112112 }
113113
114−impl Icon {
114+impl IconName {
115115 pub fn path(self) -> &'static str {
116116 match self {
117− Icon::Ai => "icons/ai.svg",
118− Icon::ArrowDown => "icons/arrow_down.svg",
119− Icon::ArrowLeft => "icons/arrow_left.svg",
120− Icon::ArrowRight => "icons/arrow_right.svg",
121− Icon::ArrowUp => "icons/arrow_up.svg",
122− Icon::ArrowUpRight => "icons/arrow_up_right.svg",
123− Icon::ArrowCircle => "icons/arrow_circle.svg",
124− Icon::AtSign => "icons/at_sign.svg",
125− Icon::AudioOff => "icons/speaker_off.svg",
126− Icon::AudioOn => "icons/speaker_loud.svg",
127− Icon::Backspace => "icons/backspace.svg",
128− Icon::Bell => "icons/bell.svg",
129− Icon::BellOff => "icons/bell_off.svg",
130− Icon::BellRing => "icons/bell_ring.svg",
131− Icon::Bolt => "icons/bolt.svg",
132− Icon::CaseSensitive => "icons/case_insensitive.svg",
133− Icon::Check => "icons/check.svg",
134− Icon::ChevronDown => "icons/chevron_down.svg",
135− Icon::ChevronLeft => "icons/chevron_left.svg",
136− Icon::ChevronRight => "icons/chevron_right.svg",
137− Icon::ChevronUp => "icons/chevron_up.svg",
138− Icon::Close => "icons/x.svg",
139− Icon::Collab => "icons/user_group_16.svg",
140− Icon::Command => "icons/command.svg",
141− Icon::Control => "icons/control.svg",
142− Icon::Copilot => "icons/copilot.svg",
143− Icon::CopilotDisabled => "icons/copilot_disabled.svg",
144− Icon::CopilotError => "icons/copilot_error.svg",
145− Icon::CopilotInit => "icons/copilot_init.svg",
146− Icon::Copy => "icons/copy.svg",
147− Icon::Dash => "icons/dash.svg",
148− Icon::Delete => "icons/delete.svg",
149− Icon::Disconnected => "icons/disconnected.svg",
150− Icon::Ellipsis => "icons/ellipsis.svg",
151− Icon::Envelope => "icons/feedback.svg",
152− Icon::Escape => "icons/escape.svg",
153− Icon::ExclamationTriangle => "icons/warning.svg",
154− Icon::Exit => "icons/exit.svg",
155− Icon::ExternalLink => "icons/external_link.svg",
156− Icon::File => "icons/file.svg",
157− Icon::FileDoc => "icons/file_icons/book.svg",
158− Icon::FileGeneric => "icons/file_icons/file.svg",
159− Icon::FileGit => "icons/file_icons/git.svg",
160− Icon::FileLock => "icons/file_icons/lock.svg",
161− Icon::FileRust => "icons/file_icons/rust.svg",
162− Icon::FileToml => "icons/file_icons/toml.svg",
163− Icon::FileTree => "icons/project.svg",
164− Icon::Filter => "icons/filter.svg",
165− Icon::Folder => "icons/file_icons/folder.svg",
166− Icon::FolderOpen => "icons/file_icons/folder_open.svg",
167− Icon::FolderX => "icons/stop_sharing.svg",
168− Icon::Github => "icons/github.svg",
169− Icon::Hash => "icons/hash.svg",
170− Icon::InlayHint => "icons/inlay_hint.svg",
171− Icon::Link => "icons/link.svg",
172− Icon::MagicWand => "icons/magic_wand.svg",
173− Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
174− Icon::MailOpen => "icons/mail_open.svg",
175− Icon::Maximize => "icons/maximize.svg",
176− Icon::Menu => "icons/menu.svg",
177− Icon::MessageBubbles => "icons/conversations.svg",
178− Icon::Mic => "icons/mic.svg",
179− Icon::MicMute => "icons/mic_mute.svg",
180− Icon::Minimize => "icons/minimize.svg",
181− Icon::Option => "icons/option.svg",
182− Icon::PageDown => "icons/page_down.svg",
183− Icon::PageUp => "icons/page_up.svg",
184− Icon::Plus => "icons/plus.svg",
185− Icon::Public => "icons/public.svg",
186− Icon::Quote => "icons/quote.svg",
187− Icon::Replace => "icons/replace.svg",
188− Icon::ReplaceAll => "icons/replace_all.svg",
189− Icon::ReplaceNext => "icons/replace_next.svg",
190− Icon::Return => "icons/return.svg",
191− Icon::Screen => "icons/desktop.svg",
192− Icon::SelectAll => "icons/select_all.svg",
193− Icon::Shift => "icons/shift.svg",
194− Icon::Snip => "icons/snip.svg",
195− Icon::Space => "icons/space.svg",
196− Icon::Split => "icons/split.svg",
197− Icon::Tab => "icons/tab.svg",
198− Icon::Terminal => "icons/terminal.svg",
199− Icon::Update => "icons/update.svg",
200− Icon::WholeWord => "icons/word_search.svg",
201− Icon::XCircle => "icons/error.svg",
202− Icon::ZedXCopilot => "icons/zed_x_copilot.svg",
117+ IconName::Ai => "icons/ai.svg",
118+ IconName::ArrowDown => "icons/arrow_down.svg",
119+ IconName::ArrowLeft => "icons/arrow_left.svg",
120+ IconName::ArrowRight => "icons/arrow_right.svg",
121+ IconName::ArrowUp => "icons/arrow_up.svg",
122+ IconName::ArrowUpRight => "icons/arrow_up_right.svg",
123+ IconName::ArrowCircle => "icons/arrow_circle.svg",
124+ IconName::AtSign => "icons/at_sign.svg",
125+ IconName::AudioOff => "icons/speaker_off.svg",
126+ IconName::AudioOn => "icons/speaker_loud.svg",
127+ IconName::Backspace => "icons/backspace.svg",
128+ IconName::Bell => "icons/bell.svg",
129+ IconName::BellOff => "icons/bell_off.svg",
130+ IconName::BellRing => "icons/bell_ring.svg",
131+ IconName::Bolt => "icons/bolt.svg",
132+ IconName::CaseSensitive => "icons/case_insensitive.svg",
133+ IconName::Check => "icons/check.svg",
134+ IconName::ChevronDown => "icons/chevron_down.svg",
135+ IconName::ChevronLeft => "icons/chevron_left.svg",
136+ IconName::ChevronRight => "icons/chevron_right.svg",
137+ IconName::ChevronUp => "icons/chevron_up.svg",
138+ IconName::Close => "icons/x.svg",
139+ IconName::Collab => "icons/user_group_16.svg",
140+ IconName::Command => "icons/command.svg",
141+ IconName::Control => "icons/control.svg",
142+ IconName::Copilot => "icons/copilot.svg",
143+ IconName::CopilotDisabled => "icons/copilot_disabled.svg",
144+ IconName::CopilotError => "icons/copilot_error.svg",
145+ IconName::CopilotInit => "icons/copilot_init.svg",
146+ IconName::Copy => "icons/copy.svg",
147+ IconName::Dash => "icons/dash.svg",
148+ IconName::Delete => "icons/delete.svg",
149+ IconName::Disconnected => "icons/disconnected.svg",
150+ IconName::Ellipsis => "icons/ellipsis.svg",
151+ IconName::Envelope => "icons/feedback.svg",
152+ IconName::Escape => "icons/escape.svg",
153+ IconName::ExclamationTriangle => "icons/warning.svg",
154+ IconName::Exit => "icons/exit.svg",
155+ IconName::ExternalLink => "icons/external_link.svg",
156+ IconName::File => "icons/file.svg",
157+ IconName::FileDoc => "icons/file_icons/book.svg",
158+ IconName::FileGeneric => "icons/file_icons/file.svg",
159+ IconName::FileGit => "icons/file_icons/git.svg",
160+ IconName::FileLock => "icons/file_icons/lock.svg",
161+ IconName::FileRust => "icons/file_icons/rust.svg",
162+ IconName::FileToml => "icons/file_icons/toml.svg",
163+ IconName::FileTree => "icons/project.svg",
164+ IconName::Filter => "icons/filter.svg",
165+ IconName::Folder => "icons/file_icons/folder.svg",
166+ IconName::FolderOpen => "icons/file_icons/folder_open.svg",
167+ IconName::FolderX => "icons/stop_sharing.svg",
168+ IconName::Github => "icons/github.svg",
169+ IconName::Hash => "icons/hash.svg",
170+ IconName::InlayHint => "icons/inlay_hint.svg",
171+ IconName::Link => "icons/link.svg",
172+ IconName::MagicWand => "icons/magic_wand.svg",
173+ IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
174+ IconName::MailOpen => "icons/mail_open.svg",
175+ IconName::Maximize => "icons/maximize.svg",
176+ IconName::Menu => "icons/menu.svg",
177+ IconName::MessageBubbles => "icons/conversations.svg",
178+ IconName::Mic => "icons/mic.svg",
179+ IconName::MicMute => "icons/mic_mute.svg",
180+ IconName::Minimize => "icons/minimize.svg",
181+ IconName::Option => "icons/option.svg",
182+ IconName::PageDown => "icons/page_down.svg",
183+ IconName::PageUp => "icons/page_up.svg",
184+ IconName::Plus => "icons/plus.svg",
185+ IconName::Public => "icons/public.svg",
186+ IconName::Quote => "icons/quote.svg",
187+ IconName::Replace => "icons/replace.svg",
188+ IconName::ReplaceAll => "icons/replace_all.svg",
189+ IconName::ReplaceNext => "icons/replace_next.svg",
190+ IconName::Return => "icons/return.svg",
191+ IconName::Screen => "icons/desktop.svg",
192+ IconName::SelectAll => "icons/select_all.svg",
193+ IconName::Shift => "icons/shift.svg",
194+ IconName::Snip => "icons/snip.svg",
195+ IconName::Space => "icons/space.svg",
196+ IconName::Split => "icons/split.svg",
197+ IconName::Tab => "icons/tab.svg",
198+ IconName::Terminal => "icons/terminal.svg",
199+ IconName::Update => "icons/update.svg",
200+ IconName::WholeWord => "icons/word_search.svg",
201+ IconName::XCircle => "icons/error.svg",
202+ IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
203203 }
204204 }
205205 }
206206
207207 #[derive(IntoElement)]
208−pub struct IconElement {
208+pub struct Icon {
209209 path: SharedString,
210210 color: Color,
211211 size: IconSize,
212212 }
213213
214−impl RenderOnce for IconElement {
215− fn render(self, cx: &mut WindowContext) -> impl IntoElement {
216− svg()
217− .size(self.size.rems())
218− .flex_none()
219− .path(self.path)
220− .text_color(self.color.color(cx))
221− }
222−}
223−
224−impl IconElement {
225− pub fn new(icon: Icon) -> Self {
214+impl Icon {
215+ pub fn new(icon: IconName) -> Self {
226216 Self {
227217 path: icon.path().into(),
228218 color: Color::default(),
@@ -248,3 +238,13 @@ impl IconElement { · 19 unmodified lines
248238 self
249239 }
250240 }
241+
242+impl RenderOnce for Icon {
243+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
244+ svg()
245+ .size(self.size.rems())
246+ .flex_none()
247+ .path(self.path)
248+ .text_color(self.color.color(cx))
249+ }
250+}
crates/ui/src/components/keybinding.rs+22−22
@@ -1,4 +1,4 @@
1−use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
1+use crate::{h_stack, prelude::*, Icon, IconName, IconSize};
22 use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};
33
44 #[derive(IntoElement, Clone)]
@@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { · 21 unmodified lines
2626 .text_color(cx.theme().colors().text_muted)
2727 .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
2828 .when(keystroke.modifiers.control, |el| {
29− el.child(KeyIcon::new(Icon::Control))
29+ el.child(KeyIcon::new(IconName::Control))
3030 })
3131 .when(keystroke.modifiers.alt, |el| {
32− el.child(KeyIcon::new(Icon::Option))
32+ el.child(KeyIcon::new(IconName::Option))
3333 })
3434 .when(keystroke.modifiers.command, |el| {
35− el.child(KeyIcon::new(Icon::Command))
35+ el.child(KeyIcon::new(IconName::Command))
3636 })
3737 .when(keystroke.modifiers.shift, |el| {
38− el.child(KeyIcon::new(Icon::Shift))
38+ el.child(KeyIcon::new(IconName::Shift))
3939 })
4040 .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
4141 .when(key_icon.is_none(), |el| {
@@ -62,21 +62,21 @@ impl KeyBinding { · 20 unmodified lines
6262 Some(Self::new(key_binding))
6363 }
6464
65− fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
65+ fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
6666 match keystroke.key.as_str() {
67− "left" => Some(Icon::ArrowLeft),
68− "right" => Some(Icon::ArrowRight),
69− "up" => Some(Icon::ArrowUp),
70− "down" => Some(Icon::ArrowDown),
71− "backspace" => Some(Icon::Backspace),
72− "delete" => Some(Icon::Delete),
73− "return" => Some(Icon::Return),
74− "enter" => Some(Icon::Return),
75− "tab" => Some(Icon::Tab),
76− "space" => Some(Icon::Space),
77− "escape" => Some(Icon::Escape),
78− "pagedown" => Some(Icon::PageDown),
79− "pageup" => Some(Icon::PageUp),
67+ "left" => Some(IconName::ArrowLeft),
68+ "right" => Some(IconName::ArrowRight),
69+ "up" => Some(IconName::ArrowUp),
70+ "down" => Some(IconName::ArrowDown),
71+ "backspace" => Some(IconName::Backspace),
72+ "delete" => Some(IconName::Delete),
73+ "return" => Some(IconName::Return),
74+ "enter" => Some(IconName::Return),
75+ "tab" => Some(IconName::Tab),
76+ "space" => Some(IconName::Space),
77+ "escape" => Some(IconName::Escape),
78+ "pagedown" => Some(IconName::PageDown),
79+ "pageup" => Some(IconName::PageUp),
8080 _ => None,
8181 }
8282 }
@@ -120,13 +120,13 @@ impl Key { · 37 unmodified lines
120120
121121 #[derive(IntoElement)]
122122 pub struct KeyIcon {
123− icon: Icon,
123+ icon: IconName,
124124 }
125125
126126 impl RenderOnce for KeyIcon {
127127 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
128128 div().w(rems(14. / 16.)).child(
129− IconElement::new(self.icon)
129+ Icon::new(self.icon)
130130 .size(IconSize::Small)
131131 .color(Color::Muted),
132132 )
@@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { · 1 unmodified line
134134 }
135135
136136 impl KeyIcon {
137− pub fn new(icon: Icon) -> Self {
137+ pub fn new(icon: IconName) -> Self {
138138 Self { icon }
139139 }
140140 }
crates/ui/src/components/label/label.rs+57−0
@@ -2,6 +2,28 @@ use gpui::WindowContext; · 1 unmodified line
22
33 use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
44
5+/// A struct representing a label element in the UI.
6+///
7+/// The `Label` struct stores the label text and common properties for a label element.
8+/// It provides methods for modifying these properties.
9+///
10+/// # Examples
11+///
12+/// ```
13+/// Label::new("Hello, World!")
14+/// ```
15+///
16+/// **A colored label**, for example labeling a dangerous action:
17+///
18+/// ```
19+/// let my_label = Label::new("Delete").color(Color::Error);
20+/// ```
21+///
22+/// **A label with a strikethrough**, for example labeling something that has been deleted:
23+///
24+/// ```
25+/// let my_label = Label::new("Deleted").strikethrough(true);
26+/// ```
527 #[derive(IntoElement)]
628 pub struct Label {
729 base: LabelLike,
@@ -9,6 +31,13 @@ pub struct Label { · 1 unmodified line
931 }
1032
1133 impl Label {
34+ /// Create a new `Label` with the given text.
35+ ///
36+ /// # Examples
37+ ///
38+ /// ```
39+ /// let my_label = Label::new("Hello, World!");
40+ /// ```
1241 pub fn new(label: impl Into<SharedString>) -> Self {
1342 Self {
1443 base: LabelLike::new(),
@@ -18,21 +47,49 @@ impl Label { · 3 unmodified lines
1847 }
1948
2049 impl LabelCommon for Label {
50+ /// Sets the size of the label using a [`LabelSize`].
51+ ///
52+ /// # Examples
53+ ///
54+ /// ```
55+ /// let my_label = Label::new("Hello, World!").size(LabelSize::Large);
56+ /// ```
2157 fn size(mut self, size: LabelSize) -> Self {
2258 self.base = self.base.size(size);
2359 self
2460 }
2561
62+ /// Sets the line height style of the label using a [`LineHeightStyle`].
63+ ///
64+ /// # Examples
65+ ///
66+ /// ```
67+ /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal);
68+ /// ```
2669 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
2770 self.base = self.base.line_height_style(line_height_style);
2871 self
2972 }
3073
74+ /// Sets the color of the label using a [`Color`].
75+ ///
76+ /// # Examples
77+ ///
78+ /// ```
79+ /// let my_label = Label::new("Hello, World!").color(Color::Primary);
80+ /// ```
3181 fn color(mut self, color: Color) -> Self {
3282 self.base = self.base.color(color);
3383 self
3484 }
3585
86+ /// Sets the strikethrough property of the label.
87+ ///
88+ /// # Examples
89+ ///
90+ /// ```
91+ /// let my_label = Label::new("Hello, World!").strikethrough(true);
92+ /// ```
3693 fn strikethrough(mut self, strikethrough: bool) -> Self {
3794 self.base = self.base.strikethrough(strikethrough);
3895 self
crates/ui/src/components/label/label_like.rs+1−0
@@ -19,6 +19,7 @@ pub enum LineHeightStyle { · 18 unmodified lines
1919 UiLabel,
2020 }
2121
22+/// A common set of traits all labels must implement.
2223 pub trait LabelCommon {
2324 fn size(self, size: LabelSize) -> Self;
2425 fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;
crates/ui/src/components/list/list_sub_header.rs+7−8
@@ -1,10 +1,10 @@
11 use crate::prelude::*;
2−use crate::{h_stack, Icon, IconElement, IconSize, Label};
2+use crate::{h_stack, Icon, IconName, IconSize, Label};
33
44 #[derive(IntoElement)]
55 pub struct ListSubHeader {
66 label: SharedString,
7− start_slot: Option<Icon>,
7+ start_slot: Option<IconName>,
88 inset: bool,
99 }
1010
@@ -17,7 +17,7 @@ impl ListSubHeader { · 6 unmodified lines
1717 }
1818 }
1919
20− pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
20+ pub fn left_icon(mut self, left_icon: Option<IconName>) -> Self {
2121 self.start_slot = left_icon;
2222 self
2323 }
@@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { · 16 unmodified lines
4040 .flex()
4141 .gap_1()
4242 .items_center()
43− .children(self.start_slot.map(|i| {
44− IconElement::new(i)
45− .color(Color::Muted)
46− .size(IconSize::Small)
47− }))
43+ .children(
44+ self.start_slot
45+ .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)),
46+ )
4847 .child(Label::new(self.label.clone()).color(Color::Muted)),
4948 ),
5049 )
crates/ui/src/components/popover_menu.rs+1−0
@@ -108,6 +108,7 @@ impl<M: ManagedView> PopoverMenu<M> { · 107 unmodified lines
108108 }
109109 }
110110
111+/// Creates a [`PopoverMenu`]
111112 pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
112113 PopoverMenu {
113114 id: id.into(),
crates/ui/src/components/right_click_menu.rs+1−0
@@ -39,6 +39,7 @@ impl<M: ManagedView> RightClickMenu<M> { · 38 unmodified lines
3939 }
4040 }
4141
42+/// Creates a [`RightClickMenu`]
4243 pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
4344 RightClickMenu {
4445 id: id.into(),
crates/ui/src/components/stack.rs+2−6
@@ -2,17 +2,13 @@ use gpui::{div, Div}; · 1 unmodified line
22
33 use crate::StyledExt;
44
5−/// Horizontally stacks elements.
6−///
7−/// Sets `flex()`, `flex_row()`, `items_center()`
5+/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()`
86 #[track_caller]
97 pub fn h_stack() -> Div {
108 div().h_flex()
119 }
1210
13−/// Vertically stacks elements.
14−///
15−/// Sets `flex()`, `flex_col()`
11+/// Vertically stacks elements. Sets `flex()`, `flex_col()`
1612 #[track_caller]
1713 pub fn v_stack() -> Div {
1814 div().v_flex()
crates/ui/src/components/stories/button.rs+3−3
@@ -1,7 +1,7 @@
11 use gpui::Render;
22 use story::Story;
33
4−use crate::{prelude::*, Icon};
4+use crate::{prelude::*, IconName};
55 use crate::{Button, ButtonStyle};
66
77 pub struct ButtonStory;
@@ -23,12 +23,12 @@ impl Render for ButtonStory { · 15 unmodified lines
2323 .child(Story::label("With `label_color`"))
2424 .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
2525 .child(Story::label("With `icon`"))
26− .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
26+ .child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit))
2727 .child(Story::label("Selected with `icon`"))
2828 .child(
2929 Button::new("filled_and_selected_with_icon", "Click me")
3030 .selected(true)
31− .icon(Icon::FileGit),
31+ .icon(IconName::FileGit),
3232 )
3333 .child(Story::label("Default (Subtle)"))
3434 .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
crates/ui/src/components/stories/icon.rs+4−4
@@ -3,17 +3,17 @@ use story::Story; · 2 unmodified lines
33 use strum::IntoEnumIterator;
44
55 use crate::prelude::*;
6−use crate::{Icon, IconElement};
6+use crate::{Icon, IconName};
77
88 pub struct IconStory;
99
1010 impl Render for IconStory {
1111 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
12− let icons = Icon::iter();
12+ let icons = IconName::iter();
1313
1414 Story::container()
15− .child(Story::title_for::<IconElement>())
15+ .child(Story::title_for::<Icon>())
1616 .child(Story::label("All Icons"))
17− .child(div().flex().gap_3().children(icons.map(IconElement::new)))
17+ .child(div().flex().gap_3().children(icons.map(Icon::new)))
1818 }
1919 }
crates/ui/src/components/stories/icon_button.rs+9−9
@@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line
22 use story::{StoryContainer, StoryItem, StorySection};
33
44 use crate::{prelude::*, Tooltip};
5−use crate::{Icon, IconButton};
5+use crate::{IconButton, IconName};
66
77 pub struct IconButtonStory;
88
@@ -10,7 +10,7 @@ impl Render for IconButtonStory { · 1 unmodified line
1010 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
1111 let default_button = StoryItem::new(
1212 "Default",
13− IconButton::new("default_icon_button", Icon::Hash),
13+ IconButton::new("default_icon_button", IconName::Hash),
1414 )
1515 .description("Displays an icon button.")
1616 .usage(
@@ -21,7 +21,7 @@ impl Render for IconButtonStory { · 4 unmodified lines
2121
2222 let selected_button = StoryItem::new(
2323 "Selected",
24− IconButton::new("selected_icon_button", Icon::Hash).selected(true),
24+ IconButton::new("selected_icon_button", IconName::Hash).selected(true),
2525 )
2626 .description("Displays an icon button that is selected.")
2727 .usage(
@@ -32,9 +32,9 @@ impl Render for IconButtonStory { · 4 unmodified lines
3232
3333 let selected_with_selected_icon = StoryItem::new(
3434 "Selected with `selected_icon`",
35− IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)
35+ IconButton::new("selected_with_selected_icon_button", IconName::AudioOn)
3636 .selected(true)
37− .selected_icon(Icon::AudioOff),
37+ .selected_icon(IconName::AudioOff),
3838 )
3939 .description(
4040 "Displays an icon button that is selected and shows a different icon when selected.",
@@ -49,7 +49,7 @@ impl Render for IconButtonStory { · 8 unmodified lines
4949
5050 let disabled_button = StoryItem::new(
5151 "Disabled",
52− IconButton::new("disabled_icon_button", Icon::Hash).disabled(true),
52+ IconButton::new("disabled_icon_button", IconName::Hash).disabled(true),
5353 )
5454 .description("Displays an icon button that is disabled.")
5555 .usage(
@@ -60,7 +60,7 @@ impl Render for IconButtonStory { · 4 unmodified lines
6060
6161 let with_on_click_button = StoryItem::new(
6262 "With `on_click`",
63− IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| {
63+ IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| {
6464 println!("Clicked!");
6565 }),
6666 )
@@ -75,7 +75,7 @@ impl Render for IconButtonStory { · 8 unmodified lines
7575
7676 let with_tooltip_button = StoryItem::new(
7777 "With `tooltip`",
78− IconButton::new("with_tooltip_button", Icon::MessageBubbles)
78+ IconButton::new("with_tooltip_button", IconName::MessageBubbles)
7979 .tooltip(|cx| Tooltip::text("Open messages", cx)),
8080 )
8181 .description("Displays an icon button that has a tooltip when hovered.")
@@ -88,7 +88,7 @@ impl Render for IconButtonStory { · 6 unmodified lines
8888
8989 let selected_with_tooltip_button = StoryItem::new(
9090 "Selected with `tooltip`",
91− IconButton::new("selected_with_tooltip_button", Icon::InlayHint)
91+ IconButton::new("selected_with_tooltip_button", IconName::InlayHint)
9292 .selected(true)
9393 .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
9494 )
crates/ui/src/components/stories/list_header.rs+7−7
@@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line
22 use story::Story;
33
44 use crate::{prelude::*, IconButton};
5−use crate::{Icon, ListHeader};
5+use crate::{IconName, ListHeader};
66
77 pub struct ListHeaderStory;
88
@@ -13,19 +13,19 @@ impl Render for ListHeaderStory { · 4 unmodified lines
1313 .child(Story::label("Default"))
1414 .child(ListHeader::new("Section 1"))
1515 .child(Story::label("With left icon"))
16− .child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell)))
16+ .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
1717 .child(Story::label("With left icon and meta"))
1818 .child(
1919 ListHeader::new("Section 3")
20− .start_slot(IconElement::new(Icon::BellOff))
21− .end_slot(IconButton::new("action_1", Icon::Bolt)),
20+ .start_slot(Icon::new(IconName::BellOff))
21+ .end_slot(IconButton::new("action_1", IconName::Bolt)),
2222 )
2323 .child(Story::label("With multiple meta"))
2424 .child(
2525 ListHeader::new("Section 4")
26− .end_slot(IconButton::new("action_1", Icon::Bolt))
27− .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle))
28− .end_slot(IconButton::new("action_3", Icon::Plus)),
26+ .end_slot(IconButton::new("action_1", IconName::Bolt))
27+ .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle))
28+ .end_slot(IconButton::new("action_3", IconName::Plus)),
2929 )
3030 }
3131 }
crates/ui/src/components/stories/list_item.rs+13−13
@@ -1,8 +1,8 @@
1−use gpui::Render;
1+use gpui::{Render, SharedUrl};
22 use story::Story;
33
44 use crate::{prelude::*, Avatar};
5−use crate::{Icon, ListItem};
5+use crate::{IconName, ListItem};
66
77 pub struct ListItemStory;
88
@@ -18,13 +18,13 @@ impl Render for ListItemStory { · 9 unmodified lines
1818 ListItem::new("inset_list_item")
1919 .inset(true)
2020 .start_slot(
21− IconElement::new(Icon::Bell)
21+ Icon::new(IconName::Bell)
2222 .size(IconSize::Small)
2323 .color(Color::Muted),
2424 )
2525 .child("Hello, world!")
2626 .end_slot(
27− IconElement::new(Icon::Bell)
27+ Icon::new(IconName::Bell)
2828 .size(IconSize::Small)
2929 .color(Color::Muted),
3030 ),
@@ -34,7 +34,7 @@ impl Render for ListItemStory { · 3 unmodified lines
3434 ListItem::new("with start slot_icon")
3535 .child("Hello, world!")
3636 .start_slot(
37− IconElement::new(Icon::Bell)
37+ Icon::new(IconName::Bell)
3838 .size(IconSize::Small)
3939 .color(Color::Muted),
4040 ),
@@ -43,7 +43,7 @@ impl Render for ListItemStory { · 2 unmodified lines
4343 .child(
4444 ListItem::new("with_start slot avatar")
4545 .child("Hello, world!")
46− .start_slot(Avatar::new(SharedString::from(
46+ .start_slot(Avatar::new(SharedUrl::from(
4747 "https://avatars.githubusercontent.com/u/1714999?v=4",
4848 ))),
4949 )
@@ -51,7 +51,7 @@ impl Render for ListItemStory { · 1 unmodified line
5151 .child(
5252 ListItem::new("with_left_avatar")
5353 .child("Hello, world!")
54− .end_slot(Avatar::new(SharedString::from(
54+ .end_slot(Avatar::new(SharedUrl::from(
5555 "https://avatars.githubusercontent.com/u/1714999?v=4",
5656 ))),
5757 )
@@ -62,23 +62,23 @@ impl Render for ListItemStory { · 4 unmodified lines
6262 .end_slot(
6363 h_stack()
6464 .gap_2()
65− .child(Avatar::new(SharedString::from(
65+ .child(Avatar::new(SharedUrl::from(
6666 "https://avatars.githubusercontent.com/u/1789?v=4",
6767 )))
68− .child(Avatar::new(SharedString::from(
68+ .child(Avatar::new(SharedUrl::from(
6969 "https://avatars.githubusercontent.com/u/1789?v=4",
7070 )))
71− .child(Avatar::new(SharedString::from(
71+ .child(Avatar::new(SharedUrl::from(
7272 "https://avatars.githubusercontent.com/u/1789?v=4",
7373 )))
74− .child(Avatar::new(SharedString::from(
74+ .child(Avatar::new(SharedUrl::from(
7575 "https://avatars.githubusercontent.com/u/1789?v=4",
7676 )))
77− .child(Avatar::new(SharedString::from(
77+ .child(Avatar::new(SharedUrl::from(
7878 "https://avatars.githubusercontent.com/u/1789?v=4",
7979 ))),
8080 )
81− .end_hover_slot(Avatar::new(SharedString::from(
81+ .end_hover_slot(Avatar::new(SharedUrl::from(
8282 "https://avatars.githubusercontent.com/u/1714999?v=4",
8383 ))),
8484 )
crates/ui/src/components/stories/tab.rs+1−1
@@ -27,7 +27,7 @@ impl Render for TabStory { · 26 unmodified lines
2727 h_stack().child(
2828 Tab::new("tab_1")
2929 .end_slot(
30− IconButton::new("close_button", Icon::Close)
30+ IconButton::new("close_button", IconName::Close)
3131 .icon_color(Color::Muted)
3232 .size(ButtonSize::None)
3333 .icon_size(IconSize::XSmall),
crates/ui/src/components/stories/tab_bar.rs+7−4
@@ -38,16 +38,19 @@ impl Render for TabBarStory { · 37 unmodified lines
3838 h_stack().child(
3939 TabBar::new("tab_bar_1")
4040 .start_child(
41− IconButton::new("navigate_backward", Icon::ArrowLeft)
41+ IconButton::new("navigate_backward", IconName::ArrowLeft)
4242 .icon_size(IconSize::Small),
4343 )
4444 .start_child(
45− IconButton::new("navigate_forward", Icon::ArrowRight)
45+ IconButton::new("navigate_forward", IconName::ArrowRight)
4646 .icon_size(IconSize::Small),
4747 )
48− .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small))
4948 .end_child(
50− IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small),
49+ IconButton::new("new", IconName::Plus).icon_size(IconSize::Small),
50+ )
51+ .end_child(
52+ IconButton::new("split_pane", IconName::Split)
53+ .icon_size(IconSize::Small),
5154 )
5255 .children(tabs),
5356 ),
crates/ui/src/disableable.rs+1−1
@@ -1,4 +1,4 @@
1−/// A trait for elements that can be disabled.
1+/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled.
22 pub trait Disableable {
33 /// Sets whether the element is disabled.
44 fn disabled(self, disabled: bool) -> Self;
crates/ui/src/fixed.rs+1−1
@@ -1,6 +1,6 @@
11 use gpui::DefiniteLength;
22
3−/// A trait for elements that have a fixed with.
3+/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods.
44 pub trait FixedWidth {
55 /// Sets the width of the element.
66 fn width(self, width: DefiniteLength) -> Self;
crates/ui/src/prelude.rs+3−1
@@ -1,3 +1,5 @@
1+//! The prelude of this crate. When building UI in Zed you almost always want to import this.
2+
13 pub use gpui::prelude::*;
24 pub use gpui::{
35 div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
@@ -15,6 +17,6 @@ pub use crate::{h_stack, v_stack}; · 11 unmodified lines
1517 pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
1618 pub use crate::{ButtonCommon, Color, StyledExt};
1719 pub use crate::{Headline, HeadlineSize};
18−pub use crate::{Icon, IconElement, IconPosition, IconSize};
20+pub use crate::{Icon, IconName, IconPosition, IconSize};
1921 pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};
2022 pub use theme::ActiveTheme;
crates/ui/src/selectable.rs+9−0
@@ -1,18 +1,27 @@
11 /// A trait for elements that can be selected.
2+///
3+/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status.
24 pub trait Selectable {
35 /// Sets whether the element is selected.
46 fn selected(self, selected: bool) -> Self;
57 }
68
9+/// Represents the selection status of an element.
710 #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
811 pub enum Selection {
12+ /// The element is not selected.
913 #[default]
1014 Unselected,
15+ /// The selection state of the element is indeterminate.
1116 Indeterminate,
17+ /// The element is selected.
1218 Selected,
1319 }
1420
1521 impl Selection {
22+ /// Returns the inverse of the current selection status.
23+ ///
24+ /// Indeterminate states become selected if inverted.
1625 pub fn inverse(&self) -> Self {
1726 match self {
1827 Self::Unselected | Self::Indeterminate => Self::Selected,
crates/ui/src/styled_ext.rs+14−7
@@ -14,7 +14,7 @@ fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) - · 13 unmodified lines
1414 .shadow(index.shadow())
1515 }
1616
17−/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
17+/// Extends [`gpui::Styled`] with Zed-specific styling methods.
1818 pub trait StyledExt: Styled + Sized {
1919 /// Horizontally stacks elements.
2020 ///
@@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines
3030 self.flex().flex_col()
3131 }
3232
33+ /// Sets the text size using a [`UiTextSize`].
3334 fn text_ui_size(self, size: UiTextSize) -> Self {
3435 self.text_size(size.rems())
3536 }
@@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
4041 ///
4142 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
4243 ///
43− /// Use [`text_ui_sm`] for regular-sized text.
44+ /// Use `text_ui_sm` for smaller text.
4445 fn text_ui(self) -> Self {
4546 self.text_size(UiTextSize::default().rems())
4647 }
@@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
5152 ///
5253 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
5354 ///
54− /// Use [`text_ui`] for regular-sized text.
55+ /// Use `text_ui` for regular-sized text.
5556 fn text_ui_sm(self) -> Self {
5657 self.text_size(UiTextSize::Small.rems())
5758 }
@@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
6263 ///
6364 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
6465 ///
65− /// Use [`text_ui`] for regular-sized text.
66+ /// Use `text_ui` for regular-sized text.
6667 fn text_ui_xs(self) -> Self {
6768 self.text_size(UiTextSize::XSmall.rems())
6869 }
@@ -78,7 +79,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines
7879 self.text_size(settings.buffer_font_size(cx))
7980 }
8081
81− /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
82+ /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
8283 ///
8384 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
8485 ///
@@ -87,7 +88,7 @@ pub trait StyledExt: Styled + Sized { · 2 unmodified lines
8788 elevated(self, cx, ElevationIndex::Surface)
8889 }
8990
90− /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
91+ /// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
9192 ///
9293 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
9394 ///
@@ -100,7 +101,7 @@ pub trait StyledExt: Styled + Sized { · 6 unmodified lines
100101 ///
101102 /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
102103 ///
103− /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer.
104+ /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer.
104105 ///
105106 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
106107 ///
@@ -119,26 +120,32 @@ pub trait StyledExt: Styled + Sized { · 12 unmodified lines
119120 self.border_color(cx.theme().colors().border_variant)
120121 }
121122
123+ /// Sets the background color to red for debugging when building UI.
122124 fn debug_bg_red(self) -> Self {
123125 self.bg(hsla(0. / 360., 1., 0.5, 1.))
124126 }
125127
128+ /// Sets the background color to green for debugging when building UI.
126129 fn debug_bg_green(self) -> Self {
127130 self.bg(hsla(120. / 360., 1., 0.5, 1.))
128131 }
129132
133+ /// Sets the background color to blue for debugging when building UI.
130134 fn debug_bg_blue(self) -> Self {
131135 self.bg(hsla(240. / 360., 1., 0.5, 1.))
132136 }
133137
138+ /// Sets the background color to yellow for debugging when building UI.
134139 fn debug_bg_yellow(self) -> Self {
135140 self.bg(hsla(60. / 360., 1., 0.5, 1.))
136141 }
137142
143+ /// Sets the background color to cyan for debugging when building UI.
138144 fn debug_bg_cyan(self) -> Self {
139145 self.bg(hsla(160. / 360., 1., 0.5, 1.))
140146 }
141147
148+ /// Sets the background color to magenta for debugging when building UI.
142149 fn debug_bg_magenta(self) -> Self {
143150 self.bg(hsla(300. / 360., 1., 0.5, 1.))
144151 }
crates/ui/src/styles/color.rs+1−0
@@ -1,6 +1,7 @@
11 use gpui::{Hsla, WindowContext};
22 use theme::ActiveTheme;
33
4+/// Sets a color that has a consistent meaning across all themes.
45 #[derive(Debug, Default, PartialEq, Copy, Clone)]
56 pub enum Color {
67 #[default]
crates/ui/src/styles/elevation.rs+1−0
@@ -85,6 +85,7 @@ impl LayerIndex { · 84 unmodified lines
8585 }
8686 }
8787
88+/// An appropriate z-index for the given layer based on its intended useage.
8889 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8990 pub enum ElementIndex {
9091 Effect,
crates/ui/src/styles/typography.rs+1−0
@@ -38,6 +38,7 @@ impl UiTextSize { · 37 unmodified lines
3838 }
3939 }
4040
41+/// The size of a [`Headline`] element
4142 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
4243 pub enum HeadlineSize {
4344 XSmall,
crates/ui/src/ui.rs+0−2
@@ -3,8 +3,6 @@ · 2 unmodified lines
33 //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.
44 //!
55
6−#![doc = include_str!("../docs/building-ui.md")]
7−
86 mod clickable;
97 mod components;
108 mod disableable;
crates/ui/src/utils.rs+2−0
@@ -1,3 +1,5 @@
1+//! UI-related utilities (e.g. converting dates to a human-readable form).
2+
13 mod format_distance;
24
35 pub use format_distance::*;
crates/ui/src/utils/format_distance.rs+10−10
@@ -7,10 +7,10 @@ pub enum DateTimeType { · 6 unmodified lines
77 }
88
99 impl DateTimeType {
10− /// Converts the DateTimeType to a NaiveDateTime.
10+ /// Converts the [`DateTimeType`] to a [`NaiveDateTime`].
1111 ///
12− /// If the DateTimeType is already a NaiveDateTime, it will be returned as is.
13− /// If the DateTimeType is a DateTime<Local>, it will be converted to a NaiveDateTime.
12+ /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is.
13+ /// If the [`DateTimeType`] is a [`DateTime<Local>`], it will be converted to a [`NaiveDateTime`].
1414 pub fn to_naive(&self) -> NaiveDateTime {
1515 match self {
1616 DateTimeType::Naive(naive) => *naive,
@@ -68,13 +68,13 @@ impl FormatDistance { · 51 unmodified lines
6868 }
6969 }
7070
71−/// Calculates the distance in seconds between two NaiveDateTime objects.
71+/// Calculates the distance in seconds between two [`NaiveDateTime`] objects.
7272 /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
7373 ///
7474 /// ## Arguments
7575 ///
76−/// * `date` - A NaiveDateTime object representing the date of interest
77−/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made
76+/// * `date` - A [NaiveDateTime`] object representing the date of interest
77+/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made
7878 fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
7979 let duration = date.signed_duration_since(base_date);
8080 -duration.num_seconds()
@@ -233,12 +233,12 @@ fn distance_string( · 152 unmodified lines
233233 ///
234234 /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.
235235 ///
236−/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now.
236+/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now.
237237 ///
238238 /// # Arguments
239239 ///
240−/// * `date` - The NaiveDateTime to compare.
241−/// * `base_date` - The NaiveDateTime to compare against.
240+/// * `date` - The [`NaiveDateTime`] to compare.
241+/// * `base_date` - The [`NaiveDateTime`] to compare against.
242242 /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
243243 /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
244244 ///
@@ -274,7 +274,7 @@ pub fn format_distance( · 29 unmodified lines
274274 ///
275275 /// # Arguments
276276 ///
277−/// * `datetime` - The NaiveDateTime to compare with the current time.
277+/// * `datetime` - The [`NaiveDateTime`] to compare with the current time.
278278 /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
279279 /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
280280 ///
crates/workspace/src/dock.rs+4−5
@@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> { · 27 unmodified lines
2828 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
2929 fn size(&self, cx: &WindowContext) -> Pixels;
3030 fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
31− fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
31+ fn icon(&self, cx: &WindowContext) -> Option<ui::IconName>;
3232 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
3333 fn toggle_action(&self) -> Box<dyn Action>;
3434 fn icon_label(&self, _: &WindowContext) -> Option<String> {
@@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { · 17 unmodified lines
5252 fn set_active(&self, active: bool, cx: &mut WindowContext);
5353 fn size(&self, cx: &WindowContext) -> Pixels;
5454 fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
55− fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
55+ fn icon(&self, cx: &WindowContext) -> Option<ui::IconName>;
5656 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
5757 fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
5858 fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -104,7 +104,7 @@ where · 45 unmodified lines
104104 self.update(cx, |this, cx| this.set_size(size, cx))
105105 }
106106
107− fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
107+ fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
108108 self.read(cx).icon(cx)
109109 }
110110
@@ -395,7 +395,6 @@ impl Dock { · 284 unmodified lines
395395 })
396396 .ok();
397397 }
398− // todo!() we do not use this event in the production code (even in zed1), remove it
399398 PanelEvent::Activate => {
400399 if let Some(ix) = this
401400 .panel_entries
@@ -775,7 +774,7 @@ pub mod test { · 373 unmodified lines
775774 self.size = size.unwrap_or(px(300.));
776775 }
777776
778− fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
777+ fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
779778 None
780779 }
781780
crates/workspace/src/modal_layer.rs+4−0
@@ -101,6 +101,10 @@ impl ModalLayer { · 100 unmodified lines
101101 let active_modal = self.active_modal.as_ref()?;
102102 active_modal.modal.view().downcast::<V>().ok()
103103 }
104+
105+ pub fn has_active_modal(&self) -> bool {
106+ self.active_modal.is_some()
107+ }
104108 }
105109
106110 impl Render for ModalLayer {
crates/workspace/src/notifications.rs+2−101
@@ -175,7 +175,7 @@ pub mod simple_message_notification { · 174 unmodified lines
175175 };
176176 use std::sync::Arc;
177177 use ui::prelude::*;
178− use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
178+ use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt};
179179
180180 pub struct MessageNotification {
181181 message: SharedString,
@@ -230,7 +230,7 @@ pub mod simple_message_notification { · 48 unmodified lines
230230 .child(
231231 div()
232232 .id("cancel")
233− .child(IconElement::new(Icon::Close))
233+ .child(Icon::new(IconName::Close))
234234 .cursor_pointer()
235235 .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
236236 ),
@@ -247,105 +247,6 @@ pub mod simple_message_notification { · 10 unmodified lines
247247 }))
248248 }
249249 }
250− // todo!()
251− // impl View for MessageNotification {
252− // fn ui_name() -> &'static str {
253− // "MessageNotification"
254− // }
255−
256− // fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
257− // let theme = theme::current(cx).clone();
258− // let theme = &theme.simple_message_notification;
259−
260− // enum MessageNotificationTag {}
261−
262− // let click_message = self.click_message.clone();
263− // let message = match &self.message {
264− // NotificationMessage::Text(text) => {
265− // Text::new(text.to_owned(), theme.message.text.clone()).into_any()
266− // }
267− // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
268− // };
269− // let on_click = self.on_click.clone();
270− // let has_click_action = on_click.is_some();
271−
272− // Flex::column()
273− // .with_child(
274− // Flex::row()
275− // .with_child(
276− // message
277− // .contained()
278− // .with_style(theme.message.container)
279− // .aligned()
280− // .top()
281− // .left()
282− // .flex(1., true),
283− // )
284− // .with_child(
285− // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
286− // let style = theme.dismiss_button.style_for(state);
287− // Svg::new("icons/x.svg")
288− // .with_color(style.color)
289− // .constrained()
290− // .with_width(style.icon_width)
291− // .aligned()
292− // .contained()
293− // .with_style(style.container)
294− // .constrained()
295− // .with_width(style.button_width)
296− // .with_height(style.button_width)
297− // })
298− // .with_padding(Padding::uniform(5.))
299− // .on_click(MouseButton::Left, move |_, this, cx| {
300− // this.dismiss(&Default::default(), cx);
301− // })
302− // .with_cursor_style(CursorStyle::PointingHand)
303− // .aligned()
304− // .constrained()
305− // .with_height(cx.font_cache().line_height(theme.message.text.font_size))
306− // .aligned()
307− // .top()
308− // .flex_float(),
309− // ),
310− // )
311− // .with_children({
312− // click_message
313− // .map(|click_message| {
314− // MouseEventHandler::new::<MessageNotificationTag, _>(
315− // 0,
316− // cx,
317− // |state, _| {
318− // let style = theme.action_message.style_for(state);
319−
320− // Flex::row()
321− // .with_child(
322− // Text::new(click_message, style.text.clone())
323− // .contained()
324− // .with_style(style.container),
325− // )
326− // .contained()
327− // },
328− // )
329− // .on_click(MouseButton::Left, move |_, this, cx| {
330− // if let Some(on_click) = on_click.as_ref() {
331− // on_click(cx);
332− // this.dismiss(&Default::default(), cx);
333− // }
334− // })
335− // // Since we're not using a proper overlay, we have to capture these extra events
336− // .on_down(MouseButton::Left, |_, _, _| {})
337− // .on_up(MouseButton::Left, |_, _, _| {})
338− // .with_cursor_style(if has_click_action {
339− // CursorStyle::PointingHand
340− // } else {
341− // CursorStyle::Arrow
342− // })
343− // })
344− // .into_iter()
345− // })
346− // .into_any()
347− // }
348− // }
349250 }
350251
351252 pub trait NotifyResultExt {
crates/workspace/src/pane.rs+9−9
@@ -31,8 +31,8 @@ use std::{ · 30 unmodified lines
3131 use theme::ThemeSettings;
3232
3333 use ui::{
34− prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label,
35− Tab, TabBar, TabPosition, Tooltip,
34+ prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator,
35+ Label, Tab, TabBar, TabPosition, Tooltip,
3636 };
3737 use ui::{v_stack, ContextMenu};
3838 use util::{maybe, truncate_and_remove_front, ResultExt};
@@ -384,7 +384,7 @@ impl Pane { · 345 unmodified lines
384384 h_stack()
385385 .gap_2()
386386 .child(
387− IconButton::new("plus", Icon::Plus)
387+ IconButton::new("plus", IconName::Plus)
388388 .icon_size(IconSize::Small)
389389 .icon_color(Color::Muted)
390390 .on_click(cx.listener(|pane, _, cx| {
@@ -406,7 +406,7 @@ impl Pane { · 15 unmodified lines
406406 el.child(Self::render_menu_overlay(new_item_menu))
407407 })
408408 .child(
409− IconButton::new("split", Icon::Split)
409+ IconButton::new("split", IconName::Split)
410410 .icon_size(IconSize::Small)
411411 .icon_color(Color::Muted)
412412 .on_click(cx.listener(|pane, _, cx| {
@@ -427,11 +427,11 @@ impl Pane { · 14 unmodified lines
427427 )
428428 .child({
429429 let zoomed = pane.is_zoomed();
430− IconButton::new("toggle_zoom", Icon::Maximize)
430+ IconButton::new("toggle_zoom", IconName::Maximize)
431431 .icon_size(IconSize::Small)
432432 .icon_color(Color::Muted)
433433 .selected(zoomed)
434− .selected_icon(Icon::Minimize)
434+ .selected_icon(IconName::Minimize)
435435 .on_click(cx.listener(|pane, _, cx| {
436436 pane.toggle_zoom(&crate::ToggleZoom, cx);
437437 }))
@@ -1570,7 +1570,7 @@ impl Pane { · 1132 unmodified lines
15701570 })
15711571 .start_slot::<Indicator>(indicator)
15721572 .end_slot(
1573− IconButton::new("close tab", Icon::Close)
1573+ IconButton::new("close tab", IconName::Close)
15741574 .icon_color(Color::Muted)
15751575 .size(ButtonSize::None)
15761576 .icon_size(IconSize::XSmall)
@@ -1676,7 +1676,7 @@ impl Pane { · 99 unmodified lines
16761676 h_stack()
16771677 .gap_2()
16781678 .child(
1679− IconButton::new("navigate_backward", Icon::ArrowLeft)
1679+ IconButton::new("navigate_backward", IconName::ArrowLeft)
16801680 .icon_size(IconSize::Small)
16811681 .on_click({
16821682 let view = cx.view().clone();
@@ -1686,7 +1686,7 @@ impl Pane { · 3 unmodified lines
16861686 .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),
16871687 )
16881688 .child(
1689− IconButton::new("navigate_forward", Icon::ArrowRight)
1689+ IconButton::new("navigate_forward", IconName::ArrowRight)
16901690 .icon_size(IconSize::Small)
16911691 .on_click({
16921692 let view = cx.view().clone();
crates/workspace/src/pane_group.rs+46−22
@@ -12,7 +12,7 @@ use serde::Deserialize; · 11 unmodified lines
1212 use std::sync::Arc;
1313 use ui::{prelude::*, Button};
1414
15−const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
15+const HANDLE_HITBOX_SIZE: f32 = 4.0;
1616 const HORIZONTAL_MIN_SIZE: f32 = 80.;
1717 const VERTICAL_MIN_SIZE: f32 = 100.;
1818
@@ -579,12 +579,15 @@ mod element { · 560 unmodified lines
579579 Size, Style, WeakView, WindowContext,
580580 };
581581 use parking_lot::Mutex;
582+ use settings::Settings;
582583 use smallvec::SmallVec;
583584 use ui::prelude::*;
584585 use util::ResultExt;
585586
586587 use crate::Workspace;
587588
589+ use crate::WorkspaceSettings;
590+
588591 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
589592
590593 const DIVIDER_SIZE: f32 = 1.0;
@@ -704,7 +707,6 @@ mod element { · 113 unmodified lines
704707 proposed_current_pixel_change -= current_pixel_change;
705708 }
706709
707− // todo!(schedule serialize)
708710 workspace
709711 .update(cx, |this, cx| this.schedule_serialize(cx))
710712 .log_err();
@@ -834,20 +836,39 @@ mod element { · 123 unmodified lines
834836 debug_assert!(flexes.len() == len);
835837 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
836838
839+ let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
840+ let active_pane_magnification = if magnification_value == 1. {
841+ None
842+ } else {
843+ Some(magnification_value)
844+ };
845+
846+ let total_flex = if let Some(flex) = active_pane_magnification {
847+ self.children.len() as f32 - 1. + flex
848+ } else {
849+ len as f32
850+ };
851+
837852 let mut origin = bounds.origin;
838− let space_per_flex = bounds.size.along(self.axis) / len as f32;
853+ let space_per_flex = bounds.size.along(self.axis) / total_flex;
839854
840855 let mut bounding_boxes = self.bounding_boxes.lock();
841856 bounding_boxes.clear();
842857
843858 for (ix, child) in self.children.iter_mut().enumerate() {
844− //todo!(active_pane_magnification)
845− // If using active pane magnification, need to switch to using
846− // 1 for all non-active panes, and then the magnification for the
847− // active pane.
859+ let child_flex = active_pane_magnification
860+ .map(|magnification| {
861+ if self.active_pane_ix == Some(ix) {
862+ magnification
863+ } else {
864+ 1.
865+ }
866+ })
867+ .unwrap_or_else(|| flexes[ix]);
868+
848869 let child_size = bounds
849870 .size
850− .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
871+ .apply_along(self.axis, |_| space_per_flex * child_flex);
851872
852873 let child_bounds = Bounds {
853874 origin,
@@ -857,20 +878,23 @@ mod element { · 3 unmodified lines
857878 cx.with_z_index(0, |cx| {
858879 child.draw(origin, child_size.into(), cx);
859880 });
860− cx.with_z_index(1, |cx| {
861− if ix < len - 1 {
862− Self::push_handle(
863− self.flexes.clone(),
864− state.clone(),
865− self.axis,
866− ix,
867− child_bounds,
868− bounds,
869− self.workspace.clone(),
870− cx,
871− );
872− }
873− });
881+
882+ if active_pane_magnification.is_none() {
883+ cx.with_z_index(1, |cx| {
884+ if ix < len - 1 {
885+ Self::push_handle(
886+ self.flexes.clone(),
887+ state.clone(),
888+ self.axis,
889+ ix,
890+ child_bounds,
891+ bounds,
892+ self.workspace.clone(),
893+ cx,
894+ );
895+ }
896+ });
897+ }
874898
875899 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
876900 }
crates/workspace/src/shared_screen.rs+2−2
@@ -12,7 +12,7 @@ use gpui::{ · 11 unmodified lines
1212 WindowContext,
1313 };
1414 use std::sync::{Arc, Weak};
15−use ui::{h_stack, prelude::*, Icon, IconElement, Label};
15+use ui::{h_stack, prelude::*, Icon, IconName, Label};
1616
1717 pub enum Event {
1818 Close,
@@ -100,7 +100,7 @@ impl Item for SharedScreen { · 81 unmodified lines
100100 ) -> gpui::AnyElement {
101101 h_stack()
102102 .gap_1()
103− .child(IconElement::new(Icon::Screen))
103+ .child(Icon::new(IconName::Screen))
104104 .child(
105105 Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
106106 Color::Default
crates/workspace/src/toolbar.rs+0−83
@@ -133,82 +133,6 @@ impl Render for Toolbar { · 132 unmodified lines
133133 }
134134 }
135135
136−// todo!()
137−// impl View for Toolbar {
138−// fn ui_name() -> &'static str {
139−// "Toolbar"
140−// }
141−
142−// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
143−// let theme = &theme::current(cx).workspace.toolbar;
144−
145−// let mut primary_left_items = Vec::new();
146−// let mut primary_right_items = Vec::new();
147−// let mut secondary_item = None;
148−// let spacing = theme.item_spacing;
149−// let mut primary_items_row_count = 1;
150−
151−// for (item, position) in &self.items {
152−// match *position {
153−// ToolbarItemLocation::Hidden => {}
154−
155−// ToolbarItemLocation::PrimaryLeft { flex } => {
156−// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
157−// let left_item = ChildView::new(item.as_any(), cx).aligned();
158−// if let Some((flex, expanded)) = flex {
159−// primary_left_items.push(left_item.flex(flex, expanded).into_any());
160−// } else {
161−// primary_left_items.push(left_item.into_any());
162−// }
163−// }
164−
165−// ToolbarItemLocation::PrimaryRight { flex } => {
166−// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
167−// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
168−// if let Some((flex, expanded)) = flex {
169−// primary_right_items.push(right_item.flex(flex, expanded).into_any());
170−// } else {
171−// primary_right_items.push(right_item.into_any());
172−// }
173−// }
174−
175−// ToolbarItemLocation::Secondary => {
176−// secondary_item = Some(
177−// ChildView::new(item.as_any(), cx)
178−// .constrained()
179−// .with_height(theme.height * item.row_count(cx) as f32)
180−// .into_any(),
181−// );
182−// }
183−// }
184−// }
185−
186−// let container_style = theme.container;
187−// let height = theme.height * primary_items_row_count as f32;
188−
189−// let mut primary_items = Flex::row().with_spacing(spacing);
190−// primary_items.extend(primary_left_items);
191−// primary_items.extend(primary_right_items);
192−
193−// let mut toolbar = Flex::column();
194−// if !primary_items.is_empty() {
195−// toolbar.add_child(primary_items.constrained().with_height(height));
196−// }
197−// if let Some(secondary_item) = secondary_item {
198−// toolbar.add_child(secondary_item);
199−// }
200−
201−// if toolbar.is_empty() {
202−// toolbar.into_any_named("toolbar")
203−// } else {
204−// toolbar
205−// .contained()
206−// .with_style(container_style)
207−// .into_any_named("toolbar")
208−// }
209−// }
210−// }
211−
212136 impl Toolbar {
213137 pub fn new() -> Self {
214138 Self {
@@ -312,10 +236,3 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> { · 97 unmodified lines
312236 self.read(cx).row_count(cx)
313237 }
314238 }
315−
316−// todo!()
317−// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
318−// fn from(val: &dyn ToolbarItemViewHandle) -> Self {
319−// val.as_any().clone()
320−// }
321−// }
crates/workspace/src/workspace.rs+7−6
@@ -943,10 +943,8 @@ impl Workspace { · 942 unmodified lines
943943 cx: &mut ViewContext<Workspace>,
944944 ) -> Task<Result<()>> {
945945 let to_load = if let Some(pane) = pane.upgrade() {
946− // todo!("focus")
947− // cx.focus(&pane);
948−
949946 pane.update(cx, |pane, cx| {
947+ pane.focus(cx);
950948 loop {
951949 // Retrieve the weak item handle from the history.
952950 let entry = pane.nav_history_mut().pop(mode, cx)?;
@@ -1631,8 +1629,7 @@ impl Workspace { · 678 unmodified lines
16311629 });
16321630 }
16331631
1634− // todo!("focus")
1635− // cx.focus_self();
1632+ cx.focus_self();
16361633 cx.notify();
16371634 self.serialize_workspace(cx);
16381635 }
@@ -1713,6 +1710,7 @@ impl Workspace { · 74 unmodified lines
17131710 cx.notify();
17141711 }
17151712
1713+ // todo!()
17161714 // #[cfg(any(test, feature = "test-support"))]
17171715 // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
17181716 // self.zoomed.and_then(|view| view.upgrade(cx))
@@ -2992,7 +2990,6 @@ impl Workspace { · 1273 unmodified lines
29922990 cx.notify();
29932991 }
29942992
2995− #[allow(unused)]
29962993 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
29972994 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
29982995 cx.background_executor()
@@ -3386,6 +3383,10 @@ impl Workspace { · 387 unmodified lines
33863383 div
33873384 }
33883385
3386+ pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3387+ self.modal_layer.read(cx).has_active_modal()
3388+ }
3389+
33893390 pub fn active_modal<V: ManagedView + 'static>(
33903391 &mut self,
33913392 cx: &ViewContext<Self>,
crates/zed/src/zed.rs+0−1
@@ -764,7 +764,6 @@ fn open_bundled_file( · 763 unmodified lines
764764 .detach_and_log_err(cx);
765765 }
766766
767−// todo!()
768767 #[cfg(test)]
769768 mod tests {
770769 use super::*;