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
1561 "serde_json",1561 "serde_json",
1562 "settings",1562 "settings",
1563 "smallvec",1563 "smallvec",
1564 "story",
1564 "theme",1565 "theme",
1565 "theme_selector",1566 "theme_selector",
1566 "time",1567 "time",
@@ -7449,6 +7450,7 @@ dependencies = [ · 5882 unmodified lines
7449 "backtrace-on-stack-overflow",7450 "backtrace-on-stack-overflow",
7450 "chrono",7451 "chrono",
7451 "clap 4.4.4",7452 "clap 4.4.4",
7453 "collab_ui",
7452 "dialoguer",7454 "dialoguer",
7453 "editor",7455 "editor",
7454 "fuzzy",7456 "fuzzy",
crates/assistant/src/assistant_panel.rs+17−17
@@ -933,7 +933,7 @@ impl AssistantPanel { · 932 unmodified lines
933 }933 }
934 934
935 fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {935 fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
936 IconButton::new("hamburger_button", Icon::Menu)936 IconButton::new("hamburger_button", IconName::Menu)
937 .on_click(cx.listener(|this, _event, cx| {937 .on_click(cx.listener(|this, _event, cx| {
938 if this.active_editor().is_some() {938 if this.active_editor().is_some() {
939 this.set_active_editor_index(None, cx);939 this.set_active_editor_index(None, cx);
@@ -957,7 +957,7 @@ impl AssistantPanel { · 17 unmodified lines
957 }957 }
958 958
959 fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {959 fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
960 IconButton::new("split_button", Icon::Snip)960 IconButton::new("split_button", IconName::Snip)
961 .on_click(cx.listener(|this, _event, cx| {961 .on_click(cx.listener(|this, _event, cx| {
962 if let Some(active_editor) = this.active_editor() {962 if let Some(active_editor) = this.active_editor() {
963 active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));963 active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
@@ -968,7 +968,7 @@ impl AssistantPanel { · 4 unmodified lines
968 }968 }
969 969
970 fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {970 fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
971 IconButton::new("assist_button", Icon::MagicWand)971 IconButton::new("assist_button", IconName::MagicWand)
972 .on_click(cx.listener(|this, _event, cx| {972 .on_click(cx.listener(|this, _event, cx| {
973 if let Some(active_editor) = this.active_editor() {973 if let Some(active_editor) = this.active_editor() {
974 active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));974 active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
@@ -979,7 +979,7 @@ impl AssistantPanel { · 4 unmodified lines
979 }979 }
980 980
981 fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {981 fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
982 IconButton::new("quote_button", Icon::Quote)982 IconButton::new("quote_button", IconName::Quote)
983 .on_click(cx.listener(|this, _event, cx| {983 .on_click(cx.listener(|this, _event, cx| {
984 if let Some(workspace) = this.workspace.upgrade() {984 if let Some(workspace) = this.workspace.upgrade() {
985 cx.window_context().defer(move |cx| {985 cx.window_context().defer(move |cx| {
@@ -994,7 +994,7 @@ impl AssistantPanel { · 8 unmodified lines
994 }994 }
995 995
996 fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {996 fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
997 IconButton::new("plus_button", Icon::Plus)997 IconButton::new("plus_button", IconName::Plus)
998 .on_click(cx.listener(|this, _event, cx| {998 .on_click(cx.listener(|this, _event, cx| {
999 this.new_conversation(cx);999 this.new_conversation(cx);
1000 }))1000 }))
@@ -1004,12 +1004,12 @@ impl AssistantPanel { · 3 unmodified lines
1004 1004
1005 fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {1005 fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1006 let zoomed = self.zoomed;1006 let zoomed = self.zoomed;
1007 IconButton::new("zoom_button", Icon::Maximize)1007 IconButton::new("zoom_button", IconName::Maximize)
1008 .on_click(cx.listener(|this, _event, cx| {1008 .on_click(cx.listener(|this, _event, cx| {
1009 this.toggle_zoom(&ToggleZoom, cx);1009 this.toggle_zoom(&ToggleZoom, cx);
1010 }))1010 }))
1011 .selected(zoomed)1011 .selected(zoomed)
1012 .selected_icon(Icon::Minimize)1012 .selected_icon(IconName::Minimize)
1013 .icon_size(IconSize::Small)1013 .icon_size(IconSize::Small)
1014 .tooltip(move |cx| {1014 .tooltip(move |cx| {
1015 Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)1015 Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
@@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { · 270 unmodified lines
1286 }1286 }
1287 }1287 }
1288 1288
1289 fn icon(&self, cx: &WindowContext) -> Option<Icon> {1289 fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1290 Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button)1290 Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button)
1291 }1291 }
1292 1292
1293 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {1293 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2349,7 +2349,7 @@ impl ConversationEditor { · 1055 unmodified lines
2349 div()2349 div()
2350 .id("error")2350 .id("error")
2351 .tooltip(move |cx| Tooltip::text(error.clone(), cx))2351 .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2352 .child(IconElement::new(Icon::XCircle)),2352 .child(Icon::new(IconName::XCircle)),
2353 )2353 )
2354 } else {2354 } else {
2355 None2355 None
@@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { · 289 unmodified lines
2645 .justify_center()2645 .justify_center()
2646 .w(measurements.gutter_width)2646 .w(measurements.gutter_width)
2647 .child(2647 .child(
2648 IconButton::new("include_conversation", Icon::Ai)2648 IconButton::new("include_conversation", IconName::Ai)
2649 .on_click(cx.listener(|this, _, cx| {2649 .on_click(cx.listener(|this, _, cx| {
2650 this.toggle_include_conversation(&ToggleIncludeConversation, cx)2650 this.toggle_include_conversation(&ToggleIncludeConversation, cx)
2651 }))2651 }))
@@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { · 8 unmodified lines
2660 )2660 )
2661 .children(if SemanticIndex::enabled(cx) {2661 .children(if SemanticIndex::enabled(cx) {
2662 Some(2662 Some(
2663 IconButton::new("retrieve_context", Icon::MagnifyingGlass)2663 IconButton::new("retrieve_context", IconName::MagnifyingGlass)
2664 .on_click(cx.listener(|this, _, cx| {2664 .on_click(cx.listener(|this, _, cx| {
2665 this.toggle_retrieve_context(&ToggleRetrieveContext, cx)2665 this.toggle_retrieve_context(&ToggleRetrieveContext, cx)
2666 }))2666 }))
@@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { · 15 unmodified lines
2682 div()2682 div()
2683 .id("error")2683 .id("error")
2684 .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))2684 .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)),
2686 )2686 )
2687 } else {2687 } else {
2688 None2688 None
@@ -2957,7 +2957,7 @@ impl InlineAssistant { · 268 unmodified lines
2957 div()2957 div()
2958 .id("error")2958 .id("error")
2959 .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))2959 .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))
2961 .into_any_element()2961 .into_any_element()
2962 ),2962 ),
2963 2963
@@ -2965,7 +2965,7 @@ impl InlineAssistant { · 1 unmodified line
2965 div()2965 div()
2966 .id("error")2966 .id("error")
2967 .tooltip(|cx| Tooltip::text("Not Indexed", cx))2967 .tooltip(|cx| Tooltip::text("Not Indexed", cx))
2968 .child(IconElement::new(Icon::XCircle))2968 .child(Icon::new(IconName::XCircle))
2969 .into_any_element()2969 .into_any_element()
2970 ),2970 ),
2971 2971
@@ -2996,7 +2996,7 @@ impl InlineAssistant { · 24 unmodified lines
2996 div()2996 div()
2997 .id("update")2997 .id("update")
2998 .tooltip(move |cx| Tooltip::text(status_text.clone(), cx))2998 .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))
3000 .into_any_element()3000 .into_any_element()
3001 )3001 )
3002 }3002 }
@@ -3005,7 +3005,7 @@ impl InlineAssistant { · 2 unmodified lines
3005 div()3005 div()
3006 .id("check")3006 .id("check")
3007 .tooltip(|cx| Tooltip::text("Index up to date", cx))3007 .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))
3009 .into_any_element()3009 .into_any_element()
3010 ),3010 ),
3011 }3011 }
crates/auto_update/src/update_notification.rs+2−2
@@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines
4};4};
5use menu::Cancel;5use menu::Cancel;
6use util::channel::ReleaseChannel;6use util::channel::ReleaseChannel;
7use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};7use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt};
8 8
9pub struct UpdateNotification {9pub struct UpdateNotification {
10 version: SemanticVersion,10 version: SemanticVersion,
@@ -30,7 +30,7 @@ impl Render for UpdateNotification { · 19 unmodified lines
30 .child(30 .child(
31 div()31 div()
32 .id("cancel")32 .id("cancel")
33 .child(IconElement::new(Icon::Close))33 .child(Icon::new(IconName::Close))
34 .cursor_pointer()34 .cursor_pointer()
35 .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),35 .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
36 ),36 ),
crates/call/src/call_settings.rs+3−0
@@ -9,9 +9,12 @@ pub struct CallSettings { · 8 unmodified lines
9 pub mute_on_join: bool,9 pub mute_on_join: bool,
10}10}
11 11
12/// Configuration of voice calls in Zed.
12#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]13#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
13pub struct CallSettingsContent {14pub struct CallSettingsContent {
14 /// Whether the microphone should be muted when joining a channel or a call.15 /// Whether the microphone should be muted when joining a channel or a call.
16 ///
17 /// Default: false
15 pub mute_on_join: Option<bool>,18 pub mute_on_join: Option<bool>,
16}19}
17 20
crates/client/src/user.rs+2−2
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; · 2 unmodified lines
3use collections::{hash_map::Entry, HashMap, HashSet};3use collections::{hash_map::Entry, HashMap, HashSet};
4use feature_flags::FeatureFlagAppExt;4use feature_flags::FeatureFlagAppExt;
5use futures::{channel::mpsc, Future, StreamExt};5use futures::{channel::mpsc, Future, StreamExt};
6use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};6use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task};
7use postage::{sink::Sink, watch};7use postage::{sink::Sink, watch};
8use rpc::proto::{RequestMessage, UsersResponse};8use rpc::proto::{RequestMessage, UsersResponse};
9use std::sync::{Arc, Weak};9use std::sync::{Arc, Weak};
@@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); · 9 unmodified lines
19pub struct User {19pub struct User {
20 pub id: UserId,20 pub id: UserId,
21 pub github_login: String,21 pub github_login: String,
22 pub avatar_uri: SharedString,22 pub avatar_uri: SharedUrl,
23}23}
24 24
25#[derive(Clone, Debug, PartialEq, Eq)]25#[derive(Clone, Debug, PartialEq, Eq)]
crates/collab_ui/Cargo.toml+3−0
@@ -9,6 +9,8 @@ path = "src/collab_ui.rs" · 8 unmodified lines
9doctest = false9doctest = false
10 10
11[features]11[features]
12default = []
13stories = ["dep:story"]
12test-support = [14test-support = [
13 "call/test-support",15 "call/test-support",
14 "client/test-support",16 "client/test-support",
@@ -44,6 +46,7 @@ project = { path = "../project" } · 29 unmodified lines
44recent_projects = { path = "../recent_projects" }46recent_projects = { path = "../recent_projects" }
45rpc = { path = "../rpc" }47rpc = { path = "../rpc" }
46settings = { path = "../settings" }48settings = { path = "../settings" }
49story = { path = "../story", optional = true }
47feature_flags = { path = "../feature_flags"}50feature_flags = { path = "../feature_flags"}
48theme = { path = "../theme" }51theme = { path = "../theme" }
49theme_selector = { path = "../theme_selector" }52theme_selector = { path = "../theme_selector" }
crates/collab_ui/src/chat_panel.rs+7−7
@@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; · 19 unmodified lines
20use settings::{Settings, SettingsStore};20use settings::{Settings, SettingsStore};
21use std::sync::Arc;21use std::sync::Arc;
22use time::{OffsetDateTime, UtcOffset};22use time::{OffsetDateTime, UtcOffset};
23use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};23use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip};
24use util::{ResultExt, TryFutureExt};24use util::{ResultExt, TryFutureExt};
25use workspace::{25use workspace::{
26 dock::{DockPosition, Panel, PanelEvent},26 dock::{DockPosition, Panel, PanelEvent},
@@ -281,12 +281,12 @@ impl ChatPanel { · 254 unmodified lines
281 )),281 )),
282 )282 )
283 .end_child(283 .end_child(
284 IconButton::new("notes", Icon::File)284 IconButton::new("notes", IconName::File)
285 .on_click(cx.listener(Self::open_notes))285 .on_click(cx.listener(Self::open_notes))
286 .tooltip(|cx| Tooltip::text("Open notes", cx)),286 .tooltip(|cx| Tooltip::text("Open notes", cx)),
287 )287 )
288 .end_child(288 .end_child(
289 IconButton::new("call", Icon::AudioOn)289 IconButton::new("call", IconName::AudioOn)
290 .on_click(cx.listener(Self::join_call))290 .on_click(cx.listener(Self::join_call))
291 .tooltip(|cx| Tooltip::text("Join call", cx)),291 .tooltip(|cx| Tooltip::text("Join call", cx)),
292 ),292 ),
@@ -395,7 +395,7 @@ impl ChatPanel { · 102 unmodified lines
395 .w_8()395 .w_8()
396 .visible_on_hover("")396 .visible_on_hover("")
397 .children(message_id_to_remove.map(|message_id| {397 .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(
399 cx.listener(move |this, _, cx| {399 cx.listener(move |this, _, cx| {
400 this.remove_message(message_id, cx);400 this.remove_message(message_id, cx);
401 }),401 }),
@@ -429,7 +429,7 @@ impl ChatPanel { · 27 unmodified lines
429 Button::new("sign-in", "Sign in")429 Button::new("sign-in", "Sign in")
430 .style(ButtonStyle::Filled)430 .style(ButtonStyle::Filled)
431 .icon_color(Color::Muted)431 .icon_color(Color::Muted)
432 .icon(Icon::Github)432 .icon(IconName::Github)
433 .icon_position(IconPosition::Start)433 .icon_position(IconPosition::Start)
434 .full_width()434 .full_width()
435 .on_click(cx.listener(move |this, _, cx| {435 .on_click(cx.listener(move |this, _, cx| {
@@ -622,12 +622,12 @@ impl Panel for ChatPanel { · 186 unmodified lines
622 "ChatPanel"622 "ChatPanel"
623 }623 }
624 624
625 fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {625 fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
626 if !is_channels_feature_enabled(cx) {626 if !is_channels_feature_enabled(cx) {
627 return None;627 return None;
628 }628 }
629 629
630 Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)630 Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
631 }631 }
632 632
633 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {633 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
31use std::{mem, sync::Arc};31use std::{mem, sync::Arc};
32use theme::{ActiveTheme, ThemeSettings};32use theme::{ActiveTheme, ThemeSettings};
33use ui::{33use ui::{
34 prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label,34 prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label,
35 ListHeader, ListItem, Tooltip,35 ListHeader, ListItem, Tooltip,
36};36};
37use util::{maybe, ResultExt, TryFutureExt};37use util::{maybe, ResultExt, TryFutureExt};
@@ -844,7 +844,7 @@ impl CollabPanel { · 806 unmodified lines
844 .end_slot(if is_pending {844 .end_slot(if is_pending {
845 Label::new("Calling").color(Color::Muted).into_any_element()845 Label::new("Calling").color(Color::Muted).into_any_element()
846 } else if is_current_user {846 } else if is_current_user {
847 IconButton::new("leave-call", Icon::Exit)847 IconButton::new("leave-call", IconName::Exit)
848 .style(ButtonStyle::Subtle)848 .style(ButtonStyle::Subtle)
849 .on_click(move |_, cx| Self::leave_call(cx))849 .on_click(move |_, cx| Self::leave_call(cx))
850 .tooltip(|cx| Tooltip::text("Leave Call", cx))850 .tooltip(|cx| Tooltip::text("Leave Call", cx))
@@ -903,7 +903,7 @@ impl CollabPanel { · 52 unmodified lines
903 h_stack()903 h_stack()
904 .gap_1()904 .gap_1()
905 .child(render_tree_branch(is_last, false, cx))905 .child(render_tree_branch(is_last, false, cx))
906 .child(IconButton::new(0, Icon::Folder)),906 .child(IconButton::new(0, IconName::Folder)),
907 )907 )
908 .child(Label::new(project_name.clone()))908 .child(Label::new(project_name.clone()))
909 .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))909 .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
@@ -924,7 +924,7 @@ impl CollabPanel { · 14 unmodified lines
924 h_stack()924 h_stack()
925 .gap_1()925 .gap_1()
926 .child(render_tree_branch(is_last, false, cx))926 .child(render_tree_branch(is_last, false, cx))
927 .child(IconButton::new(0, Icon::Screen)),927 .child(IconButton::new(0, IconName::Screen)),
928 )928 )
929 .child(Label::new("Screen"))929 .child(Label::new("Screen"))
930 .when_some(peer_id, |this, _| {930 .when_some(peer_id, |this, _| {
@@ -965,7 +965,7 @@ impl CollabPanel { · 34 unmodified lines
965 h_stack()965 h_stack()
966 .gap_1()966 .gap_1()
967 .child(render_tree_branch(false, true, cx))967 .child(render_tree_branch(false, true, cx))
968 .child(IconButton::new(0, Icon::File)),968 .child(IconButton::new(0, IconName::File)),
969 )969 )
970 .child(div().h_7().w_full().child(Label::new("notes")))970 .child(div().h_7().w_full().child(Label::new("notes")))
971 .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))971 .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
@@ -986,7 +986,7 @@ impl CollabPanel { · 14 unmodified lines
986 h_stack()986 h_stack()
987 .gap_1()987 .gap_1()
988 .child(render_tree_branch(false, false, cx))988 .child(render_tree_branch(false, false, cx))
989 .child(IconButton::new(0, Icon::MessageBubbles)),989 .child(IconButton::new(0, IconName::MessageBubbles)),
990 )990 )
991 .child(Label::new("chat"))991 .child(Label::new("chat"))
992 .tooltip(move |cx| Tooltip::text("Open Chat", cx))992 .tooltip(move |cx| Tooltip::text("Open Chat", cx))
@@ -1757,7 +1757,7 @@ impl CollabPanel { · 764 unmodified lines
1757 .child(1757 .child(
1758 Button::new("sign_in", "Sign in")1758 Button::new("sign_in", "Sign in")
1759 .icon_color(Color::Muted)1759 .icon_color(Color::Muted)
1760 .icon(Icon::Github)1760 .icon(IconName::Github)
1761 .icon_position(IconPosition::Start)1761 .icon_position(IconPosition::Start)
1762 .style(ButtonStyle::Filled)1762 .style(ButtonStyle::Filled)
1763 .full_width()1763 .full_width()
@@ -1949,7 +1949,7 @@ impl CollabPanel { · 185 unmodified lines
1949 let button = match section {1949 let button = match section {
1950 Section::ActiveCall => channel_link.map(|channel_link| {1950 Section::ActiveCall => channel_link.map(|channel_link| {
1951 let channel_link_copy = channel_link.clone();1951 let channel_link_copy = channel_link.clone();
1952 IconButton::new("channel-link", Icon::Copy)1952 IconButton::new("channel-link", IconName::Copy)
1953 .icon_size(IconSize::Small)1953 .icon_size(IconSize::Small)
1954 .size(ButtonSize::None)1954 .size(ButtonSize::None)
1955 .visible_on_hover("section-header")1955 .visible_on_hover("section-header")
@@ -1961,13 +1961,13 @@ impl CollabPanel { · 5 unmodified lines
1961 .into_any_element()1961 .into_any_element()
1962 }),1962 }),
1963 Section::Contacts => Some(1963 Section::Contacts => Some(
1964 IconButton::new("add-contact", Icon::Plus)1964 IconButton::new("add-contact", IconName::Plus)
1965 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))1965 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
1966 .tooltip(|cx| Tooltip::text("Search for new contact", cx))1966 .tooltip(|cx| Tooltip::text("Search for new contact", cx))
1967 .into_any_element(),1967 .into_any_element(),
1968 ),1968 ),
1969 Section::Channels => Some(1969 Section::Channels => Some(
1970 IconButton::new("add-channel", Icon::Plus)1970 IconButton::new("add-channel", IconName::Plus)
1971 .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))1971 .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
1972 .tooltip(|cx| Tooltip::text("Create a channel", cx))1972 .tooltip(|cx| Tooltip::text("Create a channel", cx))
1973 .into_any_element(),1973 .into_any_element(),
@@ -2038,7 +2038,7 @@ impl CollabPanel { · 64 unmodified lines
2038 })2038 })
2039 .when(!calling, |el| {2039 .when(!calling, |el| {
2040 el.child(2040 el.child(
2041 IconButton::new("remove_contact", Icon::Close)2041 IconButton::new("remove_contact", IconName::Close)
2042 .icon_color(Color::Muted)2042 .icon_color(Color::Muted)
2043 .visible_on_hover("")2043 .visible_on_hover("")
2044 .tooltip(|cx| Tooltip::text("Remove Contact", cx))2044 .tooltip(|cx| Tooltip::text("Remove Contact", cx))
@@ -2099,13 +2099,13 @@ impl CollabPanel { · 54 unmodified lines
2099 2099
2100 let controls = if is_incoming {2100 let controls = if is_incoming {
2101 vec![2101 vec![
2102 IconButton::new("decline-contact", Icon::Close)2102 IconButton::new("decline-contact", IconName::Close)
2103 .on_click(cx.listener(move |this, _, cx| {2103 .on_click(cx.listener(move |this, _, cx| {
2104 this.respond_to_contact_request(user_id, false, cx);2104 this.respond_to_contact_request(user_id, false, cx);
2105 }))2105 }))
2106 .icon_color(color)2106 .icon_color(color)
2107 .tooltip(|cx| Tooltip::text("Decline invite", cx)),2107 .tooltip(|cx| Tooltip::text("Decline invite", cx)),
2108 IconButton::new("accept-contact", Icon::Check)2108 IconButton::new("accept-contact", IconName::Check)
2109 .on_click(cx.listener(move |this, _, cx| {2109 .on_click(cx.listener(move |this, _, cx| {
2110 this.respond_to_contact_request(user_id, true, cx);2110 this.respond_to_contact_request(user_id, true, cx);
2111 }))2111 }))
@@ -2114,7 +2114,7 @@ impl CollabPanel { · 2 unmodified lines
2114 ]2114 ]
2115 } else {2115 } else {
2116 let github_login = github_login.clone();2116 let github_login = github_login.clone();
2117 vec![IconButton::new("remove_contact", Icon::Close)2117 vec![IconButton::new("remove_contact", IconName::Close)
2118 .on_click(cx.listener(move |this, _, cx| {2118 .on_click(cx.listener(move |this, _, cx| {
2119 this.remove_contact(user_id, &github_login, cx);2119 this.remove_contact(user_id, &github_login, cx);
2120 }))2120 }))
@@ -2154,13 +2154,13 @@ impl CollabPanel { · 33 unmodified lines
2154 };2154 };
2155 2155
2156 let controls = [2156 let controls = [
2157 IconButton::new("reject-invite", Icon::Close)2157 IconButton::new("reject-invite", IconName::Close)
2158 .on_click(cx.listener(move |this, _, cx| {2158 .on_click(cx.listener(move |this, _, cx| {
2159 this.respond_to_channel_invite(channel_id, false, cx);2159 this.respond_to_channel_invite(channel_id, false, cx);
2160 }))2160 }))
2161 .icon_color(color)2161 .icon_color(color)
2162 .tooltip(|cx| Tooltip::text("Decline invite", cx)),2162 .tooltip(|cx| Tooltip::text("Decline invite", cx)),
2163 IconButton::new("accept-invite", Icon::Check)2163 IconButton::new("accept-invite", IconName::Check)
2164 .on_click(cx.listener(move |this, _, cx| {2164 .on_click(cx.listener(move |this, _, cx| {
2165 this.respond_to_channel_invite(channel_id, true, cx);2165 this.respond_to_channel_invite(channel_id, true, cx);
2166 }))2166 }))
@@ -2178,7 +2178,7 @@ impl CollabPanel { · 11 unmodified lines
2178 .child(h_stack().children(controls)),2178 .child(h_stack().children(controls)),
2179 )2179 )
2180 .start_slot(2180 .start_slot(
2181 IconElement::new(Icon::Hash)2181 Icon::new(IconName::Hash)
2182 .size(IconSize::Small)2182 .size(IconSize::Small)
2183 .color(Color::Muted),2183 .color(Color::Muted),
2184 )2184 )
@@ -2190,7 +2190,7 @@ impl CollabPanel { · 5 unmodified lines
2190 cx: &mut ViewContext<Self>,2190 cx: &mut ViewContext<Self>,
2191 ) -> ListItem {2191 ) -> ListItem {
2192 ListItem::new("contact-placeholder")2192 ListItem::new("contact-placeholder")
2193 .child(IconElement::new(Icon::Plus))2193 .child(Icon::new(IconName::Plus))
2194 .child(Label::new("Add a Contact"))2194 .child(Label::new("Add a Contact"))
2195 .selected(is_selected)2195 .selected(is_selected)
2196 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))2196 .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
@@ -2274,7 +2274,7 @@ impl CollabPanel { · 77 unmodified lines
2274 };2274 };
2275 2275
2276 let messages_button = |cx: &mut ViewContext<Self>| {2276 let messages_button = |cx: &mut ViewContext<Self>| {
2277 IconButton::new("channel_chat", Icon::MessageBubbles)2277 IconButton::new("channel_chat", IconName::MessageBubbles)
2278 .icon_size(IconSize::Small)2278 .icon_size(IconSize::Small)
2279 .icon_color(if has_messages_notification {2279 .icon_color(if has_messages_notification {
2280 Color::Default2280 Color::Default
@@ -2286,7 +2286,7 @@ impl CollabPanel { · 5 unmodified lines
2286 };2286 };
2287 2287
2288 let notes_button = |cx: &mut ViewContext<Self>| {2288 let notes_button = |cx: &mut ViewContext<Self>| {
2289 IconButton::new("channel_notes", Icon::File)2289 IconButton::new("channel_notes", IconName::File)
2290 .icon_size(IconSize::Small)2290 .icon_size(IconSize::Small)
2291 .icon_color(if has_notes_notification {2291 .icon_color(if has_notes_notification {
2292 Color::Default2292 Color::Default
@@ -2343,9 +2343,13 @@ impl CollabPanel { · 50 unmodified lines
2343 },2343 },
2344 ))2344 ))
2345 .start_slot(2345 .start_slot(
2346 IconElement::new(if is_public { Icon::Public } else { Icon::Hash })2346 Icon::new(if is_public {
2347 .size(IconSize::Small)2347 IconName::Public
2348 .color(Color::Muted),2348 } else {
2349 IconName::Hash
2350 })
2351 .size(IconSize::Small)
2352 .color(Color::Muted),
2349 )2353 )
2350 .child(2354 .child(
2351 h_stack()2355 h_stack()
@@ -2414,7 +2418,7 @@ impl CollabPanel { · 62 unmodified lines
2414 .indent_level(depth + 1)2418 .indent_level(depth + 1)
2415 .indent_step_size(px(20.))2419 .indent_step_size(px(20.))
2416 .start_slot(2420 .start_slot(
2417 IconElement::new(Icon::Hash)2421 Icon::new(IconName::Hash)
2418 .size(IconSize::Small)2422 .size(IconSize::Small)
2419 .color(Color::Muted),2423 .color(Color::Muted),
2420 );2424 );
@@ -2528,10 +2532,10 @@ impl Panel for CollabPanel { · 107 unmodified lines
2528 cx.notify();2532 cx.notify();
2529 }2533 }
2530 2534
2531 fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::Icon> {2535 fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
2532 CollaborationPanelSettings::get_global(cx)2536 CollaborationPanelSettings::get_global(cx)
2533 .button2537 .button
2534 .then(|| ui::Icon::Collab)2538 .then(|| ui::IconName::Collab)
2535 }2539 }
2536 2540
2537 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {2541 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2669,11 +2673,11 @@ impl Render for DraggedChannelView { · 131 unmodified lines
2669 .p_1()2673 .p_1()
2670 .gap_1()2674 .gap_1()
2671 .child(2675 .child(
2672 IconElement::new(2676 Icon::new(
2673 if self.channel.visibility == proto::ChannelVisibility::Public {2677 if self.channel.visibility == proto::ChannelVisibility::Public {
2674 Icon::Public2678 IconName::Public
2675 } else {2679 } else {
2676 Icon::Hash2680 IconName::Hash
2677 },2681 },
2678 )2682 )
2679 .size(IconSize::Small)2683 .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
168 .w_px()168 .w_px()
169 .flex_1()169 .flex_1()
170 .gap_1()170 .gap_1()
171 .child(IconElement::new(Icon::Hash).size(IconSize::Medium))171 .child(Icon::new(IconName::Hash).size(IconSize::Medium))
172 .child(Label::new(channel_name)),172 .child(Label::new(channel_name)),
173 )173 )
174 .child(174 .child(
@@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { · 231 unmodified lines
406 Some(ChannelRole::Guest) => Some(Label::new("Guest")),406 Some(ChannelRole::Guest) => Some(Label::new("Guest")),
407 _ => None,407 _ => None,
408 })408 })
409 .child(IconButton::new("ellipsis", Icon::Ellipsis))409 .child(IconButton::new("ellipsis", IconName::Ellipsis))
410 .children(410 .children(
411 if let (Some((menu, _)), true) = (&self.context_menu, selected) {411 if let (Some((menu, _)), true) = (&self.context_menu, selected) {
412 Some(412 Some(
crates/collab_ui/src/collab_panel/contact_finder.rs+1−3
@@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { · 154 unmodified lines
155 .selected(selected)155 .selected(selected)
156 .start_slot(Avatar::new(user.avatar_uri.clone()))156 .start_slot(Avatar::new(user.avatar_uri.clone()))
157 .child(Label::new(user.github_login.clone()))157 .child(Label::new(user.github_login.clone()))
158 .end_slot::<IconElement>(158 .end_slot::<Icon>(icon_path.map(|icon_path| Icon::from_path(icon_path))),
159 icon_path.map(|icon_path| IconElement::from_path(icon_path)),
160 ),
161 )159 )
162 }160 }
163}161}
crates/collab_ui/src/collab_titlebar_item.rs+10−10
@@ -15,7 +15,7 @@ use std::sync::Arc; · 14 unmodified lines
15use theme::{ActiveTheme, PlayerColors};15use theme::{ActiveTheme, PlayerColors};
16use ui::{16use ui::{
17 h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,17 h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
18 IconButton, IconElement, TintColor, Tooltip,18 IconButton, IconName, TintColor, Tooltip,
19};19};
20use util::ResultExt;20use util::ResultExt;
21use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};21use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { · 191 unmodified lines
213 .child(213 .child(
214 div()214 div()
215 .child(215 .child(
216 IconButton::new("leave-call", ui::Icon::Exit)216 IconButton::new("leave-call", ui::IconName::Exit)
217 .style(ButtonStyle::Subtle)217 .style(ButtonStyle::Subtle)
218 .tooltip(|cx| Tooltip::text("Leave call", cx))218 .tooltip(|cx| Tooltip::text("Leave call", cx))
219 .icon_size(IconSize::Small)219 .icon_size(IconSize::Small)
@@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { · 10 unmodified lines
230 IconButton::new(230 IconButton::new(
231 "mute-microphone",231 "mute-microphone",
232 if is_muted {232 if is_muted {
233 ui::Icon::MicMute233 ui::IconName::MicMute
234 } else {234 } else {
235 ui::Icon::Mic235 ui::IconName::Mic
236 },236 },
237 )237 )
238 .tooltip(move |cx| {238 .tooltip(move |cx| {
@@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { · 17 unmodified lines
256 IconButton::new(256 IconButton::new(
257 "mute-sound",257 "mute-sound",
258 if is_deafened {258 if is_deafened {
259 ui::Icon::AudioOff259 ui::IconName::AudioOff
260 } else {260 } else {
261 ui::Icon::AudioOn261 ui::IconName::AudioOn
262 },262 },
263 )263 )
264 .style(ButtonStyle::Subtle)264 .style(ButtonStyle::Subtle)
@@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { · 16 unmodified lines
281 )281 )
282 .when(!read_only, |this| {282 .when(!read_only, |this| {
283 this.child(283 this.child(
284 IconButton::new("screen-share", ui::Icon::Screen)284 IconButton::new("screen-share", ui::IconName::Screen)
285 .style(ButtonStyle::Subtle)285 .style(ButtonStyle::Subtle)
286 .icon_size(IconSize::Small)286 .icon_size(IconSize::Small)
287 .selected(is_screen_sharing)287 .selected(is_screen_sharing)
@@ -573,7 +573,7 @@ impl CollabTitlebarItem { · 285 unmodified lines
573 | client::Status::ReconnectionError { .. } => Some(573 | client::Status::ReconnectionError { .. } => Some(
574 div()574 div()
575 .id("disconnected")575 .id("disconnected")
576 .child(IconElement::new(Icon::Disconnected).size(IconSize::Small))576 .child(Icon::new(IconName::Disconnected).size(IconSize::Small))
577 .tooltip(|cx| Tooltip::text("Disconnected", cx))577 .tooltip(|cx| Tooltip::text("Disconnected", cx))
578 .into_any_element(),578 .into_any_element(),
579 ),579 ),
@@ -643,7 +643,7 @@ impl CollabTitlebarItem { · 63 unmodified lines
643 h_stack()643 h_stack()
644 .gap_0p5()644 .gap_0p5()
645 .child(Avatar::new(user.avatar_uri.clone()))645 .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)),
647 )647 )
648 .style(ButtonStyle::Subtle)648 .style(ButtonStyle::Subtle)
649 .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),649 .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
@@ -665,7 +665,7 @@ impl CollabTitlebarItem { · 15 unmodified lines
665 .child(665 .child(
666 h_stack()666 h_stack()
667 .gap_0p5()667 .gap_0p5()
668 .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),668 .child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
669 )669 )
670 .style(ButtonStyle::Subtle)670 .style(ButtonStyle::Subtle)
671 .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),671 .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
19use settings::{Settings, SettingsStore};19use settings::{Settings, SettingsStore};
20use std::{sync::Arc, time::Duration};20use std::{sync::Arc, time::Duration};
21use time::{OffsetDateTime, UtcOffset};21use time::{OffsetDateTime, UtcOffset};
22use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label};22use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label};
23use util::{ResultExt, TryFutureExt};23use util::{ResultExt, TryFutureExt};
24use workspace::{24use workspace::{
25 dock::{DockPosition, Panel, PanelEvent},25 dock::{DockPosition, Panel, PanelEvent},
@@ -553,7 +553,7 @@ impl Render for NotificationPanel { · 527 unmodified lines
553 .border_b_1()553 .border_b_1()
554 .border_color(cx.theme().colors().border)554 .border_color(cx.theme().colors().border)
555 .child(Label::new("Notifications"))555 .child(Label::new("Notifications"))
556 .child(IconElement::new(Icon::Envelope)),556 .child(Icon::new(IconName::Envelope)),
557 )557 )
558 .map(|this| {558 .map(|this| {
559 if self.client.user_id().is_none() {559 if self.client.user_id().is_none() {
@@ -564,7 +564,7 @@ impl Render for NotificationPanel { · 4 unmodified lines
564 .child(564 .child(
565 Button::new("sign_in_prompt_button", "Sign in")565 Button::new("sign_in_prompt_button", "Sign in")
566 .icon_color(Color::Muted)566 .icon_color(Color::Muted)
567 .icon(Icon::Github)567 .icon(IconName::Github)
568 .icon_position(IconPosition::Start)568 .icon_position(IconPosition::Start)
569 .style(ButtonStyle::Filled)569 .style(ButtonStyle::Filled)
570 .full_width()570 .full_width()
@@ -655,10 +655,10 @@ impl Panel for NotificationPanel { · 84 unmodified lines
655 }655 }
656 }656 }
657 657
658 fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {658 fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
659 (NotificationPanelSettings::get_global(cx).button659 (NotificationPanelSettings::get_global(cx).button
660 && self.notification_store.read(cx).notification_count() > 0)660 && self.notification_store.read(cx).notification_count() > 0)
661 .then(|| Icon::Bell)661 .then(|| IconName::Bell)
662 }662 }
663 663
664 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {664 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -716,7 +716,7 @@ impl Render for NotificationToast { · 51 unmodified lines
716 .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))716 .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
717 .child(Label::new(self.text.clone()))717 .child(Label::new(self.text.clone()))
718 .child(718 .child(
719 IconButton::new("close", Icon::Close)719 IconButton::new("close", IconName::Close)
720 .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),720 .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
721 )721 )
722 .on_click(cx.listener(|this, _, cx| {722 .on_click(cx.listener(|this, _, cx| {
crates/collab_ui/src/notifications.rs+9−2
@@ -1,9 +1,16 @@
1mod collab_notification;
2pub mod incoming_call_notification;
3pub mod project_shared_notification;
4
5#[cfg(feature = "stories")]
6mod stories;
7
1use gpui::AppContext;8use gpui::AppContext;
2use std::sync::Arc;9use std::sync::Arc;
3use workspace::AppState;10use workspace::AppState;
4 11
5pub mod incoming_call_notification;12#[cfg(feature = "stories")]
6pub mod project_shared_notification;13pub use stories::*;
7 14
8pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {15pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
9 incoming_call_notification::init(app_state, cx);16 incoming_call_notification::init(app_state, cx);
crates/collab_ui/src/notifications/collab_notification.rs+52−0
@@ -0,0 +1,52 @@
1use gpui::{img, prelude::*, AnyElement, SharedUrl};
2use smallvec::SmallVec;
3use ui::prelude::*;
4
5#[derive(IntoElement)]
6pub struct CollabNotification {
7 avatar_uri: SharedUrl,
8 accept_button: Button,
9 dismiss_button: Button,
10 children: SmallVec<[AnyElement; 2]>,
11}
12
13impl 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
28impl ParentElement for CollabNotification {
29 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
30 &mut self.children
31 }
32}
33
34impl 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 @@
1use crate::notification_window_options;1use crate::notification_window_options;
2use crate::notifications::collab_notification::CollabNotification;
2use call::{ActiveCall, IncomingCall};3use call::{ActiveCall, IncomingCall};
3use futures::StreamExt;4use futures::StreamExt;
4use gpui::{5use gpui::{prelude::*, AppContext, WindowHandle};
5 img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
6 VisualContext as _, WindowHandle,
7};
8use settings::Settings;6use settings::Settings;
9use std::sync::{Arc, Weak};7use std::sync::{Arc, Weak};
10use theme::ThemeSettings;8use theme::ThemeSettings;
11use ui::prelude::*;9use ui::{prelude::*, Button, Label};
12use ui::{h_stack, v_stack, Button, Label};
13use util::ResultExt;10use util::ResultExt;
14use workspace::AppState;11use workspace::AppState;
15 12
@@ -31,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 15 unmodified lines
31 if let Some(incoming_call) = incoming_call {28 if let Some(incoming_call) = incoming_call {
32 let unique_screens = cx.update(|cx| cx.displays()).unwrap();29 let unique_screens = cx.update(|cx| cx.displays()).unwrap();
33 let window_size = gpui::Size {30 let window_size = gpui::Size {
34 width: px(380.),31 width: px(400.),
35 height: px(64.),32 height: px(72.),
36 };33 };
37 34
38 for screen in unique_screens {35 for screen in unique_screens {
@@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { · 90 unmodified lines
129 126
130 cx.set_rem_size(ui_font_size);127 cx.set_rem_size(ui_font_size);
131 128
132 h_stack()129 div().size_full().font(ui_font).child(
133 .font(ui_font)130 CollabNotification::new(
134 .text_ui()131 self.state.call.calling_user.avatar_uri.clone(),
135 .justify_between()132 Button::new("accept", "Accept").on_click({
136 .size_full()133 let state = self.state.clone();
137 .overflow_hidden()134 move |_, cx| state.respond(true, cx)
138 .elevation_3(cx)135 }),
139 .p_2()136 Button::new("decline", "Decline").on_click({
140 .gap_2()137 let state = self.state.clone();
141 .child(138 move |_, cx| state.respond(false, cx)
142 img(self.state.call.calling_user.avatar_uri.clone())139 }),
143 .w_12()
144 .h_12()
145 .rounded_full(),
146 )140 )
147 .child(v_stack().overflow_hidden().child(Label::new(format!(141 .child(v_stack().overflow_hidden().child(Label::new(format!(
148 "{} is sharing a project in Zed",142 "{} is sharing a project in Zed",
149 self.state.call.calling_user.github_login143 self.state.call.calling_user.github_login
150 ))))144 )))),
151 .child(145 )
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 )
162 }146 }
163}147}
crates/collab_ui/src/notifications/project_shared_notification.rs+27−47
@@ -1,12 +1,13 @@
1use crate::notification_window_options;1use crate::notification_window_options;
2use crate::notifications::collab_notification::CollabNotification;
2use call::{room, ActiveCall};3use call::{room, ActiveCall};
3use client::User;4use client::User;
4use collections::HashMap;5use collections::HashMap;
5use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};6use gpui::{AppContext, Size};
6use settings::Settings;7use settings::Settings;
7use std::sync::{Arc, Weak};8use std::sync::{Arc, Weak};
8use theme::ThemeSettings;9use theme::ThemeSettings;
9use ui::{h_stack, prelude::*, v_stack, Button, Label};10use ui::{prelude::*, Button, Label};
10use workspace::AppState;11use workspace::AppState;
11 12
12pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {13pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification { · 117 unmodified lines
130 131
131 cx.set_rem_size(ui_font_size);132 cx.set_rem_size(ui_font_size);
132 133
133 h_stack()134 div().size_full().font(ui_font).child(
134 .font(ui_font)135 CollabNotification::new(
135 .text_ui()136 self.owner.avatar_uri.clone(),
136 .justify_between()137 Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
137 .size_full()138 this.join(cx);
138 .overflow_hidden()139 })),
139 .elevation_3(cx)140 Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
140 .p_2()141 this.dismiss(cx);
141 .gap_2()142 })),
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 ))),
178 )143 )
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 )
179 }159 }
180}160}
crates/collab_ui/src/notifications/stories.rs+3−0
@@ -0,0 +1,3 @@
1mod collab_notification;
2
3pub use collab_notification::*;
crates/collab_ui/src/notifications/stories/collab_notification.rs+50−0
@@ -0,0 +1,50 @@
1use gpui::prelude::*;
2use story::{StoryContainer, StoryItem, StorySection};
3use ui::prelude::*;
4
5use crate::notifications::collab_notification::CollabNotification;
6
7pub struct CollabNotificationStory;
8
9impl 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
17use workspace::{17use workspace::{
18 create_and_open_local_file,18 create_and_open_local_file,
19 item::ItemHandle,19 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 },
21 StatusItemView, Toast, Workspace,23 StatusItemView, Toast, Workspace,
22};24};
23use zed_actions::OpenBrowser;25use zed_actions::OpenBrowser;
@@ -51,15 +53,15 @@ impl Render for CopilotButton { · 27 unmodified lines
51 .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));53 .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
52 54
53 let icon = match status {55 let icon = match status {
54 Status::Error(_) => Icon::CopilotError,56 Status::Error(_) => IconName::CopilotError,
55 Status::Authorized => {57 Status::Authorized => {
56 if enabled {58 if enabled {
57 Icon::Copilot59 IconName::Copilot
58 } else {60 } else {
59 Icon::CopilotDisabled61 IconName::CopilotDisabled
60 }62 }
61 }63 }
62 _ => Icon::CopilotInit,64 _ => IconName::CopilotInit,
63 };65 };
64 66
65 if let Status::Error(e) = status {67 if let Status::Error(e) = status {
crates/copilot_ui/src/sign_in.rs+2−2
@@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines
4 FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,4 FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,
5 Subscription, ViewContext,5 Subscription, ViewContext,
6};6};
7use ui::{prelude::*, Button, Icon, Label};7use ui::{prelude::*, Button, IconName, Label};
8use workspace::ModalView;8use workspace::ModalView;
9 9
10const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";10const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
@@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification { · 164 unmodified lines
175 .w_32()175 .w_32()
176 .h_16()176 .h_16()
177 .flex_none()177 .flex_none()
178 .path(Icon::ZedXCopilot.path())178 .path(IconName::ZedXCopilot.path())
179 .text_color(cx.theme().colors().icon),179 .text_color(cx.theme().colors().icon),
180 )180 )
181 .child(prompt)181 .child(prompt)
crates/diagnostics/src/diagnostics.rs+5−7
@@ -36,7 +36,7 @@ use std::{ · 35 unmodified lines
36};36};
37use theme::ActiveTheme;37use theme::ActiveTheme;
38pub use toolbar_controls::ToolbarControls;38pub use toolbar_controls::ToolbarControls;
39use ui::{h_stack, prelude::*, Icon, IconElement, Label};39use ui::{h_stack, prelude::*, Icon, IconName, Label};
40use util::TryFutureExt;40use util::TryFutureExt;
41use workspace::{41use workspace::{
42 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},42 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { · 617 unmodified lines
660 then.child(660 then.child(
661 h_stack()661 h_stack()
662 .gap_1()662 .gap_1()
663 .child(IconElement::new(Icon::XCircle).color(Color::Error))663 .child(Icon::new(IconName::XCircle).color(Color::Error))
664 .child(Label::new(self.summary.error_count.to_string()).color(664 .child(Label::new(self.summary.error_count.to_string()).color(
665 if selected {665 if selected {
666 Color::Default666 Color::Default
@@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { · 7 unmodified lines
674 then.child(674 then.child(
675 h_stack()675 h_stack()
676 .gap_1()676 .gap_1()
677 .child(677 .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
678 IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
679 )
680 .child(Label::new(self.summary.warning_count.to_string()).color(678 .child(Label::new(self.summary.warning_count.to_string()).color(
681 if selected {679 if selected {
682 Color::Default680 Color::Default
@@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { · 133 unmodified lines
816 .flex_none()814 .flex_none()
817 .map(|icon| {815 .map(|icon| {
818 if diagnostic.severity == DiagnosticSeverity::ERROR {816 if diagnostic.severity == DiagnosticSeverity::ERROR {
819 icon.path(Icon::XCircle.path())817 icon.path(IconName::XCircle.path())
820 .text_color(Color::Error.color(cx))818 .text_color(Color::Error.color(cx))
821 } else {819 } else {
822 icon.path(Icon::ExclamationTriangle.path())820 icon.path(IconName::ExclamationTriangle.path())
823 .text_color(Color::Warning.color(cx))821 .text_color(Color::Warning.color(cx))
824 }822 }
825 }),823 }),
crates/diagnostics/src/items.rs+7−7
@@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines
6};6};
7use language::Diagnostic;7use language::Diagnostic;
8use lsp::LanguageServerId;8use lsp::LanguageServerId;
9use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};9use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
10use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};10use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
11 11
12use crate::{Deploy, ProjectDiagnosticsEditor};12use crate::{Deploy, ProjectDiagnosticsEditor};
@@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { · 12 unmodified lines
25 let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {25 let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
26 (0, 0) => h_stack().map(|this| {26 (0, 0) => h_stack().map(|this| {
27 this.child(27 this.child(
28 IconElement::new(Icon::Check)28 Icon::new(IconName::Check)
29 .size(IconSize::Small)29 .size(IconSize::Small)
30 .color(Color::Default),30 .color(Color::Default),
31 )31 )
@@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
33 (0, warning_count) => h_stack()33 (0, warning_count) => h_stack()
34 .gap_1()34 .gap_1()
35 .child(35 .child(
36 IconElement::new(Icon::ExclamationTriangle)36 Icon::new(IconName::ExclamationTriangle)
37 .size(IconSize::Small)37 .size(IconSize::Small)
38 .color(Color::Warning),38 .color(Color::Warning),
39 )39 )
@@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
41 (error_count, 0) => h_stack()41 (error_count, 0) => h_stack()
42 .gap_1()42 .gap_1()
43 .child(43 .child(
44 IconElement::new(Icon::XCircle)44 Icon::new(IconName::XCircle)
45 .size(IconSize::Small)45 .size(IconSize::Small)
46 .color(Color::Error),46 .color(Color::Error),
47 )47 )
@@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { · 1 unmodified line
49 (error_count, warning_count) => h_stack()49 (error_count, warning_count) => h_stack()
50 .gap_1()50 .gap_1()
51 .child(51 .child(
52 IconElement::new(Icon::XCircle)52 Icon::new(IconName::XCircle)
53 .size(IconSize::Small)53 .size(IconSize::Small)
54 .color(Color::Error),54 .color(Color::Error),
55 )55 )
56 .child(Label::new(error_count.to_string()).size(LabelSize::Small))56 .child(Label::new(error_count.to_string()).size(LabelSize::Small))
57 .child(57 .child(
58 IconElement::new(Icon::ExclamationTriangle)58 Icon::new(IconName::ExclamationTriangle)
59 .size(IconSize::Small)59 .size(IconSize::Small)
60 .color(Color::Warning),60 .color(Color::Warning),
61 )61 )
@@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { · 4 unmodified lines
66 Some(66 Some(
67 h_stack()67 h_stack()
68 .gap_2()68 .gap_2()
69 .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small))69 .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small))
70 .child(70 .child(
71 Label::new("Checking…")71 Label::new("Checking…")
72 .size(LabelSize::Small)72 .size(LabelSize::Small)
crates/diagnostics/src/toolbar_controls.rs+2−2
@@ -1,7 +1,7 @@
1use crate::ProjectDiagnosticsEditor;1use crate::ProjectDiagnosticsEditor;
2use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};2use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
3use ui::prelude::*;3use ui::prelude::*;
4use ui::{Icon, IconButton, Tooltip};4use ui::{IconButton, IconName, Tooltip};
5use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};5use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
6 6
7pub struct ToolbarControls {7pub struct ToolbarControls {
@@ -24,7 +24,7 @@ impl Render for ToolbarControls { · 16 unmodified lines
24 };24 };
25 25
26 div().child(26 div().child(
27 IconButton::new("toggle-warnings", Icon::ExclamationTriangle)27 IconButton::new("toggle-warnings", IconName::ExclamationTriangle)
28 .tooltip(move |cx| Tooltip::text(tooltip, cx))28 .tooltip(move |cx| Tooltip::text(tooltip, cx))
29 .on_click(cx.listener(|this, _, cx| {29 .on_click(cx.listener(|this, _, cx| {
30 if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {30 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
1015 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))1015 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1016 .unwrap_or(10);1016 .unwrap_or(10);
1017 1017
1018 let _test_platform = &cx.test_platform;
1019 let mut tab_size = rng.gen_range(1..=4);1018 let mut tab_size = rng.gen_range(1..=4);
1020 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);1019 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1021 let excerpt_header_height = rng.gen_range(1..=5);1020 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
99use text::{OffsetUtf16, Rope};99use text::{OffsetUtf16, Rope};
100use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};100use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
101use ui::{101use ui::{
102 h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover,102 h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem,
103 Tooltip,103 Popover, Tooltip,
104};104};
105use util::{post_inc, RangeExt, ResultExt, TryFutureExt};105use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
106use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};106use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
@@ -507,7 +507,7 @@ pub enum SoftWrap { · 400 unmodified lines
507 Column(u32),507 Column(u32),
508}508}
509 509
510#[derive(Clone, Default)]510#[derive(Clone)]
511pub struct EditorStyle {511pub struct EditorStyle {
512 pub background: Hsla,512 pub background: Hsla,
513 pub local_player: PlayerColor,513 pub local_player: PlayerColor,
@@ -519,6 +519,24 @@ pub struct EditorStyle { · 5 unmodified lines
519 pub suggestions_style: HighlightStyle,519 pub suggestions_style: HighlightStyle,
520}520}
521 521
522impl 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
522type CompletionId = usize;540type CompletionId = usize;
523 541
524// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;542// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
@@ -4223,7 +4241,7 @@ impl Editor { · 3698 unmodified lines
4223 ) -> Option<IconButton> {4241 ) -> Option<IconButton> {
4224 if self.available_code_actions.is_some() {4242 if self.available_code_actions.is_some() {
4225 Some(4243 Some(
4226 IconButton::new("code_actions_indicator", ui::Icon::Bolt)4244 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
4227 .icon_size(IconSize::Small)4245 .icon_size(IconSize::Small)
4228 .icon_color(Color::Muted)4246 .icon_color(Color::Muted)
4229 .selected(is_active)4247 .selected(is_active)
@@ -4257,7 +4275,7 @@ impl Editor { · 27 unmodified lines
4257 fold_data4275 fold_data
4258 .map(|(fold_status, buffer_row, active)| {4276 .map(|(fold_status, buffer_row, active)| {
4259 (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {4277 (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)
4261 .on_click(cx.listener(move |editor, _e, cx| match fold_status {4279 .on_click(cx.listener(move |editor, _e, cx| match fold_status {
4262 FoldStatus::Folded => {4280 FoldStatus::Folded => {
4263 editor.unfold_at(&UnfoldAt { buffer_row }, cx);4281 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
@@ -4269,7 +4287,7 @@ impl Editor { · 5 unmodified lines
4269 .icon_color(ui::Color::Muted)4287 .icon_color(ui::Color::Muted)
4270 .icon_size(ui::IconSize::Small)4288 .icon_size(ui::IconSize::Small)
4271 .selected(fold_status == FoldStatus::Folded)4289 .selected(fold_status == FoldStatus::Folded)
4272 .selected_icon(ui::Icon::ChevronRight)4290 .selected_icon(ui::IconName::ChevronRight)
4273 .size(ui::ButtonSize::None)4291 .size(ui::ButtonSize::None)
4274 })4292 })
4275 })4293 })
@@ -9739,7 +9757,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren · 5463 unmodified lines
9739 ),9757 ),
9740 )9758 )
9741 .child(9759 .child(
9742 IconButton::new(("copy-block", cx.block_id), Icon::Copy)9760 IconButton::new(("copy-block", cx.block_id), IconName::Copy)
9743 .icon_color(Color::Muted)9761 .icon_color(Color::Muted)
9744 .size(ButtonSize::Compact)9762 .size(ButtonSize::Compact)
9745 .style(ButtonStyle::Transparent)9763 .style(ButtonStyle::Transparent)
crates/editor/src/element.rs+55−46
@@ -28,7 +28,7 @@ use gpui::{ · 27 unmodified lines
28 AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,28 AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
29 CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,29 CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,
30 InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,30 InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
31 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine,31 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
32 SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,32 SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
33 TextStyle, View, ViewContext, WindowContext,33 TextStyle, View, ViewContext, WindowContext,
34};34};
@@ -581,41 +581,6 @@ impl EditorElement { · 546 unmodified lines
581 }581 }
582 }582 }
583 583
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
619 fn paint_background(584 fn paint_background(
620 &self,585 &self,
621 gutter_bounds: Bounds<Pixels>,586 gutter_bounds: Bounds<Pixels>,
@@ -2450,34 +2415,78 @@ impl EditorElement { · 1828 unmodified lines
2450 )2415 )
2451 }2416 }
2452 2417
2453 fn paint_mouse_listeners(2418 fn paint_scroll_wheel_listener(
2454 &mut self,2419 &mut self,
2455 bounds: Bounds<Pixels>,2420 interactive_bounds: &InteractiveBounds,
2456 gutter_bounds: Bounds<Pixels>,
2457 text_bounds: Bounds<Pixels>,
2458 layout: &LayoutState,2421 layout: &LayoutState,
2459 cx: &mut WindowContext,2422 cx: &mut WindowContext,
2460 ) {2423 ) {
2461 let interactive_bounds = InteractiveBounds {
2462 bounds: bounds.intersect(&cx.content_mask().bounds),
2463 stacking_order: cx.stacking_order().clone(),
2464 };
2465
2466 cx.on_mouse_event({2424 cx.on_mouse_event({
2467 let position_map = layout.position_map.clone();2425 let position_map = layout.position_map.clone();
2468 let editor = self.editor.clone();2426 let editor = self.editor.clone();
2469 let interactive_bounds = interactive_bounds.clone();2427 let interactive_bounds = interactive_bounds.clone();
2428 let mut delta = ScrollDelta::default();
2470 2429
2471 move |event: &ScrollWheelEvent, phase, cx| {2430 move |event: &ScrollWheelEvent, phase, cx| {
2472 if phase == DispatchPhase::Bubble2431 if phase == DispatchPhase::Bubble
2473 && interactive_bounds.visibly_contains(&event.position, cx)2432 && interactive_bounds.visibly_contains(&event.position, cx)
2474 {2433 {
2434 delta = delta.coalesce(event.delta);
2475 editor.update(cx, |editor, cx| {2435 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();
2477 });2470 });
2478 }2471 }
2479 }2472 }
2480 });2473 });
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);
2481 2490
2482 cx.on_mouse_event({2491 cx.on_mouse_event({
2483 let position_map = layout.position_map.clone();2492 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
16use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};16use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
17use settings::Settings;17use settings::Settings;
18use std::{ops::Range, sync::Arc, time::Duration};18use std::{ops::Range, sync::Arc, time::Duration};
19use ui::{StyledExt, Tooltip};19use ui::{prelude::*, Tooltip};
20use util::TryFutureExt;20use util::TryFutureExt;
21use workspace::Workspace;21use workspace::Workspace;
22 22
@@ -514,6 +514,8 @@ impl DiagnosticPopover { · 491 unmodified lines
514 None => self.local_diagnostic.diagnostic.message.clone(),514 None => self.local_diagnostic.diagnostic.message.clone(),
515 };515 };
516 516
517 let status_colors = cx.theme().status();
518
517 struct DiagnosticColors {519 struct DiagnosticColors {
518 pub background: Hsla,520 pub background: Hsla,
519 pub border: Hsla,521 pub border: Hsla,
@@ -521,24 +523,24 @@ impl DiagnosticPopover { · 1 unmodified line
521 523
522 let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {524 let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
523 DiagnosticSeverity::ERROR => DiagnosticColors {525 DiagnosticSeverity::ERROR => DiagnosticColors {
524 background: style.status.error_background,526 background: status_colors.error_background,
525 border: style.status.error_border,527 border: status_colors.error_border,
526 },528 },
527 DiagnosticSeverity::WARNING => DiagnosticColors {529 DiagnosticSeverity::WARNING => DiagnosticColors {
528 background: style.status.warning_background,530 background: status_colors.warning_background,
529 border: style.status.warning_border,531 border: status_colors.warning_border,
530 },532 },
531 DiagnosticSeverity::INFORMATION => DiagnosticColors {533 DiagnosticSeverity::INFORMATION => DiagnosticColors {
532 background: style.status.info_background,534 background: status_colors.info_background,
533 border: style.status.info_border,535 border: status_colors.info_border,
534 },536 },
535 DiagnosticSeverity::HINT => DiagnosticColors {537 DiagnosticSeverity::HINT => DiagnosticColors {
536 background: style.status.hint_background,538 background: status_colors.hint_background,
537 border: style.status.hint_border,539 border: status_colors.hint_border,
538 },540 },
539 _ => DiagnosticColors {541 _ => DiagnosticColors {
540 background: style.status.ignored_background,542 background: status_colors.ignored_background,
541 border: style.status.ignored_border,543 border: status_colors.ignored_border,
542 },544 },
543 };545 };
544 546
crates/editor/src/scroll.rs+6−4
@@ -384,10 +384,12 @@ impl Editor { · 383 unmodified lines
384 ) {384 ) {
385 hide_hover(self, cx);385 hide_hover(self, cx);
386 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);386 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
387 let top_row = scroll_anchor387 let snapshot = &self.buffer().read(cx).snapshot(cx);
388 .anchor388 if !scroll_anchor.anchor.is_valid(snapshot) {
389 .to_point(&self.buffer().read(cx).snapshot(cx))389 log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
390 .row;390 return;
391 }
392 let top_row = scroll_anchor.anchor.to_point(snapshot).row;
391 self.scroll_manager393 self.scroll_manager
392 .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);394 .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
393 }395 }
crates/editor/src/test.rs+2−4
@@ -60,8 +60,7 @@ pub fn assert_text_with_selections( · 59 unmodified lines
60#[allow(dead_code)]60#[allow(dead_code)]
61#[cfg(any(test, feature = "test-support"))]61#[cfg(any(test, feature = "test-support"))]
62pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {62pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
63 // todo!()63 Editor::new(EditorMode::Full, buffer, None, cx)
64 Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
65}64}
66 65
67pub(crate) fn build_editor_with_project(66pub(crate) fn build_editor_with_project(
@@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( · 1 unmodified line
69 buffer: Model<MultiBuffer>,68 buffer: Model<MultiBuffer>,
70 cx: &mut ViewContext<Editor>,69 cx: &mut ViewContext<Editor>,
71) -> Editor {70) -> Editor {
72 // todo!()71 Editor::new(EditorMode::Full, buffer, Some(project), cx)
73 Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
74}72}
crates/feedback/src/deploy_feedback_button.rs+2−2
@@ -1,5 +1,5 @@
1use gpui::{Render, ViewContext, WeakView};1use gpui::{Render, ViewContext, WeakView};
2use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip};2use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip};
3use workspace::{item::ItemHandle, StatusItemView, Workspace};3use workspace::{item::ItemHandle, StatusItemView, Workspace};
4 4
5use crate::{feedback_modal::FeedbackModal, GiveFeedback};5use crate::{feedback_modal::FeedbackModal, GiveFeedback};
@@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { · 21 unmodified lines
27 })27 })
28 })28 })
29 .is_some();29 .is_some();
30 IconButton::new("give-feedback", Icon::Envelope)30 IconButton::new("give-feedback", IconName::Envelope)
31 .style(ui::ButtonStyle::Subtle)31 .style(ui::ButtonStyle::Subtle)
32 .icon_size(IconSize::Small)32 .icon_size(IconSize::Small)
33 .selected(is_open)33 .selected(is_open)
crates/feedback/src/feedback_modal.rs+1−1
@@ -488,7 +488,7 @@ impl Render for FeedbackModal { · 487 unmodified lines
488 .child(488 .child(
489 Button::new("community_repository", "Community Repository")489 Button::new("community_repository", "Community Repository")
490 .style(ButtonStyle::Transparent)490 .style(ButtonStyle::Transparent)
491 .icon(Icon::ExternalLink)491 .icon(IconName::ExternalLink)
492 .icon_position(IconPosition::End)492 .icon_position(IconPosition::End)
493 .icon_size(IconSize::Small)493 .icon_size(IconSize::Small)
494 .on_click(open_community_repo),494 .on_click(open_community_repo),
crates/gpui/docs/key_dispatch.md+2−2
@@ -50,7 +50,7 @@ impl Render for Menu { · 49 unmodified lines
50 .on_action(|this, move: &MoveDown, cx| {50 .on_action(|this, move: &MoveDown, cx| {
51 // ...51 // ...
52 })52 })
53 .children(todo!())53 .children(unimplemented!())
54 }54 }
55}55}
56```56```
@@ -68,7 +68,7 @@ impl Render for Menu { · 11 unmodified lines
68 .on_action(|this, move: &MoveDown, cx| {68 .on_action(|this, move: &MoveDown, cx| {
69 // ...69 // ...
70 })70 })
71 .children(todo!())71 .children(unimplemented!())
72 }72 }
73}73}
74```74```
crates/gpui/src/action.rs+17−6
@@ -114,14 +114,26 @@ impl ActionRegistry { · 113 unmodified lines
114 pub(crate) fn load_actions(&mut self) {114 pub(crate) fn load_actions(&mut self) {
115 for builder in __GPUI_ACTIONS {115 for builder in __GPUI_ACTIONS {
116 let action = builder();116 let action = builder();
117 //todo(remove)117 self.insert_action(action);
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);
122 }118 }
123 }119 }
124 120
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
125 /// Construct an action based on its name and optional JSON parameters sourced from the keymap.137 /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
126 pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {138 pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
127 let name = self139 let name = self
@@ -203,7 +215,6 @@ macro_rules! __impl_action { · 75 unmodified lines
203 )215 )
204 }216 }
205 217
206 // todo!() why is this needed in addition to name?
207 fn debug_name() -> &'static str218 fn debug_name() -> &'static str
208 where219 where
209 Self: ::std::marker::Sized220 Self: ::std::marker::Sized
crates/gpui/src/app.rs+28−13
@@ -45,11 +45,13 @@ use util::{ · 44 unmodified lines
45 45
46/// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.46/// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
47/// Strongly consider removing after stabilization.47/// Strongly consider removing after stabilization.
48#[doc(hidden)]
48pub struct AppCell {49pub struct AppCell {
49 app: RefCell<AppContext>,50 app: RefCell<AppContext>,
50}51}
51 52
52impl AppCell {53impl AppCell {
54 #[doc(hidden)]
53 #[track_caller]55 #[track_caller]
54 pub fn borrow(&self) -> AppRef {56 pub fn borrow(&self) -> AppRef {
55 if option_env!("TRACK_THREAD_BORROWS").is_some() {57 if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -59,6 +61,7 @@ impl AppCell { · 3 unmodified lines
59 AppRef(self.app.borrow())61 AppRef(self.app.borrow())
60 }62 }
61 63
64 #[doc(hidden)]
62 #[track_caller]65 #[track_caller]
63 pub fn borrow_mut(&self) -> AppRefMut {66 pub fn borrow_mut(&self) -> AppRefMut {
64 if option_env!("TRACK_THREAD_BORROWS").is_some() {67 if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -69,6 +72,7 @@ impl AppCell { · 4 unmodified lines
69 }72 }
70}73}
71 74
75#[doc(hidden)]
72#[derive(Deref, DerefMut)]76#[derive(Deref, DerefMut)]
73pub struct AppRef<'a>(Ref<'a, AppContext>);77pub struct AppRef<'a>(Ref<'a, AppContext>);
74 78
@@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> { · 6 unmodified lines
81 }85 }
82}86}
83 87
88#[doc(hidden)]
84#[derive(Deref, DerefMut)]89#[derive(Deref, DerefMut)]
85pub struct AppRefMut<'a>(RefMut<'a, AppContext>);90pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
86 91
@@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> { · 6 unmodified lines
93 }98 }
94}99}
95 100
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.
96pub struct App(Rc<AppCell>);103pub struct App(Rc<AppCell>);
97 104
98/// Represents an application before it is fully launched. Once your app is105/// Represents an application before it is fully launched. Once your app is
@@ -136,6 +143,8 @@ impl App { · 37 unmodified lines
136 self143 self
137 }144 }
138 145
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.
139 pub fn on_reopen<F>(&self, mut callback: F) -> &Self148 pub fn on_reopen<F>(&self, mut callback: F) -> &Self
140 where149 where
141 F: 'static + FnMut(&mut AppContext),150 F: 'static + FnMut(&mut AppContext),
@@ -149,18 +158,22 @@ impl App { · 7 unmodified lines
149 self158 self
150 }159 }
151 160
161 /// Returns metadata associated with the application
152 pub fn metadata(&self) -> AppMetadata {162 pub fn metadata(&self) -> AppMetadata {
153 self.0.borrow().app_metadata.clone()163 self.0.borrow().app_metadata.clone()
154 }164 }
155 165
166 /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
156 pub fn background_executor(&self) -> BackgroundExecutor {167 pub fn background_executor(&self) -> BackgroundExecutor {
157 self.0.borrow().background_executor.clone()168 self.0.borrow().background_executor.clone()
158 }169 }
159 170
171 /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground.
160 pub fn foreground_executor(&self) -> ForegroundExecutor {172 pub fn foreground_executor(&self) -> ForegroundExecutor {
161 self.0.borrow().foreground_executor.clone()173 self.0.borrow().foreground_executor.clone()
162 }174 }
163 175
176 /// Returns a reference to the [`TextSystem`] associated with this app.
164 pub fn text_system(&self) -> Arc<TextSystem> {177 pub fn text_system(&self) -> Arc<TextSystem> {
165 self.0.borrow().text_system.clone()178 self.0.borrow().text_system.clone()
166 }179 }
@@ -174,12 +187,6 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, () · 7 unmodified lines
174type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;187type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
175type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;188type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
176 189
177// struct FrameConsumer {
178// next_frame_callbacks: Vec<FrameCallback>,
179// task: Task<()>,
180// display_linker
181// }
182
183pub struct AppContext {190pub struct AppContext {
184 pub(crate) this: Weak<AppCell>,191 pub(crate) this: Weak<AppCell>,
185 pub(crate) platform: Rc<dyn Platform>,192 pub(crate) platform: Rc<dyn Platform>,
@@ -292,7 +299,7 @@ impl AppContext { · 106 unmodified lines
292 app299 app
293 }300 }
294 301
295 /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`302 /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
296 /// will be given 100ms to complete before exiting.303 /// will be given 100ms to complete before exiting.
297 pub fn shutdown(&mut self) {304 pub fn shutdown(&mut self) {
298 let mut futures = Vec::new();305 let mut futures = Vec::new();
@@ -314,10 +321,12 @@ impl AppContext { · 15 unmodified lines
314 }321 }
315 }322 }
316 323
324 /// Gracefully quit the application via the platform's standard routine.
317 pub fn quit(&mut self) {325 pub fn quit(&mut self) {
318 self.platform.quit();326 self.platform.quit();
319 }327 }
320 328
329 /// Get metadata about the app and platform.
321 pub fn app_metadata(&self) -> AppMetadata {330 pub fn app_metadata(&self) -> AppMetadata {
322 self.app_metadata.clone()331 self.app_metadata.clone()
323 }332 }
@@ -340,6 +349,7 @@ impl AppContext { · 16 unmodified lines
340 result349 result
341 }350 }
342 351
352 /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context.
343 pub fn observe<W, E>(353 pub fn observe<W, E>(
344 &mut self,354 &mut self,
345 entity: &E,355 entity: &E,
@@ -355,7 +365,7 @@ impl AppContext { · 9 unmodified lines
355 })365 })
356 }366 }
357 367
358 pub fn observe_internal<W, E>(368 pub(crate) fn observe_internal<W, E>(
359 &mut self,369 &mut self,
360 entity: &E,370 entity: &E,
361 mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,371 mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
@@ -380,15 +390,17 @@ impl AppContext { · 18 unmodified lines
380 subscription390 subscription
381 }391 }
382 392
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>(
384 &mut self,396 &mut self,
385 entity: &E,397 entity: &E,
386 mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,398 mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static,
387 ) -> Subscription399 ) -> Subscription
388 where400 where
389 T: 'static + EventEmitter<Evt>,401 T: 'static + EventEmitter<Event>,
390 E: Entity<T>,402 E: Entity<T>,
391 Evt: 'static,403 Event: 'static,
392 {404 {
393 self.subscribe_internal(entity, move |entity, event, cx| {405 self.subscribe_internal(entity, move |entity, event, cx| {
394 on_event(entity, event, cx);406 on_event(entity, event, cx);
@@ -426,6 +438,9 @@ impl AppContext { · 31 unmodified lines
426 subscription438 subscription
427 }439 }
428 440
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
429 pub fn windows(&self) -> Vec<AnyWindowHandle> {444 pub fn windows(&self) -> Vec<AnyWindowHandle> {
430 self.windows445 self.windows
431 .values()446 .values()
@@ -565,7 +580,7 @@ impl AppContext { · 133 unmodified lines
565 self.pending_effects.push_back(effect);580 self.pending_effects.push_back(effect);
566 }581 }
567 582
568 /// Called at the end of AppContext::update to complete any side effects583 /// Called at the end of [`AppContext::update`] to complete any side effects
569 /// such as notifying observers, emitting events, etc. Effects can themselves584 /// such as notifying observers, emitting events, etc. Effects can themselves
570 /// cause effects, so we continue looping until all effects are processed.585 /// cause effects, so we continue looping until all effects are processed.
571 fn flush_effects(&mut self) {586 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
82}82}
83 83
84impl AsyncAppContext {84impl AsyncAppContext {
85 /// Schedules all windows in the application to be redrawn.
85 pub fn refresh(&mut self) -> Result<()> {86 pub fn refresh(&mut self) -> Result<()> {
86 let app = self87 let app = self
87 .app88 .app
@@ -92,14 +93,17 @@ impl AsyncAppContext { · 4 unmodified lines
92 Ok(())93 Ok(())
93 }94 }
94 95
96 /// Get an executor which can be used to spawn futures in the background.
95 pub fn background_executor(&self) -> &BackgroundExecutor {97 pub fn background_executor(&self) -> &BackgroundExecutor {
96 &self.background_executor98 &self.background_executor
97 }99 }
98 100
101 /// Get an executor which can be used to spawn futures in the foreground.
99 pub fn foreground_executor(&self) -> &ForegroundExecutor {102 pub fn foreground_executor(&self) -> &ForegroundExecutor {
100 &self.foreground_executor103 &self.foreground_executor
101 }104 }
102 105
106 /// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
103 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {107 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
104 let app = self108 let app = self
105 .app109 .app
@@ -109,6 +113,7 @@ impl AsyncAppContext { · 3 unmodified lines
109 Ok(f(&mut lock))113 Ok(f(&mut lock))
110 }114 }
111 115
116 /// Open a window with the given options based on the root view returned by the given function.
112 pub fn open_window<V>(117 pub fn open_window<V>(
113 &self,118 &self,
114 options: crate::WindowOptions,119 options: crate::WindowOptions,
@@ -125,6 +130,7 @@ impl AsyncAppContext { · 10 unmodified lines
125 Ok(lock.open_window(options, build_root_view))130 Ok(lock.open_window(options, build_root_view))
126 }131 }
127 132
133 /// Schedule a future to be polled in the background.
128 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>134 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
129 where135 where
130 Fut: Future<Output = R> + 'static,136 Fut: Future<Output = R> + 'static,
crates/gpui/src/app/entity_map.rs+4−1
@@ -19,7 +19,10 @@ use std::{ · 18 unmodified lines
19#[cfg(any(test, feature = "test-support"))]19#[cfg(any(test, feature = "test-support"))]
20use collections::HashMap;20use collections::HashMap;
21 21
22slotmap::new_key_type! { pub struct EntityId; }22slotmap::new_key_type! {
23 /// A unique identifier for a model or view across the application.
24 pub struct EntityId;
25}
23 26
24impl EntityId {27impl EntityId {
25 pub fn as_u64(self) -> u64 {28 pub fn as_u64(self) -> u64 {
crates/gpui/src/app/test_context.rs+76−6
@@ -1,3 +1,5 @@
1#![deny(missing_docs)]
2
1use crate::{3use crate::{
2 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,4 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3 BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,5 BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
@@ -9,13 +11,19 @@ use anyhow::{anyhow, bail}; · 5 unmodified lines
9use futures::{Stream, StreamExt};11use futures::{Stream, StreamExt};
10use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};12use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
11 13
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.
12#[derive(Clone)]16#[derive(Clone)]
13pub struct TestAppContext {17pub struct TestAppContext {
18 #[doc(hidden)]
14 pub app: Rc<AppCell>,19 pub app: Rc<AppCell>,
20 #[doc(hidden)]
15 pub background_executor: BackgroundExecutor,21 pub background_executor: BackgroundExecutor,
22 #[doc(hidden)]
16 pub foreground_executor: ForegroundExecutor,23 pub foreground_executor: ForegroundExecutor,
24 #[doc(hidden)]
17 pub dispatcher: TestDispatcher,25 pub dispatcher: TestDispatcher,
18 pub test_platform: Rc<TestPlatform>,26 test_platform: Rc<TestPlatform>,
19 text_system: Arc<TextSystem>,27 text_system: Arc<TextSystem>,
20}28}
21 29
@@ -76,6 +84,7 @@ impl Context for TestAppContext { · 54 unmodified lines
76}84}
77 85
78impl TestAppContext {86impl TestAppContext {
87 /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
79 pub fn new(dispatcher: TestDispatcher) -> Self {88 pub fn new(dispatcher: TestDispatcher) -> Self {
80 let arc_dispatcher = Arc::new(dispatcher.clone());89 let arc_dispatcher = Arc::new(dispatcher.clone());
81 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());90 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
@@ -95,38 +104,47 @@ impl TestAppContext { · 13 unmodified lines
95 }104 }
96 }105 }
97 106
107 /// returns a new `TestAppContext` re-using the same executors to interleave tasks.
98 pub fn new_app(&self) -> TestAppContext {108 pub fn new_app(&self) -> TestAppContext {
99 Self::new(self.dispatcher.clone())109 Self::new(self.dispatcher.clone())
100 }110 }
101 111
112 /// Simulates quitting the app.
102 pub fn quit(&self) {113 pub fn quit(&self) {
103 self.app.borrow_mut().shutdown();114 self.app.borrow_mut().shutdown();
104 }115 }
105 116
117 /// Schedules all windows to be redrawn on the next effect cycle.
106 pub fn refresh(&mut self) -> Result<()> {118 pub fn refresh(&mut self) -> Result<()> {
107 let mut app = self.app.borrow_mut();119 let mut app = self.app.borrow_mut();
108 app.refresh();120 app.refresh();
109 Ok(())121 Ok(())
110 }122 }
111 123
124 /// Returns an executor (for running tasks in the background)
112 pub fn executor(&self) -> BackgroundExecutor {125 pub fn executor(&self) -> BackgroundExecutor {
113 self.background_executor.clone()126 self.background_executor.clone()
114 }127 }
115 128
129 /// Returns an executor (for running tasks on the main thread)
116 pub fn foreground_executor(&self) -> &ForegroundExecutor {130 pub fn foreground_executor(&self) -> &ForegroundExecutor {
117 &self.foreground_executor131 &self.foreground_executor
118 }132 }
119 133
134 /// Gives you an `&mut AppContext` for the duration of the closure
120 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {135 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
121 let mut cx = self.app.borrow_mut();136 let mut cx = self.app.borrow_mut();
122 cx.update(f)137 cx.update(f)
123 }138 }
124 139
140 /// Gives you an `&AppContext` for the duration of the closure
125 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {141 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
126 let cx = self.app.borrow();142 let cx = self.app.borrow();
127 f(&*cx)143 f(&*cx)
128 }144 }
129 145
146 /// Adds a new window. The Window will always be backed by a `TestWindow` which
147 /// can be retrieved with `self.test_window(handle)`
130 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>148 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
131 where149 where
132 F: FnOnce(&mut ViewContext<V>) -> V,150 F: FnOnce(&mut ViewContext<V>) -> V,
@@ -136,12 +154,16 @@ impl TestAppContext { · 3 unmodified lines
136 cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))154 cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
137 }155 }
138 156
157 /// Adds a new window with no content.
139 pub fn add_empty_window(&mut self) -> AnyWindowHandle {158 pub fn add_empty_window(&mut self) -> AnyWindowHandle {
140 let mut cx = self.app.borrow_mut();159 let mut cx = self.app.borrow_mut();
141 cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))160 cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
142 .any_handle161 .any_handle
143 }162 }
144 163
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(...);`
145 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)167 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
146 where168 where
147 F: FnOnce(&mut ViewContext<V>) -> V,169 F: FnOnce(&mut ViewContext<V>) -> V,
@@ -156,18 +178,23 @@ impl TestAppContext { · 8 unmodified lines
156 (view, Box::leak(cx))178 (view, Box::leak(cx))
157 }179 }
158 180
181 /// returns the TextSystem
159 pub fn text_system(&self) -> &Arc<TextSystem> {182 pub fn text_system(&self) -> &Arc<TextSystem> {
160 &self.text_system183 &self.text_system
161 }184 }
162 185
186 /// Simulates writing to the platform clipboard
163 pub fn write_to_clipboard(&self, item: ClipboardItem) {187 pub fn write_to_clipboard(&self, item: ClipboardItem) {
164 self.test_platform.write_to_clipboard(item)188 self.test_platform.write_to_clipboard(item)
165 }189 }
166 190
191 /// Simulates reading from the platform clipboard.
192 /// This will return the most recent value from `write_to_clipboard`.
167 pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {193 pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
168 self.test_platform.read_from_clipboard()194 self.test_platform.read_from_clipboard()
169 }195 }
170 196
197 /// Simulates choosing a File in the platform's "Open" dialog.
171 pub fn simulate_new_path_selection(198 pub fn simulate_new_path_selection(
172 &self,199 &self,
173 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,200 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
@@ -175,22 +202,27 @@ impl TestAppContext { · 1 unmodified line
175 self.test_platform.simulate_new_path_selection(select_path);202 self.test_platform.simulate_new_path_selection(select_path);
176 }203 }
177 204
205 /// Simulates clicking a button in an platform-level alert dialog.
178 pub fn simulate_prompt_answer(&self, button_ix: usize) {206 pub fn simulate_prompt_answer(&self, button_ix: usize) {
179 self.test_platform.simulate_prompt_answer(button_ix);207 self.test_platform.simulate_prompt_answer(button_ix);
180 }208 }
181 209
210 /// Returns true if there's an alert dialog open.
182 pub fn has_pending_prompt(&self) -> bool {211 pub fn has_pending_prompt(&self) -> bool {
183 self.test_platform.has_pending_prompt()212 self.test_platform.has_pending_prompt()
184 }213 }
185 214
215 /// Simulates the user resizing the window to the new size.
186 pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {216 pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
187 self.test_window(window_handle).simulate_resize(size);217 self.test_window(window_handle).simulate_resize(size);
188 }218 }
189 219
220 /// Returns all windows open in the test.
190 pub fn windows(&self) -> Vec<AnyWindowHandle> {221 pub fn windows(&self) -> Vec<AnyWindowHandle> {
191 self.app.borrow().windows().clone()222 self.app.borrow().windows().clone()
192 }223 }
193 224
225 /// Run the given task on the main thread.
194 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>226 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
195 where227 where
196 Fut: Future<Output = R> + 'static,228 Fut: Future<Output = R> + 'static,
@@ -199,16 +231,20 @@ impl TestAppContext { · 2 unmodified lines
199 self.foreground_executor.spawn(f(self.to_async()))231 self.foreground_executor.spawn(f(self.to_async()))
200 }232 }
201 233
234 /// true if the given global is defined
202 pub fn has_global<G: 'static>(&self) -> bool {235 pub fn has_global<G: 'static>(&self) -> bool {
203 let app = self.app.borrow();236 let app = self.app.borrow();
204 app.has_global::<G>()237 app.has_global::<G>()
205 }238 }
206 239
240 /// runs the given closure with a reference to the global
241 /// panics if `has_global` would return false.
207 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {242 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
208 let app = self.app.borrow();243 let app = self.app.borrow();
209 read(app.global(), &app)244 read(app.global(), &app)
210 }245 }
211 246
247 /// runs the given closure with a reference to the global (if set)
212 pub fn try_read_global<G: 'static, R>(248 pub fn try_read_global<G: 'static, R>(
213 &self,249 &self,
214 read: impl FnOnce(&G, &AppContext) -> R,250 read: impl FnOnce(&G, &AppContext) -> R,
@@ -217,11 +253,13 @@ impl TestAppContext { · 2 unmodified lines
217 Some(read(lock.try_global()?, &lock))253 Some(read(lock.try_global()?, &lock))
218 }254 }
219 255
256 /// sets the global in this context.
220 pub fn set_global<G: 'static>(&mut self, global: G) {257 pub fn set_global<G: 'static>(&mut self, global: G) {
221 let mut lock = self.app.borrow_mut();258 let mut lock = self.app.borrow_mut();
222 lock.set_global(global);259 lock.set_global(global);
223 }260 }
224 261
262 /// updates the global in this context. (panics if `has_global` would return false)
225 pub fn update_global<G: 'static, R>(263 pub fn update_global<G: 'static, R>(
226 &mut self,264 &mut self,
227 update: impl FnOnce(&mut G, &mut AppContext) -> R,265 update: impl FnOnce(&mut G, &mut AppContext) -> R,
@@ -230,6 +268,8 @@ impl TestAppContext { · 2 unmodified lines
230 lock.update_global(update)268 lock.update_global(update)
231 }269 }
232 270
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.
233 pub fn to_async(&self) -> AsyncAppContext {273 pub fn to_async(&self) -> AsyncAppContext {
234 AsyncAppContext {274 AsyncAppContext {
235 app: Rc::downgrade(&self.app),275 app: Rc::downgrade(&self.app),
@@ -238,10 +278,12 @@ impl TestAppContext { · 2 unmodified lines
238 }278 }
239 }279 }
240 280
281 /// Wait until there are no more pending tasks.
241 pub fn run_until_parked(&mut self) {282 pub fn run_until_parked(&mut self) {
242 self.background_executor.run_until_parked()283 self.background_executor.run_until_parked()
243 }284 }
244 285
286 /// Simulate dispatching an action to the currently focused node in the window.
245 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)287 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
246 where288 where
247 A: Action,289 A: Action,
@@ -255,7 +297,8 @@ impl TestAppContext { · 7 unmodified lines
255 297
256 /// simulate_keystrokes takes a space-separated list of keys to type.298 /// simulate_keystrokes takes a space-separated list of keys to type.
257 /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")299 /// 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.
259 pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {302 pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
260 for keystroke in keystrokes303 for keystroke in keystrokes
261 .split(" ")304 .split(" ")
@@ -270,7 +313,8 @@ impl TestAppContext { · 8 unmodified lines
270 313
271 /// simulate_input takes a string of text to type.314 /// simulate_input takes a string of text to type.
272 /// cx.simulate_input("abc")315 /// 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.
274 pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {318 pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
275 for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {319 for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
276 self.dispatch_keystroke(window, keystroke.into(), false);320 self.dispatch_keystroke(window, keystroke.into(), false);
@@ -279,6 +323,7 @@ impl TestAppContext { · 2 unmodified lines
279 self.background_executor.run_until_parked()323 self.background_executor.run_until_parked()
280 }324 }
281 325
326 /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
282 pub fn dispatch_keystroke(327 pub fn dispatch_keystroke(
283 &mut self,328 &mut self,
284 window: AnyWindowHandle,329 window: AnyWindowHandle,
@@ -289,6 +334,7 @@ impl TestAppContext { · 4 unmodified lines
289 .simulate_keystroke(keystroke, is_held)334 .simulate_keystroke(keystroke, is_held)
290 }335 }
291 336
337 /// Returns the `TestWindow` backing the given handle.
292 pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {338 pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
293 self.app339 self.app
294 .borrow_mut()340 .borrow_mut()
@@ -303,6 +349,7 @@ impl TestAppContext { · 8 unmodified lines
303 .clone()349 .clone()
304 }350 }
305 351
352 /// Returns a stream of notifications whenever the View or Model is updated.
306 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {353 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
307 let (tx, rx) = futures::channel::mpsc::unbounded();354 let (tx, rx) = futures::channel::mpsc::unbounded();
308 self.update(|cx| {355 self.update(|cx| {
@@ -319,6 +366,7 @@ impl TestAppContext { · 10 unmodified lines
319 rx366 rx
320 }367 }
321 368
369 /// Retuens a stream of events emitted by the given Model.
322 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(370 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
323 &mut self,371 &mut self,
324 entity: &Model<T>,372 entity: &Model<T>,
@@ -337,6 +385,8 @@ impl TestAppContext { · 12 unmodified lines
337 rx385 rx
338 }386 }
339 387
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).
340 pub async fn condition<T: 'static>(390 pub async fn condition<T: 'static>(
341 &mut self,391 &mut self,
342 model: &Model<T>,392 model: &Model<T>,
@@ -366,6 +416,7 @@ impl TestAppContext { · 23 unmodified lines
366}416}
367 417
368impl<T: Send> Model<T> {418impl<T: Send> Model<T> {
419 /// Block until the next event is emitted by the model, then return it.
369 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt420 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
370 where421 where
371 Evt: Send + Clone + 'static,422 Evt: Send + Clone + 'static,
@@ -395,6 +446,7 @@ impl<T: Send> Model<T> { · 23 unmodified lines
395}446}
396 447
397impl<V: 'static> View<V> {448impl<V: 'static> View<V> {
449 /// Returns a future that resolves when the view is next updated.
398 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {450 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
399 use postage::prelude::{Sink as _, Stream as _};451 use postage::prelude::{Sink as _, Stream as _};
400 452
@@ -421,6 +473,7 @@ impl<V: 'static> View<V> { · 20 unmodified lines
421}473}
422 474
423impl<V> View<V> {475impl<V> View<V> {
476 /// Returns a future that resolves when the condition becomes true.
424 pub fn condition<Evt>(477 pub fn condition<Evt>(
425 &self,478 &self,
426 cx: &TestAppContext,479 cx: &TestAppContext,
@@ -471,12 +524,11 @@ impl<V> View<V> { · 44 unmodified lines
471 }524 }
472 }525 }
473 526
474 // todo!(start_waiting)527 cx.borrow().background_executor().start_waiting();
475 // cx.borrow().foreground_executor().start_waiting();
476 rx.recv()528 rx.recv()
477 .await529 .await
478 .expect("view dropped with pending condition");530 .expect("view dropped with pending condition");
479 // cx.borrow().foreground_executor().finish_waiting();531 cx.borrow().background_executor().finish_waiting();
480 }532 }
481 })533 })
482 .await534 .await
@@ -488,6 +540,8 @@ impl<V> View<V> { · 5 unmodified lines
488 540
489use derive_more::{Deref, DerefMut};541use derive_more::{Deref, DerefMut};
490#[derive(Deref, DerefMut, Clone)]542#[derive(Deref, DerefMut, Clone)]
543/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
544/// run window-specific test code.
491pub struct VisualTestContext {545pub struct VisualTestContext {
492 #[deref]546 #[deref]
493 #[deref_mut]547 #[deref_mut]
@@ -496,10 +550,14 @@ pub struct VisualTestContext { · 2 unmodified lines
496}550}
497 551
498impl<'a> VisualTestContext {552impl<'a> VisualTestContext {
553 /// Provides the `WindowContext` for the duration of the closure.
499 pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {554 pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
500 self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()555 self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
501 }556 }
502 557
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);`
503 pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {561 pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
504 Self {562 Self {
505 cx: cx.clone(),563 cx: cx.clone(),
@@ -507,10 +565,12 @@ impl<'a> VisualTestContext { · 1 unmodified line
507 }565 }
508 }566 }
509 567
568 /// Wait until there are no more pending tasks.
510 pub fn run_until_parked(&self) {569 pub fn run_until_parked(&self) {
511 self.cx.background_executor.run_until_parked();570 self.cx.background_executor.run_until_parked();
512 }571 }
513 572
573 /// Dispatch the action to the currently focused node.
514 pub fn dispatch_action<A>(&mut self, action: A)574 pub fn dispatch_action<A>(&mut self, action: A)
515 where575 where
516 A: Action,576 A: Action,
@@ -518,24 +578,32 @@ impl<'a> VisualTestContext { · 1 unmodified line
518 self.cx.dispatch_action(self.window, action)578 self.cx.dispatch_action(self.window, action)
519 }579 }
520 580
581 /// Read the title off the window (set by `WindowContext#set_window_title`)
521 pub fn window_title(&mut self) -> Option<String> {582 pub fn window_title(&mut self) -> Option<String> {
522 self.cx.test_window(self.window).0.lock().title.clone()583 self.cx.test_window(self.window).0.lock().title.clone()
523 }584 }
524 585
586 /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
587 /// Automatically runs until parked.
525 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {588 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
526 self.cx.simulate_keystrokes(self.window, keystrokes)589 self.cx.simulate_keystrokes(self.window, keystrokes)
527 }590 }
528 591
592 /// Simulate typing text `cx.simulate_input("hello")`
593 /// Automatically runs until parked.
529 pub fn simulate_input(&mut self, input: &str) {594 pub fn simulate_input(&mut self, input: &str) {
530 self.cx.simulate_input(self.window, input)595 self.cx.simulate_input(self.window, input)
531 }596 }
532 597
598 /// Simulates the user blurring the window.
533 pub fn deactivate_window(&mut self) {599 pub fn deactivate_window(&mut self) {
534 if Some(self.window) == self.test_platform.active_window() {600 if Some(self.window) == self.test_platform.active_window() {
535 self.test_platform.set_active_window(None)601 self.test_platform.set_active_window(None)
536 }602 }
537 self.background_executor.run_until_parked();603 self.background_executor.run_until_parked();
538 }604 }
605
606 /// Simulates the user closing the window.
539 /// Returns true if the window was closed.607 /// Returns true if the window was closed.
540 pub fn simulate_close(&mut self) -> bool {608 pub fn simulate_close(&mut self) -> bool {
541 let handler = self609 let handler = self
@@ -672,6 +740,7 @@ impl VisualContext for VisualTestContext { · 130 unmodified lines
672}740}
673 741
674impl AnyWindowHandle {742impl AnyWindowHandle {
743 /// Creates the given view in this window.
675 pub fn build_view<V: Render + 'static>(744 pub fn build_view<V: Render + 'static>(
676 &self,745 &self,
677 cx: &mut TestAppContext,746 cx: &mut TestAppContext,
@@ -681,6 +750,7 @@ impl AnyWindowHandle { · 3 unmodified lines
681 }750 }
682}751}
683 752
753/// An EmptyView for testing.
684pub struct EmptyView {}754pub struct EmptyView {}
685 755
686impl Render for EmptyView {756impl Render for EmptyView {
crates/gpui/src/element.rs+5−5
@@ -31,14 +31,14 @@ pub trait IntoElement: Sized { · 30 unmodified lines
31 /// The specific type of element into which the implementing type is converted.31 /// The specific type of element into which the implementing type is converted.
32 type Element: Element;32 type Element: Element;
33 33
34 /// The [ElementId] of self once converted into an [Element].34 /// The [`ElementId`] of self once converted into an [`Element`].
35 /// If present, the resulting element's state will be carried across frames.35 /// If present, the resulting element's state will be carried across frames.
36 fn element_id(&self) -> Option<ElementId>;36 fn element_id(&self) -> Option<ElementId>;
37 37
38 /// Convert self into a type that implements [Element].38 /// Convert self into a type that implements [`Element`].
39 fn into_element(self) -> Self::Element;39 fn into_element(self) -> Self::Element;
40 40
41 /// Convert self into a dynamically-typed [AnyElement].41 /// Convert self into a dynamically-typed [`AnyElement`].
42 fn into_any_element(self) -> AnyElement {42 fn into_any_element(self) -> AnyElement {
43 self.into_element().into_any()43 self.into_element().into_any()
44 }44 }
@@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { · 70 unmodified lines
115 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;115 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
116}116}
117 117
118/// You can derive [IntoElement] on any type that implements this trait.118/// You can derive [`IntoElement`] on any type that implements this trait.
119/// It is used to allow views to be expressed in terms of abstract data.119/// It is used to allow views to be expressed in terms of abstract data.
120pub trait RenderOnce: 'static {120pub trait RenderOnce: 'static {
121 fn render(self, cx: &mut WindowContext) -> impl IntoElement;121 fn render(self, cx: &mut WindowContext) -> impl IntoElement;
@@ -224,7 +224,7 @@ enum ElementDrawPhase<S> { · 102 unmodified lines
224 },224 },
225}225}
226 226
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.
228impl<E: Element> DrawableElement<E> {228impl<E: Element> DrawableElement<E> {
229 fn new(element: E) -> Self {229 fn new(element: E) -> Self {
230 DrawableElement {230 DrawableElement {
crates/gpui/src/elements/img.rs+4−4
@@ -2,7 +2,7 @@ use std::sync::Arc; · 1 unmodified line
2 2
3use crate::{3use crate::{
4 point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,4 point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
5 InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,5 InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
6 StyleRefinement, Styled, WindowContext,6 StyleRefinement, Styled, WindowContext,
7};7};
8use futures::FutureExt;8use futures::FutureExt;
@@ -12,13 +12,13 @@ use util::ResultExt; · 3 unmodified lines
12#[derive(Clone, Debug)]12#[derive(Clone, Debug)]
13pub enum ImageSource {13pub enum ImageSource {
14 /// Image content will be loaded from provided URI at render time.14 /// Image content will be loaded from provided URI at render time.
15 Uri(SharedString),15 Uri(SharedUrl),
16 Data(Arc<ImageData>),16 Data(Arc<ImageData>),
17 Surface(CVImageBuffer),17 Surface(CVImageBuffer),
18}18}
19 19
20impl From<SharedString> for ImageSource {20impl From<SharedUrl> for ImageSource {
21 fn from(value: SharedString) -> Self {21 fn from(value: SharedUrl) -> Self {
22 Self::Uri(value)22 Self::Uri(value)
23 }23 }
24}24}
crates/gpui/src/elements/overlay.rs+1−1
@@ -14,8 +14,8 @@ pub struct Overlay { · 13 unmodified lines
14 children: SmallVec<[AnyElement; 2]>,14 children: SmallVec<[AnyElement; 2]>,
15 anchor_corner: AnchorCorner,15 anchor_corner: AnchorCorner,
16 fit_mode: OverlayFitMode,16 fit_mode: OverlayFitMode,
17 // todo!();
18 anchor_position: Option<Point<Pixels>>,17 anchor_position: Option<Point<Pixels>>,
18 // todo!();
19 // position_mode: OverlayPositionMode,19 // position_mode: OverlayPositionMode,
20}20}
21 21
crates/gpui/src/executor.rs+42−2
@@ -32,6 +32,12 @@ pub struct ForegroundExecutor { · 31 unmodified lines
32 not_send: PhantomData<Rc<()>>,32 not_send: PhantomData<Rc<()>>,
33}33}
34 34
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.
35#[must_use]41#[must_use]
36#[derive(Debug)]42#[derive(Debug)]
37pub enum Task<T> {43pub enum Task<T> {
@@ -40,10 +46,12 @@ pub enum Task<T> { · 2 unmodified lines
40}46}
41 47
42impl<T> Task<T> {48impl<T> Task<T> {
49 /// Create a new task that will resolve with the value
43 pub fn ready(val: T) -> Self {50 pub fn ready(val: T) -> Self {
44 Task::Ready(Some(val))51 Task::Ready(Some(val))
45 }52 }
46 53
54 /// Detaching a task runs it to completion in the background
47 pub fn detach(self) {55 pub fn detach(self) {
48 match self {56 match self {
49 Task::Ready(_) => {}57 Task::Ready(_) => {}
@@ -57,6 +65,8 @@ where · 7 unmodified lines
57 T: 'static,65 T: 'static,
58 E: 'static + Debug,66 E: 'static + Debug,
59{67{
68 /// Run the task to completion in the background and log any
69 /// errors that occur.
60 #[track_caller]70 #[track_caller]
61 pub fn detach_and_log_err(self, cx: &mut AppContext) {71 pub fn detach_and_log_err(self, cx: &mut AppContext) {
62 let location = core::panic::Location::caller();72 let location = core::panic::Location::caller();
@@ -97,6 +107,10 @@ type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>; · 34 unmodified lines
97 107
98type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;108type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
99 109
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.
100impl BackgroundExecutor {114impl BackgroundExecutor {
101 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {115 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
102 Self { dispatcher }116 Self { dispatcher }
@@ -135,6 +149,7 @@ impl BackgroundExecutor { · 32 unmodified lines
135 Task::Spawned(task)149 Task::Spawned(task)
136 }150 }
137 151
152 /// Used by the test harness to run an async test in a syncronous fashion.
138 #[cfg(any(test, feature = "test-support"))]153 #[cfg(any(test, feature = "test-support"))]
139 #[track_caller]154 #[track_caller]
140 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {155 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@@ -145,6 +160,8 @@ impl BackgroundExecutor { · 4 unmodified lines
145 }160 }
146 }161 }
147 162
163 /// Block the current thread until the given future resolves.
164 /// Consider using `block_with_timeout` instead.
148 pub fn block<R>(&self, future: impl Future<Output = R>) -> R {165 pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
149 if let Ok(value) = self.block_internal(true, future, usize::MAX) {166 if let Ok(value) = self.block_internal(true, future, usize::MAX) {
150 value167 value
@@ -206,6 +223,8 @@ impl BackgroundExecutor { · 55 unmodified lines
206 }223 }
207 }224 }
208 225
226 /// Block the current thread until the given future resolves
227 /// or `duration` has elapsed.
209 pub fn block_with_timeout<R>(228 pub fn block_with_timeout<R>(
210 &self,229 &self,
211 duration: Duration,230 duration: Duration,
@@ -238,6 +257,8 @@ impl BackgroundExecutor { · 26 unmodified lines
238 }257 }
239 }258 }
240 259
260 /// Scoped lets you start a number of tasks and waits
261 /// for all of them to complete before returning.
241 pub async fn scoped<'scope, F>(&self, scheduler: F)262 pub async fn scoped<'scope, F>(&self, scheduler: F)
242 where263 where
243 F: FnOnce(&mut Scope<'scope>),264 F: FnOnce(&mut Scope<'scope>),
@@ -253,6 +274,9 @@ impl BackgroundExecutor { · 9 unmodified lines
253 }274 }
254 }275 }
255 276
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.
256 pub fn timer(&self, duration: Duration) -> Task<()> {280 pub fn timer(&self, duration: Duration) -> Task<()> {
257 let (runnable, task) = async_task::spawn(async move {}, {281 let (runnable, task) = async_task::spawn(async move {}, {
258 let dispatcher = self.dispatcher.clone();282 let dispatcher = self.dispatcher.clone();
@@ -262,65 +286,81 @@ impl BackgroundExecutor { · 3 unmodified lines
262 Task::Spawned(task)286 Task::Spawned(task)
263 }287 }
264 288
289 /// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
265 #[cfg(any(test, feature = "test-support"))]290 #[cfg(any(test, feature = "test-support"))]
266 pub fn start_waiting(&self) {291 pub fn start_waiting(&self) {
267 self.dispatcher.as_test().unwrap().start_waiting();292 self.dispatcher.as_test().unwrap().start_waiting();
268 }293 }
269 294
295 /// in tests, removes the debugging data added by start_waiting
270 #[cfg(any(test, feature = "test-support"))]296 #[cfg(any(test, feature = "test-support"))]
271 pub fn finish_waiting(&self) {297 pub fn finish_waiting(&self) {
272 self.dispatcher.as_test().unwrap().finish_waiting();298 self.dispatcher.as_test().unwrap().finish_waiting();
273 }299 }
274 300
301 /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
275 #[cfg(any(test, feature = "test-support"))]302 #[cfg(any(test, feature = "test-support"))]
276 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {303 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
277 self.dispatcher.as_test().unwrap().simulate_random_delay()304 self.dispatcher.as_test().unwrap().simulate_random_delay()
278 }305 }
279 306
307 /// in tests, indicate that a given task from `spawn_labeled` should run after everything else
280 #[cfg(any(test, feature = "test-support"))]308 #[cfg(any(test, feature = "test-support"))]
281 pub fn deprioritize(&self, task_label: TaskLabel) {309 pub fn deprioritize(&self, task_label: TaskLabel) {
282 self.dispatcher.as_test().unwrap().deprioritize(task_label)310 self.dispatcher.as_test().unwrap().deprioritize(task_label)
283 }311 }
284 312
313 /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
285 #[cfg(any(test, feature = "test-support"))]314 #[cfg(any(test, feature = "test-support"))]
286 pub fn advance_clock(&self, duration: Duration) {315 pub fn advance_clock(&self, duration: Duration) {
287 self.dispatcher.as_test().unwrap().advance_clock(duration)316 self.dispatcher.as_test().unwrap().advance_clock(duration)
288 }317 }
289 318
319 /// in tests, run one task.
290 #[cfg(any(test, feature = "test-support"))]320 #[cfg(any(test, feature = "test-support"))]
291 pub fn tick(&self) -> bool {321 pub fn tick(&self) -> bool {
292 self.dispatcher.as_test().unwrap().tick(false)322 self.dispatcher.as_test().unwrap().tick(false)
293 }323 }
294 324
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`)
295 #[cfg(any(test, feature = "test-support"))]327 #[cfg(any(test, feature = "test-support"))]
296 pub fn run_until_parked(&self) {328 pub fn run_until_parked(&self) {
297 self.dispatcher.as_test().unwrap().run_until_parked()329 self.dispatcher.as_test().unwrap().run_until_parked()
298 }330 }
299 331
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.
300 #[cfg(any(test, feature = "test-support"))]335 #[cfg(any(test, feature = "test-support"))]
301 pub fn allow_parking(&self) {336 pub fn allow_parking(&self) {
302 self.dispatcher.as_test().unwrap().allow_parking();337 self.dispatcher.as_test().unwrap().allow_parking();
303 }338 }
304 339
340 /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
305 #[cfg(any(test, feature = "test-support"))]341 #[cfg(any(test, feature = "test-support"))]
306 pub fn rng(&self) -> StdRng {342 pub fn rng(&self) -> StdRng {
307 self.dispatcher.as_test().unwrap().rng()343 self.dispatcher.as_test().unwrap().rng()
308 }344 }
309 345
346 /// How many CPUs are available to the dispatcher
310 pub fn num_cpus(&self) -> usize {347 pub fn num_cpus(&self) -> usize {
311 num_cpus::get()348 num_cpus::get()
312 }349 }
313 350
351 /// Whether we're on the main thread.
314 pub fn is_main_thread(&self) -> bool {352 pub fn is_main_thread(&self) -> bool {
315 self.dispatcher.is_main_thread()353 self.dispatcher.is_main_thread()
316 }354 }
317 355
318 #[cfg(any(test, feature = "test-support"))]356 #[cfg(any(test, feature = "test-support"))]
357 /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
319 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {358 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
320 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);359 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
321 }360 }
322}361}
323 362
363/// ForegroundExecutor runs things on the main thread.
324impl ForegroundExecutor {364impl ForegroundExecutor {
325 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {365 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
326 Self {366 Self {
@@ -329,8 +369,7 @@ impl ForegroundExecutor { · 2 unmodified lines
329 }369 }
330 }370 }
331 371
332 /// Enqueues the given closure to be run on any thread. The closure returns372 /// Enqueues the given Task to run on the main thread at some point in the future.
333 /// a future which will be run to completion on any available thread.
334 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>373 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
335 where374 where
336 R: 'static,375 R: 'static,
@@ -350,6 +389,7 @@ impl ForegroundExecutor { · 13 unmodified lines
350 }389 }
351}390}
352 391
392/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
353pub struct Scope<'a> {393pub struct Scope<'a> {
354 executor: BackgroundExecutor,394 executor: BackgroundExecutor,
355 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,395 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
18pub mod prelude;18pub mod prelude;
19mod scene;19mod scene;
20mod shared_string;20mod shared_string;
21mod shared_url;
21mod style;22mod style;
22mod styled;23mod styled;
23mod subscription;24mod subscription;
@@ -67,6 +68,7 @@ pub use refineable::*; · 43 unmodified lines
67pub use scene::*;68pub use scene::*;
68use seal::Sealed;69use seal::Sealed;
69pub use shared_string::*;70pub use shared_string::*;
71pub use shared_url::*;
70pub use smol::Timer;72pub use smol::Timer;
71pub use style::*;73pub use style::*;
72pub use styled::*;74pub use styled::*;
crates/gpui/src/image_cache.rs+3−3
@@ -1,4 +1,4 @@
1use crate::{ImageData, ImageId, SharedString};1use crate::{ImageData, ImageId, SharedUrl};
2use collections::HashMap;2use collections::HashMap;
3use futures::{3use futures::{
4 future::{BoxFuture, Shared},4 future::{BoxFuture, Shared},
@@ -44,7 +44,7 @@ impl From<ImageError> for Error { · 39 unmodified lines
44 44
45pub struct ImageCache {45pub struct ImageCache {
46 client: Arc<dyn HttpClient>,46 client: Arc<dyn HttpClient>,
47 images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,47 images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
48}48}
49 49
50type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;50type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@@ -59,7 +59,7 @@ impl ImageCache { · 8 unmodified lines
59 59
60 pub fn get(60 pub fn get(
61 &self,61 &self,
62 uri: impl Into<SharedString>,62 uri: impl Into<SharedUrl>,
63 ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {63 ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
64 let uri = uri.into();64 let uri = uri.into();
65 let mut images = self.images.lock();65 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
34 ) -> Option<Bounds<Pixels>>;34 ) -> Option<Bounds<Pixels>>;
35}35}
36 36
37/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input`37/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`]
38/// with an instance during your element's paint.38/// with an instance during your element's paint.
39pub struct ElementInputHandler<V> {39pub struct ElementInputHandler<V> {
40 view: View<V>,40 view: View<V>,
crates/gpui/src/interactive.rs+14−0
@@ -178,6 +178,20 @@ impl ScrollDelta { · 177 unmodified lines
178 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),178 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
179 }179 }
180 }180 }
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 }
181}195}
182 196
183#[derive(Clone, Debug, Default)]197#[derive(Clone, Debug, Default)]
crates/gpui/src/key_dispatch.rs+75−2
@@ -192,8 +192,8 @@ impl DispatchTree { · 191 unmodified lines
192 keymap192 keymap
193 .bindings_for_action(action)193 .bindings_for_action(action)
194 .filter(|binding| {194 .filter(|binding| {
195 for i in 1..context_stack.len() {195 for i in 0..context_stack.len() {
196 let context = &context_stack[0..i];196 let context = &context_stack[0..=i];
197 if keymap.binding_enabled(binding, context) {197 if keymap.binding_enabled(binding, context) {
198 return true;198 return true;
199 }199 }
@@ -283,3 +283,76 @@ impl DispatchTree { · 83 unmodified lines
283 *self.node_stack.last().unwrap()283 *self.node_stack.last().unwrap()
284 }284 }
285}285}
286
287#[cfg(test)]
288mod 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
14unsafe impl Send for MacDisplay {}14unsafe impl Send for MacDisplay {}
15 15
16impl MacDisplay {16impl MacDisplay {
17 /// Get the screen with the given [DisplayId].17 /// Get the screen with the given [`DisplayId`].
18 pub fn find_by_id(id: DisplayId) -> Option<Self> {18 pub fn find_by_id(id: DisplayId) -> Option<Self> {
19 Self::all().find(|screen| screen.id() == id)19 Self::all().find(|screen| screen.id() == id)
20 }20 }
21 21
22 /// Get the screen with the given persistent [Uuid].22 /// Get the screen with the given persistent [`Uuid`].
23 pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {23 pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
24 Self::all().find(|screen| screen.uuid().ok() == Some(uuid))24 Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
25 }25 }
crates/gpui/src/platform/test/display.rs+1−1
@@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay { · 31 unmodified lines
32 }32 }
33 33
34 fn as_any(&self) -> &dyn std::any::Any {34 fn as_any(&self) -> &dyn std::any::Any {
35 todo!()35 unimplemented!()
36 }36 }
37 37
38 fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {38 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
15 time::Duration,15 time::Duration,
16};16};
17 17
18/// TestPlatform implements the Platform trait for use in tests.
18pub struct TestPlatform {19pub struct TestPlatform {
19 background_executor: BackgroundExecutor,20 background_executor: BackgroundExecutor,
20 foreground_executor: ForegroundExecutor,21 foreground_executor: ForegroundExecutor,
@@ -103,7 +104,6 @@ impl TestPlatform { · 82 unmodified lines
103 }104 }
104}105}
105 106
106// todo!("implement out what our tests needed in GPUI 1")
107impl Platform for TestPlatform {107impl Platform for TestPlatform {
108 fn background_executor(&self) -> BackgroundExecutor {108 fn background_executor(&self) -> BackgroundExecutor {
109 self.background_executor.clone()109 self.background_executor.clone()
crates/gpui/src/shared_url.rs+25−0
@@ -0,0 +1,25 @@
1use derive_more::{Deref, DerefMut};
2
3use crate::SharedString;
4
5/// A [`SharedString`] containing a URL.
6#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)]
7pub struct SharedUrl(SharedString);
8
9impl 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
15impl 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
21impl<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
37 })))37 })))
38 }38 }
39 39
40 /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions40 /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions
41 /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.41 /// 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 latter42 /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter
43 /// to activate the `[Subscription]`.43 /// to activate the [`Subscription`].
44 #[must_use]44 #[must_use]
45 pub fn insert(45 pub fn insert(
46 &self,46 &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//! ```
1use crate::{Entity, Subscription, TestAppContext, TestDispatcher};28use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
2use futures::StreamExt as _;29use futures::StreamExt as _;
3use rand::prelude::*;30use rand::prelude::*;
@@ -68,6 +95,7 @@ impl<T: 'static> futures::Stream for Observation<T> { · 64 unmodified lines
68 }95 }
69}96}
70 97
98/// observe returns a stream of the change events from the given `View` or `Model`
71pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {99pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
72 let (tx, rx) = smol::channel::unbounded();100 let (tx, rx) = smol::channel::unbounded();
73 let _subscription = cx.update(|cx| {101 let _subscription = cx.update(|cx| {
crates/gpui/src/window.rs+142−12
@@ -1,3 +1,5 @@
1#![deny(missing_docs)]
2
1use crate::{3use crate::{
2 px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef,4 px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef,
3 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,5 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
@@ -85,10 +87,12 @@ pub enum DispatchPhase { · 81 unmodified lines
85}87}
86 88
87impl DispatchPhase {89impl DispatchPhase {
90 /// Returns true if this represents the "bubble" phase.
88 pub fn bubble(self) -> bool {91 pub fn bubble(self) -> bool {
89 self == DispatchPhase::Bubble92 self == DispatchPhase::Bubble
90 }93 }
91 94
95 /// Returns true if this represents the "capture" phase.
92 pub fn capture(self) -> bool {96 pub fn capture(self) -> bool {
93 self == DispatchPhase::Capture97 self == DispatchPhase::Capture
94 }98 }
@@ -103,7 +107,10 @@ struct FocusEvent { · 8 unmodified lines
103 current_focus_path: SmallVec<[FocusId; 8]>,107 current_focus_path: SmallVec<[FocusId; 8]>,
104}108}
105 109
106slotmap::new_key_type! { pub struct FocusId; }110slotmap::new_key_type! {
111 /// A globally unique identifier for a focusable element.
112 pub struct FocusId;
113}
107 114
108thread_local! {115thread_local! {
109 pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));116 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
231/// FocusableView allows users of your view to easily238/// FocusableView allows users of your view to easily
232/// focus it (using cx.focus_view(view))239/// focus it (using cx.focus_view(view))
233pub trait FocusableView: 'static + Render {240pub trait FocusableView: 'static + Render {
241 /// Returns the focus handle associated with this view.
234 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;242 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
235}243}
236 244
@@ -240,9 +248,11 @@ pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {} · 3 unmodified lines
240 248
241impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}249impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
242 250
251/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
243pub struct DismissEvent;252pub struct DismissEvent;
244 253
245// Holds the state for a specific window.254// Holds the state for a specific window.
255#[doc(hidden)]
246pub struct Window {256pub struct Window {
247 pub(crate) handle: AnyWindowHandle,257 pub(crate) handle: AnyWindowHandle,
248 pub(crate) removed: bool,258 pub(crate) removed: bool,
@@ -434,6 +444,7 @@ impl Window { · 185 unmodified lines
434#[derive(Clone, Debug, Default, PartialEq, Eq)]444#[derive(Clone, Debug, Default, PartialEq, Eq)]
435#[repr(C)]445#[repr(C)]
436pub struct ContentMask<P: Clone + Default + Debug> {446pub struct ContentMask<P: Clone + Default + Debug> {
447 /// The bounds
437 pub bounds: Bounds<P>,448 pub bounds: Bounds<P>,
438}449}
439 450
@@ -453,8 +464,8 @@ impl ContentMask<Pixels> { · 13 unmodified lines
453}464}
454 465
455/// Provides access to application state in the context of a single window. Derefs466/// 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 takes467/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes
457/// an `AppContext` and call any `AppContext` methods.468/// an [`AppContext`] and call any [`AppContext`] methods.
458pub struct WindowContext<'a> {469pub struct WindowContext<'a> {
459 pub(crate) app: &'a mut AppContext,470 pub(crate) app: &'a mut AppContext,
460 pub(crate) window: &'a mut Window,471 pub(crate) window: &'a mut Window,
@@ -482,20 +493,20 @@ impl<'a> WindowContext<'a> { · 21 unmodified lines
482 self.window.removed = true;493 self.window.removed = true;
483 }494 }
484 495
485 /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus496 /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
486 /// for elements rendered within this window.497 /// for elements rendered within this window.
487 pub fn focus_handle(&mut self) -> FocusHandle {498 pub fn focus_handle(&mut self) -> FocusHandle {
488 FocusHandle::new(&self.window.focus_handles)499 FocusHandle::new(&self.window.focus_handles)
489 }500 }
490 501
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`.
492 pub fn focused(&self) -> Option<FocusHandle> {503 pub fn focused(&self) -> Option<FocusHandle> {
493 self.window504 self.window
494 .focus505 .focus
495 .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))506 .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))
496 }507 }
497 508
498 /// Move focus to the element associated with the given `FocusHandle`.509 /// Move focus to the element associated with the given [`FocusHandle`].
499 pub fn focus(&mut self, handle: &FocusHandle) {510 pub fn focus(&mut self, handle: &FocusHandle) {
500 if !self.window.focus_enabled || self.window.focus == Some(handle.id) {511 if !self.window.focus_enabled || self.window.focus == Some(handle.id) {
501 return;512 return;
@@ -525,11 +536,13 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines
525 self.notify();536 self.notify();
526 }537 }
527 538
539 /// Blur the window and don't allow anything in it to be focused again.
528 pub fn disable_focus(&mut self) {540 pub fn disable_focus(&mut self) {
529 self.blur();541 self.blur();
530 self.window.focus_enabled = false;542 self.window.focus_enabled = false;
531 }543 }
532 544
545 /// Dispatch the given action on the currently focused element.
533 pub fn dispatch_action(&mut self, action: Box<dyn Action>) {546 pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
534 let focus_handle = self.focused();547 let focus_handle = self.focused();
535 548
@@ -591,6 +604,9 @@ impl<'a> WindowContext<'a> { · 55 unmodified lines
591 });604 });
592 }605 }
593 606
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.
594 pub fn subscribe<Emitter, E, Evt>(610 pub fn subscribe<Emitter, E, Evt>(
595 &mut self,611 &mut self,
596 entity: &E,612 entity: &E,
@@ -754,6 +770,9 @@ impl<'a> WindowContext<'a> { · 157 unmodified lines
754 .request_measured_layout(style, rem_size, measure)770 .request_measured_layout(style, rem_size, measure)
755 }771 }
756 772
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.
757 pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {776 pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
758 let mut layout_engine = self.window.layout_engine.take().unwrap();777 let mut layout_engine = self.window.layout_engine.take().unwrap();
759 layout_engine.compute_layout(layout_id, available_space, self);778 layout_engine.compute_layout(layout_id, available_space, self);
@@ -788,30 +807,37 @@ impl<'a> WindowContext<'a> { · 28 unmodified lines
788 .retain(&(), |callback| callback(self));807 .retain(&(), |callback| callback(self));
789 }808 }
790 809
810 /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
791 pub fn window_bounds(&self) -> WindowBounds {811 pub fn window_bounds(&self) -> WindowBounds {
792 self.window.bounds812 self.window.bounds
793 }813 }
794 814
815 /// Returns the size of the drawable area within the window.
795 pub fn viewport_size(&self) -> Size<Pixels> {816 pub fn viewport_size(&self) -> Size<Pixels> {
796 self.window.viewport_size817 self.window.viewport_size
797 }818 }
798 819
820 /// Returns whether this window is focused by the operating system (receiving key events).
799 pub fn is_window_active(&self) -> bool {821 pub fn is_window_active(&self) -> bool {
800 self.window.active822 self.window.active
801 }823 }
802 824
825 /// Toggle zoom on the window.
803 pub fn zoom_window(&self) {826 pub fn zoom_window(&self) {
804 self.window.platform_window.zoom();827 self.window.platform_window.zoom();
805 }828 }
806 829
830 /// Update the window's title at the platform level.
807 pub fn set_window_title(&mut self, title: &str) {831 pub fn set_window_title(&mut self, title: &str) {
808 self.window.platform_window.set_title(title);832 self.window.platform_window.set_title(title);
809 }833 }
810 834
835 /// Mark the window as dirty at the platform level.
811 pub fn set_window_edited(&mut self, edited: bool) {836 pub fn set_window_edited(&mut self, edited: bool) {
812 self.window.platform_window.set_edited(edited);837 self.window.platform_window.set_edited(edited);
813 }838 }
814 839
840 /// Determine the display on which the window is visible.
815 pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {841 pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
816 self.platform842 self.platform
817 .displays()843 .displays()
@@ -819,6 +845,7 @@ impl<'a> WindowContext<'a> { · 1 unmodified line
819 .find(|display| display.id() == self.window.display_id)845 .find(|display| display.id() == self.window.display_id)
820 }846 }
821 847
848 /// Show the platform character palette.
822 pub fn show_character_palette(&self) {849 pub fn show_character_palette(&self) {
823 self.window.platform_window.show_character_palette();850 self.window.platform_window.show_character_palette();
824 }851 }
@@ -936,6 +963,7 @@ impl<'a> WindowContext<'a> { · 111 unmodified lines
936 .on_action(action_type, ArenaRef::from(listener));963 .on_action(action_type, ArenaRef::from(listener));
937 }964 }
938 965
966 /// Determine whether the given action is available along the dispatch path to the currently focused element.
939 pub fn is_action_available(&self, action: &dyn Action) -> bool {967 pub fn is_action_available(&self, action: &dyn Action) -> bool {
940 let target = self968 let target = self
941 .focused()969 .focused()
@@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> { · 20 unmodified lines
962 self.window.modifiers990 self.window.modifiers
963 }991 }
964 992
993 /// Update the cursor style at the platform level.
965 pub fn set_cursor_style(&mut self, style: CursorStyle) {994 pub fn set_cursor_style(&mut self, style: CursorStyle) {
966 self.window.requested_cursor_style = Some(style)995 self.window.requested_cursor_style = Some(style)
967 }996 }
@@ -991,7 +1020,7 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines
991 true1020 true
992 }1021 }
993 1022
994 pub fn was_top_layer_under_active_drag(1023 pub(crate) fn was_top_layer_under_active_drag(
995 &self,1024 &self,
996 point: &Point<Pixels>,1025 point: &Point<Pixels>,
997 level: &StackingOrder,1026 level: &StackingOrder,
@@ -1649,6 +1678,7 @@ impl<'a> WindowContext<'a> { · 651 unmodified lines
1649 self.dispatch_keystroke_observers(event, None);1678 self.dispatch_keystroke_observers(event, None);
1650 }1679 }
1651 1680
1681 /// Determine whether a potential multi-stroke key binding is in progress on this window.
1652 pub fn has_pending_keystrokes(&self) -> bool {1682 pub fn has_pending_keystrokes(&self) -> bool {
1653 self.window1683 self.window
1654 .rendered_frame1684 .rendered_frame
@@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> { · 60 unmodified lines
1715 subscription1745 subscription
1716 }1746 }
1717 1747
1748 /// Focus the current window and bring it to the foreground at the platform level.
1718 pub fn activate_window(&self) {1749 pub fn activate_window(&self) {
1719 self.window.platform_window.activate();1750 self.window.platform_window.activate();
1720 }1751 }
1721 1752
1753 /// Minimize the current window at the platform level.
1722 pub fn minimize_window(&self) {1754 pub fn minimize_window(&self) {
1723 self.window.platform_window.minimize();1755 self.window.platform_window.minimize();
1724 }1756 }
1725 1757
1758 /// Toggle full screen status on the current window at the platform level.
1726 pub fn toggle_full_screen(&self) {1759 pub fn toggle_full_screen(&self) {
1727 self.window.platform_window.toggle_full_screen();1760 self.window.platform_window.toggle_full_screen();
1728 }1761 }
1729 1762
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.
1730 pub fn prompt(1766 pub fn prompt(
1731 &self,1767 &self,
1732 level: PromptLevel,1768 level: PromptLevel,
1733 msg: &str,1769 message: &str,
1734 answers: &[&str],1770 answers: &[&str],
1735 ) -> oneshot::Receiver<usize> {1771 ) -> oneshot::Receiver<usize> {
1736 self.window.platform_window.prompt(level, msg, answers)1772 self.window.platform_window.prompt(level, message, answers)
1737 }1773 }
1738 1774
1775 /// Returns all available actions for the focused element.
1739 pub fn available_actions(&self) -> Vec<Box<dyn Action>> {1776 pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
1740 let node_id = self1777 let node_id = self
1741 .window1778 .window
@@ -1754,6 +1791,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines
1754 .available_actions(node_id)1791 .available_actions(node_id)
1755 }1792 }
1756 1793
1794 /// Returns key bindings that invoke the given action on the currently focused element.
1757 pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {1795 pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
1758 self.window1796 self.window
1759 .rendered_frame1797 .rendered_frame
@@ -1764,6 +1802,7 @@ impl<'a> WindowContext<'a> { · 4 unmodified lines
1764 )1802 )
1765 }1803 }
1766 1804
1805 /// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
1767 pub fn bindings_for_action_in(1806 pub fn bindings_for_action_in(
1768 &self,1807 &self,
1769 action: &dyn Action,1808 action: &dyn Action,
@@ -1782,6 +1821,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines
1782 dispatch_tree.bindings_for_action(action, &context_stack)1821 dispatch_tree.bindings_for_action(action, &context_stack)
1783 }1822 }
1784 1823
1824 /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
1785 pub fn listener_for<V: Render, E>(1825 pub fn listener_for<V: Render, E>(
1786 &self,1826 &self,
1787 view: &View<V>,1827 view: &View<V>,
@@ -1793,6 +1833,7 @@ impl<'a> WindowContext<'a> { · 5 unmodified lines
1793 }1833 }
1794 }1834 }
1795 1835
1836 /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
1796 pub fn handler_for<V: Render>(1837 pub fn handler_for<V: Render>(
1797 &self,1838 &self,
1798 view: &View<V>,1839 view: &View<V>,
@@ -1804,7 +1845,8 @@ impl<'a> WindowContext<'a> { · 5 unmodified lines
1804 }1845 }
1805 }1846 }
1806 1847
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.
1808 pub fn with_key_dispatch<R>(1850 pub fn with_key_dispatch<R>(
1809 &mut self,1851 &mut self,
1810 context: Option<KeyContext>,1852 context: Option<KeyContext>,
@@ -1843,6 +1885,8 @@ impl<'a> WindowContext<'a> { · 32 unmodified lines
1843 }1885 }
1844 }1886 }
1845 1887
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.
1846 pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {1890 pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
1847 let mut this = self.to_async();1891 let mut this = self.to_async();
1848 self.window1892 self.window
@@ -2017,19 +2061,24 @@ impl<'a> BorrowMut<AppContext> for WindowContext<'a> { · 168 unmodified lines
2017 }2061 }
2018}2062}
2019 2063
2064/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`]
2020pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {2065pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
2066 #[doc(hidden)]
2021 fn app_mut(&mut self) -> &mut AppContext {2067 fn app_mut(&mut self) -> &mut AppContext {
2022 self.borrow_mut()2068 self.borrow_mut()
2023 }2069 }
2024 2070
2071 #[doc(hidden)]
2025 fn app(&self) -> &AppContext {2072 fn app(&self) -> &AppContext {
2026 self.borrow()2073 self.borrow()
2027 }2074 }
2028 2075
2076 #[doc(hidden)]
2029 fn window(&self) -> &Window {2077 fn window(&self) -> &Window {
2030 self.borrow()2078 self.borrow()
2031 }2079 }
2032 2080
2081 #[doc(hidden)]
2033 fn window_mut(&mut self) -> &mut Window {2082 fn window_mut(&mut self) -> &mut Window {
2034 self.borrow_mut()2083 self.borrow_mut()
2035 }2084 }
@@ -2279,6 +2328,10 @@ impl BorrowMut<Window> for WindowContext<'_> { · 243 unmodified lines
2279 2328
2280impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}2329impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
2281 2330
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>`.
2282pub struct ViewContext<'a, V> {2335pub struct ViewContext<'a, V> {
2283 window_cx: WindowContext<'a>,2336 window_cx: WindowContext<'a>,
2284 view: &'a View<V>,2337 view: &'a View<V>,
@@ -2316,14 +2369,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 31 unmodified lines
2316 }2369 }
2317 }2370 }
2318 2371
2372 /// Get the entity_id of this view.
2319 pub fn entity_id(&self) -> EntityId {2373 pub fn entity_id(&self) -> EntityId {
2320 self.view.entity_id()2374 self.view.entity_id()
2321 }2375 }
2322 2376
2377 /// Get the view pointer underlying this context.
2323 pub fn view(&self) -> &View<V> {2378 pub fn view(&self) -> &View<V> {
2324 self.view2379 self.view
2325 }2380 }
2326 2381
2382 /// Get the model underlying this view.
2327 pub fn model(&self) -> &Model<V> {2383 pub fn model(&self) -> &Model<V> {
2328 &self.view.model2384 &self.view.model
2329 }2385 }
@@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines
2333 &mut self.window_cx2389 &mut self.window_cx
2334 }2390 }
2335 2391
2392 /// Set a given callback to be run on the next frame.
2336 pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)2393 pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
2337 where2394 where
2338 V: 'static,2395 V: 'static,
@@ -2350,6 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 11 unmodified lines
2350 });2407 });
2351 }2408 }
2352 2409
2410 /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`].
2353 pub fn observe<V2, E>(2411 pub fn observe<V2, E>(
2354 &mut self,2412 &mut self,
2355 entity: &E,2413 entity: &E,
@@ -2383,6 +2441,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 27 unmodified lines
2383 subscription2441 subscription
2384 }2442 }
2385 2443
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.
2386 pub fn subscribe<V2, E, Evt>(2447 pub fn subscribe<V2, E, Evt>(
2387 &mut self,2448 &mut self,
2388 entity: &E,2449 entity: &E,
@@ -2440,6 +2501,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 51 unmodified lines
2440 subscription2501 subscription
2441 }2502 }
2442 2503
2504 /// Register a callback to be invoked when the given Model or View is released.
2443 pub fn observe_release<V2, E>(2505 pub fn observe_release<V2, E>(
2444 &mut self,2506 &mut self,
2445 entity: &E,2507 entity: &E,
@@ -2466,6 +2528,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 20 unmodified lines
2466 subscription2528 subscription
2467 }2529 }
2468 2530
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.
2469 pub fn notify(&mut self) {2533 pub fn notify(&mut self) {
2470 if !self.window.drawing {2534 if !self.window.drawing {
2471 self.window_cx.notify();2535 self.window_cx.notify();
@@ -2475,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines
2475 }2539 }
2476 }2540 }
2477 2541
2542 /// Register a callback to be invoked when the window is resized.
2478 pub fn observe_window_bounds(2543 pub fn observe_window_bounds(
2479 &mut self,2544 &mut self,
2480 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,2545 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2488,6 +2553,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 7 unmodified lines
2488 subscription2553 subscription
2489 }2554 }
2490 2555
2556 /// Register a callback to be invoked when the window is activated or deactivated.
2491 pub fn observe_window_activation(2557 pub fn observe_window_activation(
2492 &mut self,2558 &mut self,
2493 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,2559 mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2620,6 +2686,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 126 unmodified lines
2620 subscription2686 subscription
2621 }2687 }
2622 2688
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.
2623 pub fn spawn<Fut, R>(2693 pub fn spawn<Fut, R>(
2624 &mut self,2694 &mut self,
2625 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,2695 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2632,6 +2702,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 6 unmodified lines
2632 self.window_cx.spawn(|cx| f(view, cx))2702 self.window_cx.spawn(|cx| f(view, cx))
2633 }2703 }
2634 2704
2705 /// Update the global state of the given type.
2635 pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R2706 pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
2636 where2707 where
2637 G: 'static,2708 G: 'static,
@@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 4 unmodified lines
2642 result2713 result
2643 }2714 }
2644 2715
2716 /// Register a callback to be invoked when the given global state changes.
2645 pub fn observe_global<G: 'static>(2717 pub fn observe_global<G: 'static>(
2646 &mut self,2718 &mut self,
2647 mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,2719 mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,
@@ -2660,6 +2732,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 12 unmodified lines
2660 subscription2732 subscription
2661 }2733 }
2662 2734
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.
2663 pub fn on_mouse_event<Event: 'static>(2738 pub fn on_mouse_event<Event: 'static>(
2664 &mut self,2739 &mut self,
2665 handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,2740 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
2672 });2747 });
2673 }2748 }
2674 2749
2750 /// Register a callback to be invoked when the given Key Event is dispatched to the window.
2675 pub fn on_key_event<Event: 'static>(2751 pub fn on_key_event<Event: 'static>(
2676 &mut self,2752 &mut self,
2677 handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,2753 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
2684 });2760 });
2685 }2761 }
2686 2762
2763 /// Register a callback to be invoked when the given Action type is dispatched to the window.
2687 pub fn on_action(2764 pub fn on_action(
2688 &mut self,2765 &mut self,
2689 action_type: TypeId,2766 action_type: TypeId,
@@ -2698,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 8 unmodified lines
2698 });2775 });
2699 }2776 }
2700 2777
2778 /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe].
2701 pub fn emit<Evt>(&mut self, event: Evt)2779 pub fn emit<Evt>(&mut self, event: Evt)
2702 where2780 where
2703 Evt: 'static,2781 Evt: 'static,
@@ -2711,6 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 7 unmodified lines
2711 });2789 });
2712 }2790 }
2713 2791
2792 /// Move focus to the current view, assuming it implements [`FocusableView`].
2714 pub fn focus_self(&mut self)2793 pub fn focus_self(&mut self)
2715 where2794 where
2716 V: FocusableView,2795 V: FocusableView,
@@ -2718,6 +2797,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 1 unmodified line
2718 self.defer(|view, cx| view.focus_handle(cx).focus(cx))2797 self.defer(|view, cx| view.focus_handle(cx).focus(cx))
2719 }2798 }
2720 2799
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.
2721 pub fn listener<E>(2805 pub fn listener<E>(
2722 &self,2806 &self,
2723 f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,2807 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
2827}2911}
2828 2912
2829// #[derive(Clone, Copy, Eq, PartialEq, Hash)]2913// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
2830slotmap::new_key_type! { pub struct WindowId; }2914slotmap::new_key_type! {
2915 /// A unique identifier for a window.
2916 pub struct WindowId;
2917}
2831 2918
2832impl WindowId {2919impl WindowId {
2920 /// Converts this window ID to a `u64`.
2833 pub fn as_u64(&self) -> u64 {2921 pub fn as_u64(&self) -> u64 {
2834 self.0.as_ffi()2922 self.0.as_ffi()
2835 }2923 }
2836}2924}
2837 2925
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.
2838#[derive(Deref, DerefMut)]2928#[derive(Deref, DerefMut)]
2839pub struct WindowHandle<V> {2929pub struct WindowHandle<V> {
2840 #[deref]2930 #[deref]
@@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> { · 3 unmodified lines
2844}2934}
2845 2935
2846impl<V: 'static + Render> WindowHandle<V> {2936impl<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`.
2847 pub fn new(id: WindowId) -> Self {2939 pub fn new(id: WindowId) -> Self {
2848 WindowHandle {2940 WindowHandle {
2849 any_handle: AnyWindowHandle {2941 any_handle: AnyWindowHandle {
@@ -2854,6 +2946,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 4 unmodified lines
2854 }2946 }
2855 }2947 }
2856 2948
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`.
2857 pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>2952 pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>
2858 where2953 where
2859 C: Context,2954 C: Context,
@@ -2865,6 +2960,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 5 unmodified lines
2865 }))2960 }))
2866 }2961 }
2867 2962
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
2868 pub fn update<C, R>(2966 pub fn update<C, R>(
2869 &self,2967 &self,
2870 cx: &mut C,2968 cx: &mut C,
@@ -2881,6 +2979,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 10 unmodified lines
2881 })?2979 })?
2882 }2980 }
2883 2981
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`.
2884 pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {2985 pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
2885 let x = cx2986 let x = cx
2886 .windows2987 .windows
@@ -2897,6 +2998,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 10 unmodified lines
2897 Ok(x.read(cx))2998 Ok(x.read(cx))
2898 }2999 }
2899 3000
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`.
2900 pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>3004 pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
2901 where3005 where
2902 C: Context,3006 C: Context,
@@ -2904,6 +3008,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line
2904 cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))3008 cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
2905 }3009 }
2906 3010
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`.
2907 pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>3014 pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
2908 where3015 where
2909 C: Context,3016 C: Context,
@@ -2911,6 +3018,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line
2911 cx.read_window(self, |root_view, _cx| root_view.clone())3018 cx.read_window(self, |root_view, _cx| root_view.clone())
2912 }3019 }
2913 3020
3021 /// Check if this window is 'active'.
3022 ///
3023 /// Will return `None` if the window is closed.
2914 pub fn is_active(&self, cx: &AppContext) -> Option<bool> {3024 pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
2915 cx.windows3025 cx.windows
2916 .get(self.id)3026 .get(self.id)
@@ -2946,6 +3056,7 @@ impl<V: 'static> From<WindowHandle<V>> for AnyWindowHandle { · 29 unmodified lines
2946 }3056 }
2947}3057}
2948 3058
3059/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type.
2949#[derive(Copy, Clone, PartialEq, Eq, Hash)]3060#[derive(Copy, Clone, PartialEq, Eq, Hash)]
2950pub struct AnyWindowHandle {3061pub struct AnyWindowHandle {
2951 pub(crate) id: WindowId,3062 pub(crate) id: WindowId,
@@ -2953,10 +3064,13 @@ pub struct AnyWindowHandle { · 1 unmodified line
2953}3064}
2954 3065
2955impl AnyWindowHandle {3066impl AnyWindowHandle {
3067 /// Get the ID of this window.
2956 pub fn window_id(&self) -> WindowId {3068 pub fn window_id(&self) -> WindowId {
2957 self.id3069 self.id
2958 }3070 }
2959 3071
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`.
2960 pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {3074 pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {
2961 if TypeId::of::<T>() == self.state_type {3075 if TypeId::of::<T>() == self.state_type {
2962 Some(WindowHandle {3076 Some(WindowHandle {
@@ -2968,6 +3082,9 @@ impl AnyWindowHandle { · 5 unmodified lines
2968 }3082 }
2969 }3083 }
2970 3084
3085 /// Update the state of the root view of this window.
3086 ///
3087 /// This will fail if the window has been closed.
2971 pub fn update<C, R>(3088 pub fn update<C, R>(
2972 self,3089 self,
2973 cx: &mut C,3090 cx: &mut C,
@@ -2979,6 +3096,9 @@ impl AnyWindowHandle { · 5 unmodified lines
2979 cx.update_window(self, update)3096 cx.update_window(self, update)
2980 }3097 }
2981 3098
3099 /// Read the state of the root view of this window.
3100 ///
3101 /// This will fail if the window has been closed.
2982 pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>3102 pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
2983 where3103 where
2984 C: Context,3104 C: Context,
@@ -2999,12 +3119,21 @@ impl AnyWindowHandle { · 14 unmodified lines
2999// }3119// }
3000// }3120// }
3001 3121
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.
3002#[derive(Clone, Debug, Eq, PartialEq, Hash)]3126#[derive(Clone, Debug, Eq, PartialEq, Hash)]
3003pub enum ElementId {3127pub enum ElementId {
3128 /// The ID of a View element
3004 View(EntityId),3129 View(EntityId),
3130 /// An integer ID.
3005 Integer(usize),3131 Integer(usize),
3132 /// A string based ID.
3006 Name(SharedString),3133 Name(SharedString),
3134 /// An ID that's equated with a focus handle.
3007 FocusHandle(FocusId),3135 FocusHandle(FocusId),
3136 /// A combination of a name and an integer.
3008 NamedInteger(SharedString, usize),3137 NamedInteger(SharedString, usize),
3009}3138}
3010 3139
@@ -3074,7 +3203,8 @@ impl From<(&'static str, u64)> for ElementId { · 63 unmodified lines
3074 }3203 }
3075}3204}
3076 3205
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`].
3078#[derive(Clone)]3208#[derive(Clone)]
3079pub struct PaintQuad {3209pub struct PaintQuad {
3080 bounds: Bounds<Pixels>,3210 bounds: Bounds<Pixels>,
crates/gpui/tests/action_macros.rs+6−6
@@ -18,33 +18,33 @@ fn test_action_macros() { · 17 unmodified lines
18 18
19 impl gpui::Action for RegisterableAction {19 impl gpui::Action for RegisterableAction {
20 fn boxed_clone(&self) -> Box<dyn gpui::Action> {20 fn boxed_clone(&self) -> Box<dyn gpui::Action> {
21 todo!()21 unimplemented!()
22 }22 }
23 23
24 fn as_any(&self) -> &dyn std::any::Any {24 fn as_any(&self) -> &dyn std::any::Any {
25 todo!()25 unimplemented!()
26 }26 }
27 27
28 fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {28 fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
29 todo!()29 unimplemented!()
30 }30 }
31 31
32 fn name(&self) -> &str {32 fn name(&self) -> &str {
33 todo!()33 unimplemented!()
34 }34 }
35 35
36 fn debug_name() -> &'static str36 fn debug_name() -> &'static str
37 where37 where
38 Self: Sized,38 Self: Sized,
39 {39 {
40 todo!()40 unimplemented!()
41 }41 }
42 42
43 fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>>43 fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>>
44 where44 where
45 Self: Sized,45 Self: Sized,
46 {46 {
47 todo!()47 unimplemented!()
48 }48 }
49 }49 }
50}50}
crates/gpui_macros/src/gpui_macros.rs+30−0
@@ -7,26 +7,56 @@ mod test; · 6 unmodified lines
7use proc_macro::TokenStream;7use proc_macro::TokenStream;
8 8
9#[proc_macro]9#[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.
10pub fn register_action(ident: TokenStream) -> TokenStream {13pub fn register_action(ident: TokenStream) -> TokenStream {
11 register_action::register_action_macro(ident)14 register_action::register_action_macro(ident)
12}15}
13 16
14#[proc_macro_derive(IntoElement)]17#[proc_macro_derive(IntoElement)]
18// #[derive(IntoElement)] is used to create a Component out of anything that implements
19// the `RenderOnce` trait.
15pub fn derive_into_element(input: TokenStream) -> TokenStream {20pub fn derive_into_element(input: TokenStream) -> TokenStream {
16 derive_into_element::derive_into_element(input)21 derive_into_element::derive_into_element(input)
17}22}
18 23
19#[proc_macro_derive(Render)]24#[proc_macro_derive(Render)]
25#[doc(hidden)]
20pub fn derive_render(input: TokenStream) -> TokenStream {26pub fn derive_render(input: TokenStream) -> TokenStream {
21 derive_render::derive_render(input)27 derive_render::derive_render(input)
22}28}
23 29
30// Used by gpui to generate the style helpers.
24#[proc_macro]31#[proc_macro]
32#[doc(hidden)]
25pub fn style_helpers(input: TokenStream) -> TokenStream {33pub fn style_helpers(input: TokenStream) -> TokenStream {
26 style_helpers::style_helpers(input)34 style_helpers::style_helpers(input)
27}35}
28 36
29#[proc_macro_attribute]37#[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.
30pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {60pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
31 test::test(args, function)61 test::test(args, function)
32}62}
crates/language/src/syntax_map/syntax_map_tests.rs+14−14
@@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { · 257 unmodified lines
258 let (buffer, syntax_map) = test_edit_sequence(258 let (buffer, syntax_map) = test_edit_sequence(
259 "Rust",259 "Rust",
260 &[260 &[
261 "fn a() { dbg }",261 "fn a() { test_macro }",
262 "fn a() { dbg«!» }",262 "fn a() { test_macro«!» }",
263 "fn a() { dbg!«()» }",263 "fn a() { test_macro!«()» }",
264 "fn a() { dbg!(«b») }",264 "fn a() { test_macro!(«b») }",
265 "fn a() { dbg!(b«.») }",265 "fn a() { test_macro!(b«.») }",
266 "fn a() { dbg!(b.«c») }",266 "fn a() { test_macro!(b.«c») }",
267 "fn a() { dbg!(b.c«()») }",267 "fn a() { test_macro!(b.c«()») }",
268 "fn a() { dbg!(b.c(«vec»)) }",268 "fn a() { test_macro!(b.c(«vec»)) }",
269 "fn a() { dbg!(b.c(vec«!»)) }",269 "fn a() { test_macro!(b.c(vec«!»)) }",
270 "fn a() { dbg!(b.c(vec!«[]»)) }",270 "fn a() { test_macro!(b.c(vec!«[]»)) }",
271 "fn a() { dbg!(b.c(vec![«d»])) }",271 "fn a() { test_macro!(b.c(vec![«d»])) }",
272 "fn a() { dbg!(b.c(vec![d«.»])) }",272 "fn a() { test_macro!(b.c(vec![d«.»])) }",
273 "fn a() { dbg!(b.c(vec![d.«e»])) }",273 "fn a() { test_macro!(b.c(vec![d.«e»])) }",
274 ],274 ],
275 );275 );
276 276
@@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { · 1 unmodified line
278 &syntax_map,278 &syntax_map,
279 &buffer,279 &buffer,
280 &["field"],280 &["field"],
281 "fn a() { dbg!(b.«c»(vec![d.«e»])) }",281 "fn a() { test_macro!(b.«c»(vec![d.«e»])) }",
282 );282 );
283}283}
284 284
crates/project_panel/src/project_panel.rs+8−5
@@ -30,7 +30,7 @@ use std::{ · 29 unmodified lines
30 sync::Arc,30 sync::Arc,
31};31};
32use theme::ThemeSettings;32use theme::ThemeSettings;
33use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem};33use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem};
34use unicase::UniCase;34use unicase::UniCase;
35use util::{maybe, ResultExt, TryFutureExt};35use util::{maybe, ResultExt, TryFutureExt};
36use workspace::{36use workspace::{
@@ -1403,7 +1403,7 @@ impl ProjectPanel { · 1366 unmodified lines
1403 .indent_step_size(px(settings.indent_size))1403 .indent_step_size(px(settings.indent_size))
1404 .selected(is_selected)1404 .selected(is_selected)
1405 .child(if let Some(icon) = &icon {1405 .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))
1407 } else {1407 } else {
1408 div().size(IconSize::default().rems()).invisible()1408 div().size(IconSize::default().rems()).invisible()
1409 })1409 })
@@ -1433,6 +1433,9 @@ impl ProjectPanel { · 23 unmodified lines
1433 }))1433 }))
1434 .on_secondary_mouse_down(cx.listener(1434 .on_secondary_mouse_down(cx.listener(
1435 move |this, event: &MouseDownEvent, cx| {1435 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();
1436 this.deploy_context_menu(event.position, entry_id, cx);1439 this.deploy_context_menu(event.position, entry_id, cx);
1437 },1440 },
1438 )),1441 )),
@@ -1587,7 +1590,7 @@ impl Render for DraggedProjectEntryView { · 148 unmodified lines
1587 .indent_level(self.details.depth)1590 .indent_level(self.details.depth)
1588 .indent_step_size(px(settings.indent_size))1591 .indent_step_size(px(settings.indent_size))
1589 .child(if let Some(icon) = &self.details.icon {1592 .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()))
1591 } else {1594 } else {
1592 div()1595 div()
1593 })1596 })
@@ -1637,8 +1640,8 @@ impl Panel for ProjectPanel { · 43 unmodified lines
1637 cx.notify();1640 cx.notify();
1638 }1641 }
1639 1642
1640 fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {1643 fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
1641 Some(ui::Icon::FileTree)1644 Some(ui::IconName::FileTree)
1642 }1645 }
1643 1646
1644 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {1647 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
6 Subscription, View, ViewContext, WeakView,6 Subscription, View, ViewContext, WeakView,
7};7};
8use search::{buffer_search, BufferSearchBar};8use search::{buffer_search, BufferSearchBar};
9use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};9use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
10use workspace::{10use workspace::{
11 item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,11 item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
12};12};
@@ -43,7 +43,7 @@ impl Render for QuickActionBar { · 30 unmodified lines
43 43
44 let inlay_hints_button = Some(QuickActionBarButton::new(44 let inlay_hints_button = Some(QuickActionBarButton::new(
45 "toggle inlay hints",45 "toggle inlay hints",
46 Icon::InlayHint,46 IconName::InlayHint,
47 editor.read(cx).inlay_hints_enabled(),47 editor.read(cx).inlay_hints_enabled(),
48 Box::new(editor::ToggleInlayHints),48 Box::new(editor::ToggleInlayHints),
49 "Toggle Inlay Hints",49 "Toggle Inlay Hints",
@@ -60,7 +60,7 @@ impl Render for QuickActionBar { · 10 unmodified lines
60 60
61 let search_button = Some(QuickActionBarButton::new(61 let search_button = Some(QuickActionBarButton::new(
62 "toggle buffer search",62 "toggle buffer search",
63 Icon::MagnifyingGlass,63 IconName::MagnifyingGlass,
64 !self.buffer_search_bar.read(cx).is_dismissed(),64 !self.buffer_search_bar.read(cx).is_dismissed(),
65 Box::new(buffer_search::Deploy { focus: false }),65 Box::new(buffer_search::Deploy { focus: false }),
66 "Buffer Search",66 "Buffer Search",
@@ -77,7 +77,7 @@ impl Render for QuickActionBar { · 10 unmodified lines
77 77
78 let assistant_button = QuickActionBarButton::new(78 let assistant_button = QuickActionBarButton::new(
79 "toggle inline assistant",79 "toggle inline assistant",
80 Icon::MagicWand,80 IconName::MagicWand,
81 false,81 false,
82 Box::new(InlineAssist),82 Box::new(InlineAssist),
83 "Inline Assist",83 "Inline Assist",
@@ -107,7 +107,7 @@ impl EventEmitter<ToolbarItemEvent> for QuickActionBar {} · 23 unmodified lines
107#[derive(IntoElement)]107#[derive(IntoElement)]
108struct QuickActionBarButton {108struct QuickActionBarButton {
109 id: ElementId,109 id: ElementId,
110 icon: Icon,110 icon: IconName,
111 toggled: bool,111 toggled: bool,
112 action: Box<dyn Action>,112 action: Box<dyn Action>,
113 tooltip: SharedString,113 tooltip: SharedString,
@@ -117,7 +117,7 @@ struct QuickActionBarButton { · 3 unmodified lines
117impl QuickActionBarButton {117impl QuickActionBarButton {
118 fn new(118 fn new(
119 id: impl Into<ElementId>,119 id: impl Into<ElementId>,
120 icon: Icon,120 icon: IconName,
121 toggled: bool,121 toggled: bool,
122 action: Box<dyn Action>,122 action: Box<dyn Action>,
123 tooltip: impl Into<SharedString>,123 tooltip: impl Into<SharedString>,
crates/rope/src/rope.rs+3−3
@@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; · 24 unmodified lines
25#[cfg(not(test))]25#[cfg(not(test))]
26const CHUNK_BASE: usize = 16;26const CHUNK_BASE: usize = 16;
27 27
28/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances28/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances
29/// containing the same text will produce the same fingerprint. This hash function is special in that29/// 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 resulting30/// 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.31/// hash being equivalent to hashing all the text contained in the [`Rope`] at once.
32pub type RopeFingerprint = HashMatrix;32pub type RopeFingerprint = HashMatrix;
33 33
34#[derive(Clone, Default)]34#[derive(Clone, Default)]
crates/rpc/src/notification.rs+30−25
@@ -76,30 +76,35 @@ impl Notification { · 75 unmodified lines
76 }76 }
77}77}
78 78
79#[test]79#[cfg(test)]
80fn test_notification() {80mod tests {
81 // Notifications can be serialized and deserialized.81 use crate::Notification;
82 for notification in [82
83 Notification::ContactRequest { sender_id: 1 },83 #[test]
84 Notification::ContactRequestAccepted { responder_id: 2 },84 fn test_notification() {
85 Notification::ChannelInvitation {85 // Notifications can be serialized and deserialized.
86 channel_id: 100,86 for notification in [
87 channel_name: "the-channel".into(),87 Notification::ContactRequest { sender_id: 1 },
88 inviter_id: 50,88 Notification::ContactRequestAccepted { responder_id: 2 },
89 },89 Notification::ChannelInvitation {
90 Notification::ChannelMessageMention {90 channel_id: 100,
91 sender_id: 200,91 channel_name: "the-channel".into(),
92 channel_id: 30,92 inviter_id: 50,
93 message_id: 1,93 },
94 },94 Notification::ChannelMessageMention {
95 ] {95 sender_id: 200,
96 let message = notification.to_proto();96 channel_id: 30,
97 let deserialized = Notification::from_proto(&message).unwrap();97 message_id: 1,
98 assert_eq!(deserialized, notification);98 },
99 }99 ] {
100 let message = notification.to_proto();
101 let deserialized = Notification::from_proto(&message).unwrap();
102 assert_eq!(deserialized, notification);
103 }
100 104
101 // When notifications are serialized, the `kind` and `actor_id` fields are105 // When notifications are serialized, the `kind` and `actor_id` fields are
102 // stored separately, and do not appear redundantly in the JSON.106 // stored separately, and do not appear redundantly in the JSON.
103 let notification = Notification::ContactRequest { sender_id: 1 };107 let notification = Notification::ContactRequest { sender_id: 1 };
104 assert_eq!(notification.to_proto().content, "{}");108 assert_eq!(notification.to_proto().content, "{}");
109 }
105}110}
crates/search/src/buffer_search.rs+17−12
@@ -21,7 +21,7 @@ use settings::Settings; · 20 unmodified lines
21use std::{any::Any, sync::Arc};21use std::{any::Any, sync::Arc};
22use theme::ThemeSettings;22use theme::ThemeSettings;
23 23
24use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip};24use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip};
25use util::ResultExt;25use util::ResultExt;
26use workspace::{26use workspace::{
27 item::ItemHandle,27 item::ItemHandle,
@@ -43,7 +43,7 @@ pub enum Event { · 15 unmodified lines
43}43}
44 44
45pub fn init(cx: &mut AppContext) {45pub 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))
47 .detach();47 .detach();
48}48}
49 49
@@ -225,7 +225,7 @@ impl Render for BufferSearchBar { · 175 unmodified lines
225 .border_color(editor_border)225 .border_color(editor_border)
226 .min_w(rems(384. / 16.))226 .min_w(rems(384. / 16.))
227 .rounded_lg()227 .rounded_lg()
228 .child(IconElement::new(Icon::MagnifyingGlass))228 .child(Icon::new(IconName::MagnifyingGlass))
229 .child(self.render_text_input(&self.query_editor, cx))229 .child(self.render_text_input(&self.query_editor, cx))
230 .children(supported_options.case.then(|| {230 .children(supported_options.case.then(|| {
231 self.render_search_option_button(231 self.render_search_option_button(
@@ -287,7 +287,7 @@ impl Render for BufferSearchBar { · 55 unmodified lines
287 this.child(287 this.child(
288 IconButton::new(288 IconButton::new(
289 "buffer-search-bar-toggle-replace-button",289 "buffer-search-bar-toggle-replace-button",
290 Icon::Replace,290 IconName::Replace,
291 )291 )
292 .style(ButtonStyle::Subtle)292 .style(ButtonStyle::Subtle)
293 .when(self.replace_enabled, |button| {293 .when(self.replace_enabled, |button| {
@@ -323,7 +323,7 @@ impl Render for BufferSearchBar { · 29 unmodified lines
323 )323 )
324 .when(should_show_replace_input, |this| {324 .when(should_show_replace_input, |this| {
325 this.child(325 this.child(
326 IconButton::new("search-replace-next", ui::Icon::ReplaceNext)326 IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
327 .tooltip(move |cx| {327 .tooltip(move |cx| {
328 Tooltip::for_action("Replace next", &ReplaceNext, cx)328 Tooltip::for_action("Replace next", &ReplaceNext, cx)
329 })329 })
@@ -332,7 +332,7 @@ impl Render for BufferSearchBar { · 2 unmodified lines
332 })),332 })),
333 )333 )
334 .child(334 .child(
335 IconButton::new("search-replace-all", ui::Icon::ReplaceAll)335 IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
336 .tooltip(move |cx| {336 .tooltip(move |cx| {
337 Tooltip::for_action("Replace all", &ReplaceAll, cx)337 Tooltip::for_action("Replace all", &ReplaceAll, cx)
338 })338 })
@@ -350,7 +350,7 @@ impl Render for BufferSearchBar { · 11 unmodified lines
350 .gap_0p5()350 .gap_0p5()
351 .flex_none()351 .flex_none()
352 .child(352 .child(
353 IconButton::new("select-all", ui::Icon::SelectAll)353 IconButton::new("select-all", ui::IconName::SelectAll)
354 .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))354 .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
355 .tooltip(|cx| {355 .tooltip(|cx| {
356 Tooltip::for_action("Select all matches", &SelectAllMatches, cx)356 Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
@@ -358,13 +358,13 @@ impl Render for BufferSearchBar { · 1 unmodified line
358 )358 )
359 .children(match_count)359 .children(match_count)
360 .child(render_nav_button(360 .child(render_nav_button(
361 ui::Icon::ChevronLeft,361 ui::IconName::ChevronLeft,
362 self.active_match_index.is_some(),362 self.active_match_index.is_some(),
363 "Select previous match",363 "Select previous match",
364 &SelectPrevMatch,364 &SelectPrevMatch,
365 ))365 ))
366 .child(render_nav_button(366 .child(render_nav_button(
367 ui::Icon::ChevronRight,367 ui::IconName::ChevronRight,
368 self.active_match_index.is_some(),368 self.active_match_index.is_some(),
369 "Select next match",369 "Select next match",
370 &SelectNextMatch,370 &SelectNextMatch,
@@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { · 108 unmodified lines
479 callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),479 callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
480 ) {480 ) {
481 self.register_action(move |workspace, action: &A, cx| {481 self.register_action(move |workspace, action: &A, cx| {
482 if workspace.has_active_modal(cx) {
483 cx.propagate();
484 return;
485 }
486
482 let pane = workspace.active_pane();487 let pane = workspace.active_pane();
483 pane.update(cx, move |this, cx| {488 pane.update(cx, move |this, cx| {
484 this.toolbar().update(cx, move |this, cx| {489 this.toolbar().update(cx, move |this, cx| {
@@ -539,11 +544,11 @@ impl BufferSearchBar { · 54 unmodified lines
539 this.select_all_matches(action, cx);544 this.select_all_matches(action, cx);
540 });545 });
541 registrar.register_handler(|this, _: &editor::Cancel, cx| {546 registrar.register_handler(|this, _: &editor::Cancel, cx| {
542 if !this.dismissed {547 if this.dismissed {
548 cx.propagate();
549 } else {
543 this.dismiss(&Dismiss, cx);550 this.dismiss(&Dismiss, cx);
544 return;
545 }551 }
546 cx.propagate();
547 });552 });
548 registrar.register_handler(|this, deploy, cx| {553 registrar.register_handler(|this, deploy, cx| {
549 this.deploy(deploy, cx);554 this.deploy(deploy, cx);
crates/search/src/project_search.rs+14−13
@@ -38,7 +38,7 @@ use std::{ · 37 unmodified lines
38use theme::ThemeSettings;38use theme::ThemeSettings;
39 39
40use ui::{40use 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,
42 Selectable, ToggleButton, Tooltip,42 Selectable, ToggleButton, Tooltip,
43};43};
44use util::{paths::PathMatcher, ResultExt as _};44use util::{paths::PathMatcher, ResultExt as _};
@@ -424,7 +424,8 @@ impl Item for ProjectSearchView { · 379 unmodified lines
424 .current()424 .current()
425 .as_ref()425 .as_ref()
426 .map(|query| {426 .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);
428 query_text.into()429 query_text.into()
429 });430 });
430 let tab_name = last_query431 let tab_name = last_query
@@ -432,7 +433,7 @@ impl Item for ProjectSearchView { · 1 unmodified line
432 .unwrap_or_else(|| "Project search".into());433 .unwrap_or_else(|| "Project search".into());
433 h_stack()434 h_stack()
434 .gap_2()435 .gap_2()
435 .child(IconElement::new(Icon::MagnifyingGlass).color(if selected {436 .child(Icon::new(IconName::MagnifyingGlass).color(if selected {
436 Color::Default437 Color::Default
437 } else {438 } else {
438 Color::Muted439 Color::Muted
@@ -1616,12 +1617,12 @@ impl Render for ProjectSearchBar { · 1177 unmodified lines
1616 .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))1617 .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
1617 .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))1618 .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))
1618 .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx)))1619 .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))
1620 .child(self.render_text_input(&search.query_editor, cx))1621 .child(self.render_text_input(&search.query_editor, cx))
1621 .child(1622 .child(
1622 h_stack()1623 h_stack()
1623 .child(1624 .child(
1624 IconButton::new("project-search-filter-button", Icon::Filter)1625 IconButton::new("project-search-filter-button", IconName::Filter)
1625 .tooltip(|cx| {1626 .tooltip(|cx| {
1626 Tooltip::for_action("Toggle filters", &ToggleFilters, cx)1627 Tooltip::for_action("Toggle filters", &ToggleFilters, cx)
1627 })1628 })
@@ -1639,7 +1640,7 @@ impl Render for ProjectSearchBar { · 11 unmodified lines
1639 this.child(1640 this.child(
1640 IconButton::new(1641 IconButton::new(
1641 "project-search-case-sensitive",1642 "project-search-case-sensitive",
1642 Icon::CaseSensitive,1643 IconName::CaseSensitive,
1643 )1644 )
1644 .tooltip(|cx| {1645 .tooltip(|cx| {
1645 Tooltip::for_action(1646 Tooltip::for_action(
@@ -1659,7 +1660,7 @@ impl Render for ProjectSearchBar { · 13 unmodified lines
1659 )),1660 )),
1660 )1661 )
1661 .child(1662 .child(
1662 IconButton::new("project-search-whole-word", Icon::WholeWord)1663 IconButton::new("project-search-whole-word", IconName::WholeWord)
1663 .tooltip(|cx| {1664 .tooltip(|cx| {
1664 Tooltip::for_action(1665 Tooltip::for_action(
1665 "Toggle whole word",1666 "Toggle whole word",
@@ -1738,7 +1739,7 @@ impl Render for ProjectSearchBar { · 72 unmodified lines
1738 }),1739 }),
1739 )1740 )
1740 .child(1741 .child(
1741 IconButton::new("project-search-toggle-replace", Icon::Replace)1742 IconButton::new("project-search-toggle-replace", IconName::Replace)
1742 .on_click(cx.listener(|this, _, cx| {1743 .on_click(cx.listener(|this, _, cx| {
1743 this.toggle_replace(&ToggleReplace, cx);1744 this.toggle_replace(&ToggleReplace, cx);
1744 }))1745 }))
@@ -1755,7 +1756,7 @@ impl Render for ProjectSearchBar { · 10 unmodified lines
1755 .border_1()1756 .border_1()
1756 .border_color(cx.theme().colors().border)1757 .border_color(cx.theme().colors().border)
1757 .rounded_lg()1758 .rounded_lg()
1758 .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small))1759 .child(Icon::new(IconName::Replace).size(ui::IconSize::Small))
1759 .child(self.render_text_input(&search.replacement_editor, cx))1760 .child(self.render_text_input(&search.replacement_editor, cx))
1760 } else {1761 } else {
1761 // Fill out the space if we don't have a replacement editor.1762 // Fill out the space if we don't have a replacement editor.
@@ -1764,7 +1765,7 @@ impl Render for ProjectSearchBar { · 2 unmodified lines
1764 let actions_column = h_stack()1765 let actions_column = h_stack()
1765 .when(search.replace_enabled, |this| {1766 .when(search.replace_enabled, |this| {
1766 this.child(1767 this.child(
1767 IconButton::new("project-search-replace-next", Icon::ReplaceNext)1768 IconButton::new("project-search-replace-next", IconName::ReplaceNext)
1768 .on_click(cx.listener(|this, _, cx| {1769 .on_click(cx.listener(|this, _, cx| {
1769 if let Some(search) = this.active_project_search.as_ref() {1770 if let Some(search) = this.active_project_search.as_ref() {
1770 search.update(cx, |this, cx| {1771 search.update(cx, |this, cx| {
@@ -1775,7 +1776,7 @@ impl Render for ProjectSearchBar { · 4 unmodified lines
1775 .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),1776 .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
1776 )1777 )
1777 .child(1778 .child(
1778 IconButton::new("project-search-replace-all", Icon::ReplaceAll)1779 IconButton::new("project-search-replace-all", IconName::ReplaceAll)
1779 .on_click(cx.listener(|this, _, cx| {1780 .on_click(cx.listener(|this, _, cx| {
1780 if let Some(search) = this.active_project_search.as_ref() {1781 if let Some(search) = this.active_project_search.as_ref() {
1781 search.update(cx, |this, cx| {1782 search.update(cx, |this, cx| {
@@ -1796,7 +1797,7 @@ impl Render for ProjectSearchBar { · 14 unmodified lines
1796 this1797 this
1797 })1798 })
1798 .child(1799 .child(
1799 IconButton::new("project-search-prev-match", Icon::ChevronLeft)1800 IconButton::new("project-search-prev-match", IconName::ChevronLeft)
1800 .disabled(search.active_match_index.is_none())1801 .disabled(search.active_match_index.is_none())
1801 .on_click(cx.listener(|this, _, cx| {1802 .on_click(cx.listener(|this, _, cx| {
1802 if let Some(search) = this.active_project_search.as_ref() {1803 if let Some(search) = this.active_project_search.as_ref() {
@@ -1810,7 +1811,7 @@ impl Render for ProjectSearchBar { · 7 unmodified lines
1810 }),1811 }),
1811 )1812 )
1812 .child(1813 .child(
1813 IconButton::new("project-search-next-match", Icon::ChevronRight)1814 IconButton::new("project-search-next-match", IconName::ChevronRight)
1814 .disabled(search.active_match_index.is_none())1815 .disabled(search.active_match_index.is_none())
1815 .on_click(cx.listener(|this, _, cx| {1816 .on_click(cx.listener(|this, _, cx| {
1816 if let Some(search) = this.active_project_search.as_ref() {1817 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
60 }60 }
61 }61 }
62 62
63 pub fn icon(&self) -> ui::Icon {63 pub fn icon(&self) -> ui::IconName {
64 match *self {64 match *self {
65 SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,65 SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
66 SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,66 SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
67 SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,67 SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit,
68 _ => panic!("{:?} is not a named SearchOption", self),68 _ => panic!("{:?} is not a named SearchOption", self),
69 }69 }
70 }70 }
crates/search/src/search_bar.rs+1−1
@@ -3,7 +3,7 @@ use ui::IconButton; · 2 unmodified lines
3use ui::{prelude::*, Tooltip};3use ui::{prelude::*, Tooltip};
4 4
5pub(super) fn render_nav_button(5pub(super) fn render_nav_button(
6 icon: ui::Icon,6 icon: ui::IconName,
7 active: bool,7 active: bool,
8 tooltip: &'static str,8 tooltip: &'static str,
9 action: &'static dyn Action,9 action: &'static dyn Action,
crates/storybook/Cargo.toml+1−0
@@ -14,6 +14,7 @@ anyhow.workspace = true · 13 unmodified lines
14backtrace-on-stack-overflow = "0.3.0"14backtrace-on-stack-overflow = "0.3.0"
15chrono = "0.4"15chrono = "0.4"
16clap = { version = "4.4", features = ["derive", "string"] }16clap = { version = "4.4", features = ["derive", "string"] }
17collab_ui = { path = "../collab_ui", features = ["stories"] }
17strum = { version = "0.25.0", features = ["derive"] }18strum = { version = "0.25.0", features = ["derive"] }
18dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }19dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
19editor = { path = "../editor" }20editor = { path = "../editor" }
crates/storybook/src/story_selector.rs+4−0
@@ -16,6 +16,7 @@ pub enum ComponentStory { · 15 unmodified lines
16 Avatar,16 Avatar,
17 Button,17 Button,
18 Checkbox,18 Checkbox,
19 CollabNotification,
19 ContextMenu,20 ContextMenu,
20 Cursor,21 Cursor,
21 Disclosure,22 Disclosure,
@@ -45,6 +46,9 @@ impl ComponentStory { · 23 unmodified lines
45 Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),46 Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
46 Self::Button => cx.new_view(|_| ui::ButtonStory).into(),47 Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
47 Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),48 Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
49 Self::CollabNotification => cx
50 .new_view(|_| collab_ui::notifications::CollabNotificationStory)
51 .into(),
48 Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),52 Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
49 Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),53 Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
50 Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),54 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
451 }451 }
452 });452 });
453 453
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
454 let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {466 let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
455 div()467 div()
456 .size_full()468 .size_full()
crates/terminal_view/src/terminal_panel.rs+6−6
@@ -19,7 +19,7 @@ use workspace::{ · 18 unmodified lines
19 dock::{DockPosition, Panel, PanelEvent},19 dock::{DockPosition, Panel, PanelEvent},
20 item::Item,20 item::Item,
21 pane,21 pane,
22 ui::Icon,22 ui::IconName,
23 DraggedTab, Pane, Workspace,23 DraggedTab, Pane, Workspace,
24};24};
25 25
@@ -71,7 +71,7 @@ impl TerminalPanel { · 45 unmodified lines
71 h_stack()71 h_stack()
72 .gap_2()72 .gap_2()
73 .child(73 .child(
74 IconButton::new("plus", Icon::Plus)74 IconButton::new("plus", IconName::Plus)
75 .icon_size(IconSize::Small)75 .icon_size(IconSize::Small)
76 .on_click(move |_, cx| {76 .on_click(move |_, cx| {
77 terminal_panel77 terminal_panel
@@ -82,10 +82,10 @@ impl TerminalPanel { · 4 unmodified lines
82 )82 )
83 .child({83 .child({
84 let zoomed = pane.is_zoomed();84 let zoomed = pane.is_zoomed();
85 IconButton::new("toggle_zoom", Icon::Maximize)85 IconButton::new("toggle_zoom", IconName::Maximize)
86 .icon_size(IconSize::Small)86 .icon_size(IconSize::Small)
87 .selected(zoomed)87 .selected(zoomed)
88 .selected_icon(Icon::Minimize)88 .selected_icon(IconName::Minimize)
89 .on_click(cx.listener(|pane, _, cx| {89 .on_click(cx.listener(|pane, _, cx| {
90 pane.toggle_zoom(&workspace::ToggleZoom, cx);90 pane.toggle_zoom(&workspace::ToggleZoom, cx);
91 }))91 }))
@@ -477,8 +477,8 @@ impl Panel for TerminalPanel { · 385 unmodified lines
477 "TerminalPanel"477 "TerminalPanel"
478 }478 }
479 479
480 fn icon(&self, _cx: &WindowContext) -> Option<Icon> {480 fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
481 Some(Icon::Terminal)481 Some(IconName::Terminal)
482 }482 }
483 483
484 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {484 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
20 Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,20 Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
21};21};
22use terminal_element::TerminalElement;22use terminal_element::TerminalElement;
23use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};23use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label};
24use util::{paths::PathLikeWithPosition, ResultExt};24use util::{paths::PathLikeWithPosition, ResultExt};
25use workspace::{25use workspace::{
26 item::{BreadcrumbText, Item, ItemEvent},26 item::{BreadcrumbText, Item, ItemEvent},
@@ -690,7 +690,7 @@ impl Item for TerminalView { · 663 unmodified lines
690 let title = self.terminal().read(cx).title(true);690 let title = self.terminal().read(cx).title(true);
691 h_stack()691 h_stack()
692 .gap_2()692 .gap_2()
693 .child(IconElement::new(Icon::Terminal))693 .child(Icon::new(IconName::Terminal))
694 .child(Label::new(title).color(if selected {694 .child(Label::new(title).color(if selected {
695 Color::Default695 Color::Default
696 } else {696 } else {
crates/text/src/anchor.rs+1−1
@@ -90,7 +90,7 @@ impl Anchor { · 89 unmodified lines
90 content.summary_for_anchor(self)90 content.summary_for_anchor(self)
91 }91 }
92 92
93 /// Returns true when the [Anchor] is located inside a visible fragment.93 /// Returns true when the [`Anchor`] is located inside a visible fragment.
94 pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {94 pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
95 if *self == Anchor::MIN || *self == Anchor::MAX {95 if *self == Anchor::MIN || *self == Anchor::MAX {
96 true96 true
crates/theme/src/scale.rs+8−1
@@ -2,7 +2,9 @@ use gpui::{AppContext, Hsla, SharedString}; · 1 unmodified line
2 2
3use crate::{ActiveTheme, Appearance};3use crate::{ActiveTheme, Appearance};
4 4
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.
6#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]8#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
7pub struct ColorScaleStep(usize);9pub struct ColorScaleStep(usize);
8 10
@@ -37,6 +39,10 @@ impl ColorScaleStep { · 28 unmodified lines
37 ];39 ];
38}40}
39 41
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.
40pub struct ColorScale(Vec<Hsla>);46pub struct ColorScale(Vec<Hsla>);
41 47
42impl FromIterator<Hsla> for ColorScale {48impl FromIterator<Hsla> for ColorScale {
@@ -229,6 +235,7 @@ impl IntoIterator for ColorScales { · 186 unmodified lines
229 }235 }
230}236}
231 237
238/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
232pub struct ColorScaleSet {239pub struct ColorScaleSet {
233 name: SharedString,240 name: SharedString,
234 light: ColorScale,241 light: ColorScale,
crates/theme/src/settings.rs+1−1
@@ -27,7 +27,7 @@ pub struct ThemeSettings { · 26 unmodified lines
27}27}
28 28
29#[derive(Default)]29#[derive(Default)]
30pub struct AdjustedBufferFontSize(Pixels);30pub(crate) struct AdjustedBufferFontSize(Pixels);
31 31
32#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]32#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
33pub struct ThemeSettingsContent {33pub struct ThemeSettingsContent {
crates/theme/src/styles/colors.rs+4−1
@@ -2,11 +2,12 @@ use gpui::Hsla; · 1 unmodified line
2use refineable::Refineable;2use refineable::Refineable;
3use std::sync::Arc;3use std::sync::Arc;
4 4
5use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};5use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors};
6 6
7#[derive(Refineable, Clone, Debug)]7#[derive(Refineable, Clone, Debug)]
8#[refineable(Debug, serde::Deserialize)]8#[refineable(Debug, serde::Deserialize)]
9pub struct ThemeColors {9pub struct ThemeColors {
10 /// Border color. Used for most borders, is usually a high contrast color.
10 pub border: Hsla,11 pub border: Hsla,
11 /// Border color. Used for deemphasized borders, like a visual divider between two sections12 /// Border color. Used for deemphasized borders, like a visual divider between two sections
12 pub border_variant: Hsla,13 pub border_variant: Hsla,
@@ -219,6 +220,8 @@ pub struct ThemeStyles { · 206 unmodified lines
219 220
220 #[refineable]221 #[refineable]
221 pub colors: ThemeColors,222 pub colors: ThemeColors,
223
224 #[refineable]
222 pub status: StatusColors,225 pub status: StatusColors,
223 pub player: PlayerColors,226 pub player: PlayerColors,
224 pub syntax: Arc<SyntaxTheme>,227 pub syntax: Arc<SyntaxTheme>,
crates/theme/src/styles/status.rs+0−9
@@ -78,15 +78,6 @@ pub struct StatusColors { · 77 unmodified lines
78 pub warning_border: Hsla,78 pub warning_border: Hsla,
79}79}
80 80
81impl 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
90pub struct DiagnosticColors {81pub struct DiagnosticColors {
91 pub error: Hsla,82 pub error: Hsla,
92 pub warning: Hsla,83 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
1mod default_colors;9mod default_colors;
2mod default_theme;10mod default_theme;
3mod one_themes;11mod 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 @@
1use gpui::{ClickEvent, WindowContext};1use gpui::{ClickEvent, WindowContext};
2 2
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.
4pub trait Clickable {4pub trait Clickable {
5 /// Sets the click handler that will fire whenever the element is clicked.5 /// Sets the click handler that will fire whenever the element is clicked.
6 fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;6 fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;
crates/ui/src/components/avatar.rs+35−5
@@ -1,13 +1,26 @@
1use crate::prelude::*;1use crate::prelude::*;
2use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};2use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};
3 3
4/// The shape of an [`Avatar`].
4#[derive(Debug, Default, PartialEq, Clone)]5#[derive(Debug, Default, PartialEq, Clone)]
5pub enum Shape {6pub enum AvatarShape {
7 /// The avatar is shown in a circle.
6 #[default]8 #[default]
7 Circle,9 Circle,
10 /// The avatar is shown in a rectangle with rounded corners.
8 RoundedRectangle,11 RoundedRectangle,
9}12}
10 13
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/// ```
11#[derive(IntoElement)]24#[derive(IntoElement)]
12pub struct Avatar {25pub struct Avatar {
13 image: Img,26 image: Img,
@@ -18,7 +31,7 @@ pub struct Avatar { · 4 unmodified lines
18impl RenderOnce for Avatar {31impl RenderOnce for Avatar {
19 fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {32 fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
20 if self.image.style().corner_radii.top_left.is_none() {33 if self.image.style().corner_radii.top_left.is_none() {
21 self = self.shape(Shape::Circle);34 self = self.shape(AvatarShape::Circle);
22 }35 }
23 36
24 let size = cx.rem_size();37 let size = cx.rem_size();
@@ -66,14 +79,31 @@ impl Avatar { · 41 unmodified lines
66 }79 }
67 }80 }
68 81
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 {
70 self.image = match shape {93 self.image = match shape {
71 Shape::Circle => self.image.rounded_full(),94 AvatarShape::Circle => self.image.rounded_full(),
72 Shape::RoundedRectangle => self.image.rounded_md(),95 AvatarShape::RoundedRectangle => self.image.rounded_md(),
73 };96 };
74 self97 self
75 }98 }
76 99
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 /// ```
77 pub fn grayscale(mut self, grayscale: bool) -> Self {107 pub fn grayscale(mut self, grayscale: bool) -> Self {
78 self.image = self.image.grayscale(grayscale);108 self.image = self.image.grayscale(grayscale);
79 self109 self
crates/ui/src/components/button/button.rs+175−5
@@ -2,11 +2,69 @@ use gpui::{AnyView, DefiniteLength}; · 1 unmodified line
2 2
3use crate::{prelude::*, IconPosition, KeyBinding};3use crate::{prelude::*, IconPosition, KeyBinding};
4use crate::{4use crate::{
5 ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,5 ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
6};6};
7 7
8use super::button_icon::ButtonIcon;8use super::button_icon::ButtonIcon;
9 9
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///
10#[derive(IntoElement)]68#[derive(IntoElement)]
11pub struct Button {69pub struct Button {
12 base: ButtonLike,70 base: ButtonLike,
@@ -14,15 +72,21 @@ pub struct Button { · 1 unmodified line
14 label_color: Option<Color>,72 label_color: Option<Color>,
15 label_size: Option<LabelSize>,73 label_size: Option<LabelSize>,
16 selected_label: Option<SharedString>,74 selected_label: Option<SharedString>,
17 icon: Option<Icon>,75 icon: Option<IconName>,
18 icon_position: Option<IconPosition>,76 icon_position: Option<IconPosition>,
19 icon_size: Option<IconSize>,77 icon_size: Option<IconSize>,
20 icon_color: Option<Color>,78 icon_color: Option<Color>,
21 selected_icon: Option<Icon>,79 selected_icon: Option<IconName>,
22 key_binding: Option<KeyBinding>,80 key_binding: Option<KeyBinding>,
23}81}
24 82
25impl Button {83impl 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.
26 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {90 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
27 Self {91 Self {
28 base: ButtonLike::new(id),92 base: ButtonLike::new(id),
@@ -39,46 +103,55 @@ impl Button { · 10 unmodified lines
39 }103 }
40 }104 }
41 105
106 /// Sets the color of the button's label.
42 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {107 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
43 self.label_color = label_color.into();108 self.label_color = label_color.into();
44 self109 self
45 }110 }
46 111
112 /// Defines the size of the button's label.
47 pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {113 pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
48 self.label_size = label_size.into();114 self.label_size = label_size.into();
49 self115 self
50 }116 }
51 117
118 /// Sets the label used when the button is in a selected state.
52 pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {119 pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
53 self.selected_label = label.into().map(Into::into);120 self.selected_label = label.into().map(Into::into);
54 self121 self
55 }122 }
56 123
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 {
58 self.icon = icon.into();126 self.icon = icon.into();
59 self127 self
60 }128 }
61 129
130 /// Sets the position of the icon relative to the label.
62 pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {131 pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
63 self.icon_position = icon_position.into();132 self.icon_position = icon_position.into();
64 self133 self
65 }134 }
66 135
136 /// Specifies the size of the button's icon.
67 pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {137 pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
68 self.icon_size = icon_size.into();138 self.icon_size = icon_size.into();
69 self139 self
70 }140 }
71 141
142 /// Sets the color of the button's icon.
72 pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {143 pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
73 self.icon_color = icon_color.into();144 self.icon_color = icon_color.into();
74 self145 self
75 }146 }
76 147
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 {
78 self.selected_icon = icon.into();150 self.selected_icon = icon.into();
79 self151 self
80 }152 }
81 153
154 /// Binds a key combination to the button for keyboard shortcuts.
82 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {155 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
83 self.key_binding = key_binding.into();156 self.key_binding = key_binding.into();
84 self157 self
@@ -86,6 +159,22 @@ impl Button { · 1 unmodified line
86}159}
87 160
88impl Selectable for Button {161impl 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.
89 fn selected(mut self, selected: bool) -> Self {178 fn selected(mut self, selected: bool) -> Self {
90 self.base = self.base.selected(selected);179 self.base = self.base.selected(selected);
91 self180 self
@@ -93,6 +182,19 @@ impl Selectable for Button { · 1 unmodified line
93}182}
94 183
95impl SelectableButton for Button {184impl 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.
96 fn selected_style(mut self, style: ButtonStyle) -> Self {198 fn selected_style(mut self, style: ButtonStyle) -> Self {
97 self.base = self.base.selected_style(style);199 self.base = self.base.selected_style(style);
98 self200 self
@@ -100,6 +202,22 @@ impl SelectableButton for Button { · 1 unmodified line
100}202}
101 203
102impl Disableable for Button {204impl 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.
103 fn disabled(mut self, disabled: bool) -> Self {221 fn disabled(mut self, disabled: bool) -> Self {
104 self.base = self.base.disabled(disabled);222 self.base = self.base.disabled(disabled);
105 self223 self
@@ -107,6 +225,7 @@ impl Disableable for Button { · 1 unmodified line
107}225}
108 226
109impl Clickable for Button {227impl Clickable for Button {
228 /// Sets the click event handler for the button.
110 fn on_click(229 fn on_click(
111 mut self,230 mut self,
112 handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,231 handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
@@ -117,11 +236,40 @@ impl Clickable for Button { · 4 unmodified lines
117}236}
118 237
119impl FixedWidth for Button {238impl 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.
120 fn width(mut self, width: DefiniteLength) -> Self {255 fn width(mut self, width: DefiniteLength) -> Self {
121 self.base = self.base.width(width);256 self.base = self.base.width(width);
122 self257 self
123 }258 }
124 259
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.
125 fn full_width(mut self) -> Self {273 fn full_width(mut self) -> Self {
126 self.base = self.base.full_width();274 self.base = self.base.full_width();
127 self275 self
@@ -129,20 +277,42 @@ impl FixedWidth for Button { · 1 unmodified line
129}277}
130 278
131impl ButtonCommon for Button {279impl ButtonCommon for Button {
280 /// Sets the button's id.
132 fn id(&self) -> &ElementId {281 fn id(&self) -> &ElementId {
133 self.base.id()282 self.base.id()
134 }283 }
135 284
285 /// Sets the visual style of the button using a [`ButtonStyle`].
136 fn style(mut self, style: ButtonStyle) -> Self {286 fn style(mut self, style: ButtonStyle) -> Self {
137 self.base = self.base.style(style);287 self.base = self.base.style(style);
138 self288 self
139 }289 }
140 290
291 /// Sets the button's size using a [`ButtonSize`].
141 fn size(mut self, size: ButtonSize) -> Self {292 fn size(mut self, size: ButtonSize) -> Self {
142 self.base = self.base.size(size);293 self.base = self.base.size(size);
143 self294 self
144 }295 }
145 296
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.
146 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {316 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
147 self.base = self.base.tooltip(tooltip);317 self.base = self.base.tooltip(tooltip);
148 self318 self
crates/ui/src/components/button/button_icon.rs+6−6
@@ -1,4 +1,4 @@
1use crate::{prelude::*, Icon, IconElement, IconSize};1use crate::{prelude::*, Icon, IconName, IconSize};
2 2
3/// An icon that appears within a button.3/// An icon that appears within a button.
4///4///
@@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; · 1 unmodified line
6/// or as a standalone icon, like in [`IconButton`](crate::IconButton).6/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
7#[derive(IntoElement)]7#[derive(IntoElement)]
8pub(super) struct ButtonIcon {8pub(super) struct ButtonIcon {
9 icon: Icon,9 icon: IconName,
10 size: IconSize,10 size: IconSize,
11 color: Color,11 color: Color,
12 disabled: bool,12 disabled: bool,
13 selected: bool,13 selected: bool,
14 selected_icon: Option<Icon>,14 selected_icon: Option<IconName>,
15 selected_style: Option<ButtonStyle>,15 selected_style: Option<ButtonStyle>,
16}16}
17 17
18impl ButtonIcon {18impl ButtonIcon {
19 pub fn new(icon: Icon) -> Self {19 pub fn new(icon: IconName) -> Self {
20 Self {20 Self {
21 icon,21 icon,
22 size: IconSize::default(),22 size: IconSize::default(),
@@ -44,7 +44,7 @@ impl ButtonIcon { · 21 unmodified lines
44 self44 self
45 }45 }
46 46
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 {
48 self.selected_icon = icon.into();48 self.selected_icon = icon.into();
49 self49 self
50 }50 }
@@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { · 37 unmodified lines
88 self.color88 self.color
89 };89 };
90 90
91 IconElement::new(icon).size(self.size).color(icon_color)91 Icon::new(icon).size(self.size).color(icon_color)
92 }92 }
93}93}
crates/ui/src/components/button/button_like.rs+6−2
@@ -4,10 +4,12 @@ use smallvec::SmallVec; · 3 unmodified lines
4 4
5use crate::prelude::*;5use crate::prelude::*;
6 6
7/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
7pub trait SelectableButton: Selectable {8pub trait SelectableButton: Selectable {
8 fn selected_style(self, style: ButtonStyle) -> Self;9 fn selected_style(self, style: ButtonStyle) -> Self;
9}10}
10 11
12/// A common set of traits all buttons must implement.
11pub trait ButtonCommon: Clickable + Disableable {13pub trait ButtonCommon: Clickable + Disableable {
12 /// A unique element ID to identify the button.14 /// A unique element ID to identify the button.
13 fn id(&self) -> &ElementId;15 fn id(&self) -> &ElementId;
@@ -93,6 +95,7 @@ impl From<ButtonStyle> for Color { · 79 unmodified lines
93 }95 }
94}96}
95 97
98/// The visual appearance of a button.
96#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]99#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
97pub enum ButtonStyle {100pub enum ButtonStyle {
98 /// A filled button with a solid background color. Provides emphasis versus101 /// A filled button with a solid background color. Provides emphasis versus
@@ -260,8 +263,9 @@ impl ButtonStyle { · 161 unmodified lines
260 }263 }
261}264}
262 265
263/// ButtonSize can also be used to help build non-button elements266/// The height of a button.
264/// that are consistently sized with buttons.267///
268/// Can also be used to size non-button elements to align with [`Button`]s.
265#[derive(Default, PartialEq, Clone, Copy)]269#[derive(Default, PartialEq, Clone, Copy)]
266pub enum ButtonSize {270pub enum ButtonSize {
267 Large,271 Large,
crates/ui/src/components/button/icon_button.rs+5−5
@@ -1,21 +1,21 @@
1use gpui::{AnyView, DefiniteLength};1use gpui::{AnyView, DefiniteLength};
2 2
3use crate::{prelude::*, SelectableButton};3use crate::{prelude::*, SelectableButton};
4use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};4use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
5 5
6use super::button_icon::ButtonIcon;6use super::button_icon::ButtonIcon;
7 7
8#[derive(IntoElement)]8#[derive(IntoElement)]
9pub struct IconButton {9pub struct IconButton {
10 base: ButtonLike,10 base: ButtonLike,
11 icon: Icon,11 icon: IconName,
12 icon_size: IconSize,12 icon_size: IconSize,
13 icon_color: Color,13 icon_color: Color,
14 selected_icon: Option<Icon>,14 selected_icon: Option<IconName>,
15}15}
16 16
17impl IconButton {17impl IconButton {
18 pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {18 pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
19 Self {19 Self {
20 base: ButtonLike::new(id),20 base: ButtonLike::new(id),
21 icon,21 icon,
@@ -35,7 +35,7 @@ impl IconButton { · 13 unmodified lines
35 self35 self
36 }36 }
37 37
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 {
39 self.selected_icon = icon.into();39 self.selected_icon = icon.into();
40 self40 self
41 }41 }
crates/ui/src/components/checkbox.rs+3−3
@@ -1,7 +1,7 @@
1use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};1use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
2 2
3use crate::prelude::*;3use crate::prelude::*;
4use crate::{Color, Icon, IconElement, Selection};4use crate::{Color, Icon, IconName, Selection};
5 5
6pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;6pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
7 7
@@ -47,7 +47,7 @@ impl RenderOnce for Checkbox { · 39 unmodified lines
47 let group_id = format!("checkbox_group_{:?}", self.id);47 let group_id = format!("checkbox_group_{:?}", self.id);
48 48
49 let icon = match self.checked {49 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(
51 if self.disabled {51 if self.disabled {
52 Color::Disabled52 Color::Disabled
53 } else {53 } else {
@@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { · 1 unmodified line
55 },55 },
56 )),56 )),
57 Selection::Indeterminate => Some(57 Selection::Indeterminate => Some(
58 IconElement::new(Icon::Dash)58 Icon::new(IconName::Dash)
59 .size(IconSize::Small)59 .size(IconSize::Small)
60 .color(if self.disabled {60 .color(if self.disabled {
61 Color::Disabled61 Color::Disabled
crates/ui/src/components/context_menu.rs+5−5
@@ -1,6 +1,6 @@
1use crate::{1use crate::{
2 h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,2 h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator,
3 ListSeparator, ListSubHeader,3 ListSubHeader,
4};4};
5use gpui::{5use gpui::{
6 px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,6 px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
@@ -14,7 +14,7 @@ enum ContextMenuItem { · 7 unmodified lines
14 Header(SharedString),14 Header(SharedString),
15 Entry {15 Entry {
16 label: SharedString,16 label: SharedString,
17 icon: Option<Icon>,17 icon: Option<IconName>,
18 handler: Rc<dyn Fn(&mut WindowContext)>,18 handler: Rc<dyn Fn(&mut WindowContext)>,
19 action: Option<Box<dyn Action>>,19 action: Option<Box<dyn Action>>,
20 },20 },
@@ -117,7 +117,7 @@ impl ContextMenu { · 96 unmodified lines
117 label: label.into(),117 label: label.into(),
118 action: Some(action.boxed_clone()),118 action: Some(action.boxed_clone()),
119 handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),119 handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
120 icon: Some(Icon::Link),120 icon: Some(IconName::Link),
121 });121 });
122 self122 self
123 }123 }
@@ -280,7 +280,7 @@ impl Render for ContextMenu { · 156 unmodified lines
280 h_stack()280 h_stack()
281 .gap_1()281 .gap_1()
282 .child(Label::new(label.clone()))282 .child(Label::new(label.clone()))
283 .child(IconElement::new(*icon))283 .child(Icon::new(*icon))
284 .into_any_element()284 .into_any_element()
285 } else {285 } else {
286 Label::new(label.clone()).into_any_element()286 Label::new(label.clone()).into_any_element()
crates/ui/src/components/disclosure.rs+3−3
@@ -1,6 +1,6 @@
1use gpui::ClickEvent;1use gpui::ClickEvent;
2 2
3use crate::{prelude::*, Color, Icon, IconButton, IconSize};3use crate::{prelude::*, Color, IconButton, IconName, IconSize};
4 4
5#[derive(IntoElement)]5#[derive(IntoElement)]
6pub struct Disclosure {6pub struct Disclosure {
@@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { · 25 unmodified lines
32 IconButton::new(32 IconButton::new(
33 self.id,33 self.id,
34 match self.is_open {34 match self.is_open {
35 true => Icon::ChevronDown,35 true => IconName::ChevronDown,
36 false => Icon::ChevronRight,36 false => IconName::ChevronRight,
37 },37 },
38 )38 )
39 .icon_color(Color::Muted)39 .icon_color(Color::Muted)
crates/ui/src/components/divider.rs+1−0
@@ -7,6 +7,7 @@ enum DividerDirection { · 6 unmodified lines
7 Vertical,7 Vertical,
8}8}
9 9
10/// The color of a [`Divider`].
10#[derive(Default)]11#[derive(Default)]
11pub enum DividerColor {12pub enum DividerColor {
12 Border,13 Border,
crates/ui/src/components/icon.rs+101−101
@@ -22,7 +22,7 @@ impl IconSize { · 21 unmodified lines
22}22}
23 23
24#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]24#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
25pub enum Icon {25pub enum IconName {
26 Ai,26 Ai,
27 ArrowDown,27 ArrowDown,
28 ArrowLeft,28 ArrowLeft,
@@ -111,118 +111,108 @@ pub enum Icon { · 82 unmodified lines
111 ZedXCopilot,111 ZedXCopilot,
112}112}
113 113
114impl Icon {114impl IconName {
115 pub fn path(self) -> &'static str {115 pub fn path(self) -> &'static str {
116 match self {116 match self {
117 Icon::Ai => "icons/ai.svg",117 IconName::Ai => "icons/ai.svg",
118 Icon::ArrowDown => "icons/arrow_down.svg",118 IconName::ArrowDown => "icons/arrow_down.svg",
119 Icon::ArrowLeft => "icons/arrow_left.svg",119 IconName::ArrowLeft => "icons/arrow_left.svg",
120 Icon::ArrowRight => "icons/arrow_right.svg",120 IconName::ArrowRight => "icons/arrow_right.svg",
121 Icon::ArrowUp => "icons/arrow_up.svg",121 IconName::ArrowUp => "icons/arrow_up.svg",
122 Icon::ArrowUpRight => "icons/arrow_up_right.svg",122 IconName::ArrowUpRight => "icons/arrow_up_right.svg",
123 Icon::ArrowCircle => "icons/arrow_circle.svg",123 IconName::ArrowCircle => "icons/arrow_circle.svg",
124 Icon::AtSign => "icons/at_sign.svg",124 IconName::AtSign => "icons/at_sign.svg",
125 Icon::AudioOff => "icons/speaker_off.svg",125 IconName::AudioOff => "icons/speaker_off.svg",
126 Icon::AudioOn => "icons/speaker_loud.svg",126 IconName::AudioOn => "icons/speaker_loud.svg",
127 Icon::Backspace => "icons/backspace.svg",127 IconName::Backspace => "icons/backspace.svg",
128 Icon::Bell => "icons/bell.svg",128 IconName::Bell => "icons/bell.svg",
129 Icon::BellOff => "icons/bell_off.svg",129 IconName::BellOff => "icons/bell_off.svg",
130 Icon::BellRing => "icons/bell_ring.svg",130 IconName::BellRing => "icons/bell_ring.svg",
131 Icon::Bolt => "icons/bolt.svg",131 IconName::Bolt => "icons/bolt.svg",
132 Icon::CaseSensitive => "icons/case_insensitive.svg",132 IconName::CaseSensitive => "icons/case_insensitive.svg",
133 Icon::Check => "icons/check.svg",133 IconName::Check => "icons/check.svg",
134 Icon::ChevronDown => "icons/chevron_down.svg",134 IconName::ChevronDown => "icons/chevron_down.svg",
135 Icon::ChevronLeft => "icons/chevron_left.svg",135 IconName::ChevronLeft => "icons/chevron_left.svg",
136 Icon::ChevronRight => "icons/chevron_right.svg",136 IconName::ChevronRight => "icons/chevron_right.svg",
137 Icon::ChevronUp => "icons/chevron_up.svg",137 IconName::ChevronUp => "icons/chevron_up.svg",
138 Icon::Close => "icons/x.svg",138 IconName::Close => "icons/x.svg",
139 Icon::Collab => "icons/user_group_16.svg",139 IconName::Collab => "icons/user_group_16.svg",
140 Icon::Command => "icons/command.svg",140 IconName::Command => "icons/command.svg",
141 Icon::Control => "icons/control.svg",141 IconName::Control => "icons/control.svg",
142 Icon::Copilot => "icons/copilot.svg",142 IconName::Copilot => "icons/copilot.svg",
143 Icon::CopilotDisabled => "icons/copilot_disabled.svg",143 IconName::CopilotDisabled => "icons/copilot_disabled.svg",
144 Icon::CopilotError => "icons/copilot_error.svg",144 IconName::CopilotError => "icons/copilot_error.svg",
145 Icon::CopilotInit => "icons/copilot_init.svg",145 IconName::CopilotInit => "icons/copilot_init.svg",
146 Icon::Copy => "icons/copy.svg",146 IconName::Copy => "icons/copy.svg",
147 Icon::Dash => "icons/dash.svg",147 IconName::Dash => "icons/dash.svg",
148 Icon::Delete => "icons/delete.svg",148 IconName::Delete => "icons/delete.svg",
149 Icon::Disconnected => "icons/disconnected.svg",149 IconName::Disconnected => "icons/disconnected.svg",
150 Icon::Ellipsis => "icons/ellipsis.svg",150 IconName::Ellipsis => "icons/ellipsis.svg",
151 Icon::Envelope => "icons/feedback.svg",151 IconName::Envelope => "icons/feedback.svg",
152 Icon::Escape => "icons/escape.svg",152 IconName::Escape => "icons/escape.svg",
153 Icon::ExclamationTriangle => "icons/warning.svg",153 IconName::ExclamationTriangle => "icons/warning.svg",
154 Icon::Exit => "icons/exit.svg",154 IconName::Exit => "icons/exit.svg",
155 Icon::ExternalLink => "icons/external_link.svg",155 IconName::ExternalLink => "icons/external_link.svg",
156 Icon::File => "icons/file.svg",156 IconName::File => "icons/file.svg",
157 Icon::FileDoc => "icons/file_icons/book.svg",157 IconName::FileDoc => "icons/file_icons/book.svg",
158 Icon::FileGeneric => "icons/file_icons/file.svg",158 IconName::FileGeneric => "icons/file_icons/file.svg",
159 Icon::FileGit => "icons/file_icons/git.svg",159 IconName::FileGit => "icons/file_icons/git.svg",
160 Icon::FileLock => "icons/file_icons/lock.svg",160 IconName::FileLock => "icons/file_icons/lock.svg",
161 Icon::FileRust => "icons/file_icons/rust.svg",161 IconName::FileRust => "icons/file_icons/rust.svg",
162 Icon::FileToml => "icons/file_icons/toml.svg",162 IconName::FileToml => "icons/file_icons/toml.svg",
163 Icon::FileTree => "icons/project.svg",163 IconName::FileTree => "icons/project.svg",
164 Icon::Filter => "icons/filter.svg",164 IconName::Filter => "icons/filter.svg",
165 Icon::Folder => "icons/file_icons/folder.svg",165 IconName::Folder => "icons/file_icons/folder.svg",
166 Icon::FolderOpen => "icons/file_icons/folder_open.svg",166 IconName::FolderOpen => "icons/file_icons/folder_open.svg",
167 Icon::FolderX => "icons/stop_sharing.svg",167 IconName::FolderX => "icons/stop_sharing.svg",
168 Icon::Github => "icons/github.svg",168 IconName::Github => "icons/github.svg",
169 Icon::Hash => "icons/hash.svg",169 IconName::Hash => "icons/hash.svg",
170 Icon::InlayHint => "icons/inlay_hint.svg",170 IconName::InlayHint => "icons/inlay_hint.svg",
171 Icon::Link => "icons/link.svg",171 IconName::Link => "icons/link.svg",
172 Icon::MagicWand => "icons/magic_wand.svg",172 IconName::MagicWand => "icons/magic_wand.svg",
173 Icon::MagnifyingGlass => "icons/magnifying_glass.svg",173 IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
174 Icon::MailOpen => "icons/mail_open.svg",174 IconName::MailOpen => "icons/mail_open.svg",
175 Icon::Maximize => "icons/maximize.svg",175 IconName::Maximize => "icons/maximize.svg",
176 Icon::Menu => "icons/menu.svg",176 IconName::Menu => "icons/menu.svg",
177 Icon::MessageBubbles => "icons/conversations.svg",177 IconName::MessageBubbles => "icons/conversations.svg",
178 Icon::Mic => "icons/mic.svg",178 IconName::Mic => "icons/mic.svg",
179 Icon::MicMute => "icons/mic_mute.svg",179 IconName::MicMute => "icons/mic_mute.svg",
180 Icon::Minimize => "icons/minimize.svg",180 IconName::Minimize => "icons/minimize.svg",
181 Icon::Option => "icons/option.svg",181 IconName::Option => "icons/option.svg",
182 Icon::PageDown => "icons/page_down.svg",182 IconName::PageDown => "icons/page_down.svg",
183 Icon::PageUp => "icons/page_up.svg",183 IconName::PageUp => "icons/page_up.svg",
184 Icon::Plus => "icons/plus.svg",184 IconName::Plus => "icons/plus.svg",
185 Icon::Public => "icons/public.svg",185 IconName::Public => "icons/public.svg",
186 Icon::Quote => "icons/quote.svg",186 IconName::Quote => "icons/quote.svg",
187 Icon::Replace => "icons/replace.svg",187 IconName::Replace => "icons/replace.svg",
188 Icon::ReplaceAll => "icons/replace_all.svg",188 IconName::ReplaceAll => "icons/replace_all.svg",
189 Icon::ReplaceNext => "icons/replace_next.svg",189 IconName::ReplaceNext => "icons/replace_next.svg",
190 Icon::Return => "icons/return.svg",190 IconName::Return => "icons/return.svg",
191 Icon::Screen => "icons/desktop.svg",191 IconName::Screen => "icons/desktop.svg",
192 Icon::SelectAll => "icons/select_all.svg",192 IconName::SelectAll => "icons/select_all.svg",
193 Icon::Shift => "icons/shift.svg",193 IconName::Shift => "icons/shift.svg",
194 Icon::Snip => "icons/snip.svg",194 IconName::Snip => "icons/snip.svg",
195 Icon::Space => "icons/space.svg",195 IconName::Space => "icons/space.svg",
196 Icon::Split => "icons/split.svg",196 IconName::Split => "icons/split.svg",
197 Icon::Tab => "icons/tab.svg",197 IconName::Tab => "icons/tab.svg",
198 Icon::Terminal => "icons/terminal.svg",198 IconName::Terminal => "icons/terminal.svg",
199 Icon::Update => "icons/update.svg",199 IconName::Update => "icons/update.svg",
200 Icon::WholeWord => "icons/word_search.svg",200 IconName::WholeWord => "icons/word_search.svg",
201 Icon::XCircle => "icons/error.svg",201 IconName::XCircle => "icons/error.svg",
202 Icon::ZedXCopilot => "icons/zed_x_copilot.svg",202 IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
203 }203 }
204 }204 }
205}205}
206 206
207#[derive(IntoElement)]207#[derive(IntoElement)]
208pub struct IconElement {208pub struct Icon {
209 path: SharedString,209 path: SharedString,
210 color: Color,210 color: Color,
211 size: IconSize,211 size: IconSize,
212}212}
213 213
214impl RenderOnce for IconElement {214impl Icon {
215 fn render(self, cx: &mut WindowContext) -> impl IntoElement {215 pub fn new(icon: IconName) -> Self {
216 svg()
217 .size(self.size.rems())
218 .flex_none()
219 .path(self.path)
220 .text_color(self.color.color(cx))
221 }
222}
223
224impl IconElement {
225 pub fn new(icon: Icon) -> Self {
226 Self {216 Self {
227 path: icon.path().into(),217 path: icon.path().into(),
228 color: Color::default(),218 color: Color::default(),
@@ -248,3 +238,13 @@ impl IconElement { · 19 unmodified lines
248 self238 self
249 }239 }
250}240}
241
242impl 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 @@
1use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};1use crate::{h_stack, prelude::*, Icon, IconName, IconSize};
2use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};2use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};
3 3
4#[derive(IntoElement, Clone)]4#[derive(IntoElement, Clone)]
@@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { · 21 unmodified lines
26 .text_color(cx.theme().colors().text_muted)26 .text_color(cx.theme().colors().text_muted)
27 .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))27 .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
28 .when(keystroke.modifiers.control, |el| {28 .when(keystroke.modifiers.control, |el| {
29 el.child(KeyIcon::new(Icon::Control))29 el.child(KeyIcon::new(IconName::Control))
30 })30 })
31 .when(keystroke.modifiers.alt, |el| {31 .when(keystroke.modifiers.alt, |el| {
32 el.child(KeyIcon::new(Icon::Option))32 el.child(KeyIcon::new(IconName::Option))
33 })33 })
34 .when(keystroke.modifiers.command, |el| {34 .when(keystroke.modifiers.command, |el| {
35 el.child(KeyIcon::new(Icon::Command))35 el.child(KeyIcon::new(IconName::Command))
36 })36 })
37 .when(keystroke.modifiers.shift, |el| {37 .when(keystroke.modifiers.shift, |el| {
38 el.child(KeyIcon::new(Icon::Shift))38 el.child(KeyIcon::new(IconName::Shift))
39 })39 })
40 .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))40 .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
41 .when(key_icon.is_none(), |el| {41 .when(key_icon.is_none(), |el| {
@@ -62,21 +62,21 @@ impl KeyBinding { · 20 unmodified lines
62 Some(Self::new(key_binding))62 Some(Self::new(key_binding))
63 }63 }
64 64
65 fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {65 fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
66 match keystroke.key.as_str() {66 match keystroke.key.as_str() {
67 "left" => Some(Icon::ArrowLeft),67 "left" => Some(IconName::ArrowLeft),
68 "right" => Some(Icon::ArrowRight),68 "right" => Some(IconName::ArrowRight),
69 "up" => Some(Icon::ArrowUp),69 "up" => Some(IconName::ArrowUp),
70 "down" => Some(Icon::ArrowDown),70 "down" => Some(IconName::ArrowDown),
71 "backspace" => Some(Icon::Backspace),71 "backspace" => Some(IconName::Backspace),
72 "delete" => Some(Icon::Delete),72 "delete" => Some(IconName::Delete),
73 "return" => Some(Icon::Return),73 "return" => Some(IconName::Return),
74 "enter" => Some(Icon::Return),74 "enter" => Some(IconName::Return),
75 "tab" => Some(Icon::Tab),75 "tab" => Some(IconName::Tab),
76 "space" => Some(Icon::Space),76 "space" => Some(IconName::Space),
77 "escape" => Some(Icon::Escape),77 "escape" => Some(IconName::Escape),
78 "pagedown" => Some(Icon::PageDown),78 "pagedown" => Some(IconName::PageDown),
79 "pageup" => Some(Icon::PageUp),79 "pageup" => Some(IconName::PageUp),
80 _ => None,80 _ => None,
81 }81 }
82 }82 }
@@ -120,13 +120,13 @@ impl Key { · 37 unmodified lines
120 120
121#[derive(IntoElement)]121#[derive(IntoElement)]
122pub struct KeyIcon {122pub struct KeyIcon {
123 icon: Icon,123 icon: IconName,
124}124}
125 125
126impl RenderOnce for KeyIcon {126impl RenderOnce for KeyIcon {
127 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {127 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
128 div().w(rems(14. / 16.)).child(128 div().w(rems(14. / 16.)).child(
129 IconElement::new(self.icon)129 Icon::new(self.icon)
130 .size(IconSize::Small)130 .size(IconSize::Small)
131 .color(Color::Muted),131 .color(Color::Muted),
132 )132 )
@@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { · 1 unmodified line
134}134}
135 135
136impl KeyIcon {136impl KeyIcon {
137 pub fn new(icon: Icon) -> Self {137 pub fn new(icon: IconName) -> Self {
138 Self { icon }138 Self { icon }
139 }139 }
140}140}
crates/ui/src/components/label/label.rs+57−0
@@ -2,6 +2,28 @@ use gpui::WindowContext; · 1 unmodified line
2 2
3use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};3use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
4 4
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/// ```
5#[derive(IntoElement)]27#[derive(IntoElement)]
6pub struct Label {28pub struct Label {
7 base: LabelLike,29 base: LabelLike,
@@ -9,6 +31,13 @@ pub struct Label { · 1 unmodified line
9}31}
10 32
11impl Label {33impl Label {
34 /// Create a new `Label` with the given text.
35 ///
36 /// # Examples
37 ///
38 /// ```
39 /// let my_label = Label::new("Hello, World!");
40 /// ```
12 pub fn new(label: impl Into<SharedString>) -> Self {41 pub fn new(label: impl Into<SharedString>) -> Self {
13 Self {42 Self {
14 base: LabelLike::new(),43 base: LabelLike::new(),
@@ -18,21 +47,49 @@ impl Label { · 3 unmodified lines
18}47}
19 48
20impl LabelCommon for Label {49impl 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 /// ```
21 fn size(mut self, size: LabelSize) -> Self {57 fn size(mut self, size: LabelSize) -> Self {
22 self.base = self.base.size(size);58 self.base = self.base.size(size);
23 self59 self
24 }60 }
25 61
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 /// ```
26 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {69 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
27 self.base = self.base.line_height_style(line_height_style);70 self.base = self.base.line_height_style(line_height_style);
28 self71 self
29 }72 }
30 73
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 /// ```
31 fn color(mut self, color: Color) -> Self {81 fn color(mut self, color: Color) -> Self {
32 self.base = self.base.color(color);82 self.base = self.base.color(color);
33 self83 self
34 }84 }
35 85
86 /// Sets the strikethrough property of the label.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// let my_label = Label::new("Hello, World!").strikethrough(true);
92 /// ```
36 fn strikethrough(mut self, strikethrough: bool) -> Self {93 fn strikethrough(mut self, strikethrough: bool) -> Self {
37 self.base = self.base.strikethrough(strikethrough);94 self.base = self.base.strikethrough(strikethrough);
38 self95 self
crates/ui/src/components/label/label_like.rs+1−0
@@ -19,6 +19,7 @@ pub enum LineHeightStyle { · 18 unmodified lines
19 UiLabel,19 UiLabel,
20}20}
21 21
22/// A common set of traits all labels must implement.
22pub trait LabelCommon {23pub trait LabelCommon {
23 fn size(self, size: LabelSize) -> Self;24 fn size(self, size: LabelSize) -> Self;
24 fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;25 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 @@
1use crate::prelude::*;1use crate::prelude::*;
2use crate::{h_stack, Icon, IconElement, IconSize, Label};2use crate::{h_stack, Icon, IconName, IconSize, Label};
3 3
4#[derive(IntoElement)]4#[derive(IntoElement)]
5pub struct ListSubHeader {5pub struct ListSubHeader {
6 label: SharedString,6 label: SharedString,
7 start_slot: Option<Icon>,7 start_slot: Option<IconName>,
8 inset: bool,8 inset: bool,
9}9}
10 10
@@ -17,7 +17,7 @@ impl ListSubHeader { · 6 unmodified lines
17 }17 }
18 }18 }
19 19
20 pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {20 pub fn left_icon(mut self, left_icon: Option<IconName>) -> Self {
21 self.start_slot = left_icon;21 self.start_slot = left_icon;
22 self22 self
23 }23 }
@@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { · 16 unmodified lines
40 .flex()40 .flex()
41 .gap_1()41 .gap_1()
42 .items_center()42 .items_center()
43 .children(self.start_slot.map(|i| {43 .children(
44 IconElement::new(i)44 self.start_slot
45 .color(Color::Muted)45 .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)),
46 .size(IconSize::Small)46 )
47 }))
48 .child(Label::new(self.label.clone()).color(Color::Muted)),47 .child(Label::new(self.label.clone()).color(Color::Muted)),
49 ),48 ),
50 )49 )
crates/ui/src/components/popover_menu.rs+1−0
@@ -108,6 +108,7 @@ impl<M: ManagedView> PopoverMenu<M> { · 107 unmodified lines
108 }108 }
109}109}
110 110
111/// Creates a [`PopoverMenu`]
111pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {112pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
112 PopoverMenu {113 PopoverMenu {
113 id: id.into(),114 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
39 }39 }
40}40}
41 41
42/// Creates a [`RightClickMenu`]
42pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {43pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
43 RightClickMenu {44 RightClickMenu {
44 id: id.into(),45 id: id.into(),
crates/ui/src/components/stack.rs+2−6
@@ -2,17 +2,13 @@ use gpui::{div, Div}; · 1 unmodified line
2 2
3use crate::StyledExt;3use crate::StyledExt;
4 4
5/// Horizontally stacks elements.5/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()`
6///
7/// Sets `flex()`, `flex_row()`, `items_center()`
8#[track_caller]6#[track_caller]
9pub fn h_stack() -> Div {7pub fn h_stack() -> Div {
10 div().h_flex()8 div().h_flex()
11}9}
12 10
13/// Vertically stacks elements.11/// Vertically stacks elements. Sets `flex()`, `flex_col()`
14///
15/// Sets `flex()`, `flex_col()`
16#[track_caller]12#[track_caller]
17pub fn v_stack() -> Div {13pub fn v_stack() -> Div {
18 div().v_flex()14 div().v_flex()
crates/ui/src/components/stories/button.rs+3−3
@@ -1,7 +1,7 @@
1use gpui::Render;1use gpui::Render;
2use story::Story;2use story::Story;
3 3
4use crate::{prelude::*, Icon};4use crate::{prelude::*, IconName};
5use crate::{Button, ButtonStyle};5use crate::{Button, ButtonStyle};
6 6
7pub struct ButtonStory;7pub struct ButtonStory;
@@ -23,12 +23,12 @@ impl Render for ButtonStory { · 15 unmodified lines
23 .child(Story::label("With `label_color`"))23 .child(Story::label("With `label_color`"))
24 .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))24 .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
25 .child(Story::label("With `icon`"))25 .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))
27 .child(Story::label("Selected with `icon`"))27 .child(Story::label("Selected with `icon`"))
28 .child(28 .child(
29 Button::new("filled_and_selected_with_icon", "Click me")29 Button::new("filled_and_selected_with_icon", "Click me")
30 .selected(true)30 .selected(true)
31 .icon(Icon::FileGit),31 .icon(IconName::FileGit),
32 )32 )
33 .child(Story::label("Default (Subtle)"))33 .child(Story::label("Default (Subtle)"))
34 .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))34 .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
3use strum::IntoEnumIterator;3use strum::IntoEnumIterator;
4 4
5use crate::prelude::*;5use crate::prelude::*;
6use crate::{Icon, IconElement};6use crate::{Icon, IconName};
7 7
8pub struct IconStory;8pub struct IconStory;
9 9
10impl Render for IconStory {10impl Render for IconStory {
11 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {11 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
12 let icons = Icon::iter();12 let icons = IconName::iter();
13 13
14 Story::container()14 Story::container()
15 .child(Story::title_for::<IconElement>())15 .child(Story::title_for::<Icon>())
16 .child(Story::label("All Icons"))16 .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)))
18 }18 }
19}19}
crates/ui/src/components/stories/icon_button.rs+9−9
@@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line
2use story::{StoryContainer, StoryItem, StorySection};2use story::{StoryContainer, StoryItem, StorySection};
3 3
4use crate::{prelude::*, Tooltip};4use crate::{prelude::*, Tooltip};
5use crate::{Icon, IconButton};5use crate::{IconButton, IconName};
6 6
7pub struct IconButtonStory;7pub struct IconButtonStory;
8 8
@@ -10,7 +10,7 @@ impl Render for IconButtonStory { · 1 unmodified line
10 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {10 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
11 let default_button = StoryItem::new(11 let default_button = StoryItem::new(
12 "Default",12 "Default",
13 IconButton::new("default_icon_button", Icon::Hash),13 IconButton::new("default_icon_button", IconName::Hash),
14 )14 )
15 .description("Displays an icon button.")15 .description("Displays an icon button.")
16 .usage(16 .usage(
@@ -21,7 +21,7 @@ impl Render for IconButtonStory { · 4 unmodified lines
21 21
22 let selected_button = StoryItem::new(22 let selected_button = StoryItem::new(
23 "Selected",23 "Selected",
24 IconButton::new("selected_icon_button", Icon::Hash).selected(true),24 IconButton::new("selected_icon_button", IconName::Hash).selected(true),
25 )25 )
26 .description("Displays an icon button that is selected.")26 .description("Displays an icon button that is selected.")
27 .usage(27 .usage(
@@ -32,9 +32,9 @@ impl Render for IconButtonStory { · 4 unmodified lines
32 32
33 let selected_with_selected_icon = StoryItem::new(33 let selected_with_selected_icon = StoryItem::new(
34 "Selected with `selected_icon`",34 "Selected with `selected_icon`",
35 IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)35 IconButton::new("selected_with_selected_icon_button", IconName::AudioOn)
36 .selected(true)36 .selected(true)
37 .selected_icon(Icon::AudioOff),37 .selected_icon(IconName::AudioOff),
38 )38 )
39 .description(39 .description(
40 "Displays an icon button that is selected and shows a different icon when selected.",40 "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
49 49
50 let disabled_button = StoryItem::new(50 let disabled_button = StoryItem::new(
51 "Disabled",51 "Disabled",
52 IconButton::new("disabled_icon_button", Icon::Hash).disabled(true),52 IconButton::new("disabled_icon_button", IconName::Hash).disabled(true),
53 )53 )
54 .description("Displays an icon button that is disabled.")54 .description("Displays an icon button that is disabled.")
55 .usage(55 .usage(
@@ -60,7 +60,7 @@ impl Render for IconButtonStory { · 4 unmodified lines
60 60
61 let with_on_click_button = StoryItem::new(61 let with_on_click_button = StoryItem::new(
62 "With `on_click`",62 "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| {
64 println!("Clicked!");64 println!("Clicked!");
65 }),65 }),
66 )66 )
@@ -75,7 +75,7 @@ impl Render for IconButtonStory { · 8 unmodified lines
75 75
76 let with_tooltip_button = StoryItem::new(76 let with_tooltip_button = StoryItem::new(
77 "With `tooltip`",77 "With `tooltip`",
78 IconButton::new("with_tooltip_button", Icon::MessageBubbles)78 IconButton::new("with_tooltip_button", IconName::MessageBubbles)
79 .tooltip(|cx| Tooltip::text("Open messages", cx)),79 .tooltip(|cx| Tooltip::text("Open messages", cx)),
80 )80 )
81 .description("Displays an icon button that has a tooltip when hovered.")81 .description("Displays an icon button that has a tooltip when hovered.")
@@ -88,7 +88,7 @@ impl Render for IconButtonStory { · 6 unmodified lines
88 88
89 let selected_with_tooltip_button = StoryItem::new(89 let selected_with_tooltip_button = StoryItem::new(
90 "Selected with `tooltip`",90 "Selected with `tooltip`",
91 IconButton::new("selected_with_tooltip_button", Icon::InlayHint)91 IconButton::new("selected_with_tooltip_button", IconName::InlayHint)
92 .selected(true)92 .selected(true)
93 .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),93 .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
94 )94 )
crates/ui/src/components/stories/list_header.rs+7−7
@@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line
2use story::Story;2use story::Story;
3 3
4use crate::{prelude::*, IconButton};4use crate::{prelude::*, IconButton};
5use crate::{Icon, ListHeader};5use crate::{IconName, ListHeader};
6 6
7pub struct ListHeaderStory;7pub struct ListHeaderStory;
8 8
@@ -13,19 +13,19 @@ impl Render for ListHeaderStory { · 4 unmodified lines
13 .child(Story::label("Default"))13 .child(Story::label("Default"))
14 .child(ListHeader::new("Section 1"))14 .child(ListHeader::new("Section 1"))
15 .child(Story::label("With left icon"))15 .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)))
17 .child(Story::label("With left icon and meta"))17 .child(Story::label("With left icon and meta"))
18 .child(18 .child(
19 ListHeader::new("Section 3")19 ListHeader::new("Section 3")
20 .start_slot(IconElement::new(Icon::BellOff))20 .start_slot(Icon::new(IconName::BellOff))
21 .end_slot(IconButton::new("action_1", Icon::Bolt)),21 .end_slot(IconButton::new("action_1", IconName::Bolt)),
22 )22 )
23 .child(Story::label("With multiple meta"))23 .child(Story::label("With multiple meta"))
24 .child(24 .child(
25 ListHeader::new("Section 4")25 ListHeader::new("Section 4")
26 .end_slot(IconButton::new("action_1", Icon::Bolt))26 .end_slot(IconButton::new("action_1", IconName::Bolt))
27 .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle))27 .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle))
28 .end_slot(IconButton::new("action_3", Icon::Plus)),28 .end_slot(IconButton::new("action_3", IconName::Plus)),
29 )29 )
30 }30 }
31}31}
crates/ui/src/components/stories/list_item.rs+13−13
@@ -1,8 +1,8 @@
1use gpui::Render;1use gpui::{Render, SharedUrl};
2use story::Story;2use story::Story;
3 3
4use crate::{prelude::*, Avatar};4use crate::{prelude::*, Avatar};
5use crate::{Icon, ListItem};5use crate::{IconName, ListItem};
6 6
7pub struct ListItemStory;7pub struct ListItemStory;
8 8
@@ -18,13 +18,13 @@ impl Render for ListItemStory { · 9 unmodified lines
18 ListItem::new("inset_list_item")18 ListItem::new("inset_list_item")
19 .inset(true)19 .inset(true)
20 .start_slot(20 .start_slot(
21 IconElement::new(Icon::Bell)21 Icon::new(IconName::Bell)
22 .size(IconSize::Small)22 .size(IconSize::Small)
23 .color(Color::Muted),23 .color(Color::Muted),
24 )24 )
25 .child("Hello, world!")25 .child("Hello, world!")
26 .end_slot(26 .end_slot(
27 IconElement::new(Icon::Bell)27 Icon::new(IconName::Bell)
28 .size(IconSize::Small)28 .size(IconSize::Small)
29 .color(Color::Muted),29 .color(Color::Muted),
30 ),30 ),
@@ -34,7 +34,7 @@ impl Render for ListItemStory { · 3 unmodified lines
34 ListItem::new("with start slot_icon")34 ListItem::new("with start slot_icon")
35 .child("Hello, world!")35 .child("Hello, world!")
36 .start_slot(36 .start_slot(
37 IconElement::new(Icon::Bell)37 Icon::new(IconName::Bell)
38 .size(IconSize::Small)38 .size(IconSize::Small)
39 .color(Color::Muted),39 .color(Color::Muted),
40 ),40 ),
@@ -43,7 +43,7 @@ impl Render for ListItemStory { · 2 unmodified lines
43 .child(43 .child(
44 ListItem::new("with_start slot avatar")44 ListItem::new("with_start slot avatar")
45 .child("Hello, world!")45 .child("Hello, world!")
46 .start_slot(Avatar::new(SharedString::from(46 .start_slot(Avatar::new(SharedUrl::from(
47 "https://avatars.githubusercontent.com/u/1714999?v=4",47 "https://avatars.githubusercontent.com/u/1714999?v=4",
48 ))),48 ))),
49 )49 )
@@ -51,7 +51,7 @@ impl Render for ListItemStory { · 1 unmodified line
51 .child(51 .child(
52 ListItem::new("with_left_avatar")52 ListItem::new("with_left_avatar")
53 .child("Hello, world!")53 .child("Hello, world!")
54 .end_slot(Avatar::new(SharedString::from(54 .end_slot(Avatar::new(SharedUrl::from(
55 "https://avatars.githubusercontent.com/u/1714999?v=4",55 "https://avatars.githubusercontent.com/u/1714999?v=4",
56 ))),56 ))),
57 )57 )
@@ -62,23 +62,23 @@ impl Render for ListItemStory { · 4 unmodified lines
62 .end_slot(62 .end_slot(
63 h_stack()63 h_stack()
64 .gap_2()64 .gap_2()
65 .child(Avatar::new(SharedString::from(65 .child(Avatar::new(SharedUrl::from(
66 "https://avatars.githubusercontent.com/u/1789?v=4",66 "https://avatars.githubusercontent.com/u/1789?v=4",
67 )))67 )))
68 .child(Avatar::new(SharedString::from(68 .child(Avatar::new(SharedUrl::from(
69 "https://avatars.githubusercontent.com/u/1789?v=4",69 "https://avatars.githubusercontent.com/u/1789?v=4",
70 )))70 )))
71 .child(Avatar::new(SharedString::from(71 .child(Avatar::new(SharedUrl::from(
72 "https://avatars.githubusercontent.com/u/1789?v=4",72 "https://avatars.githubusercontent.com/u/1789?v=4",
73 )))73 )))
74 .child(Avatar::new(SharedString::from(74 .child(Avatar::new(SharedUrl::from(
75 "https://avatars.githubusercontent.com/u/1789?v=4",75 "https://avatars.githubusercontent.com/u/1789?v=4",
76 )))76 )))
77 .child(Avatar::new(SharedString::from(77 .child(Avatar::new(SharedUrl::from(
78 "https://avatars.githubusercontent.com/u/1789?v=4",78 "https://avatars.githubusercontent.com/u/1789?v=4",
79 ))),79 ))),
80 )80 )
81 .end_hover_slot(Avatar::new(SharedString::from(81 .end_hover_slot(Avatar::new(SharedUrl::from(
82 "https://avatars.githubusercontent.com/u/1714999?v=4",82 "https://avatars.githubusercontent.com/u/1714999?v=4",
83 ))),83 ))),
84 )84 )
crates/ui/src/components/stories/tab.rs+1−1
@@ -27,7 +27,7 @@ impl Render for TabStory { · 26 unmodified lines
27 h_stack().child(27 h_stack().child(
28 Tab::new("tab_1")28 Tab::new("tab_1")
29 .end_slot(29 .end_slot(
30 IconButton::new("close_button", Icon::Close)30 IconButton::new("close_button", IconName::Close)
31 .icon_color(Color::Muted)31 .icon_color(Color::Muted)
32 .size(ButtonSize::None)32 .size(ButtonSize::None)
33 .icon_size(IconSize::XSmall),33 .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
38 h_stack().child(38 h_stack().child(
39 TabBar::new("tab_bar_1")39 TabBar::new("tab_bar_1")
40 .start_child(40 .start_child(
41 IconButton::new("navigate_backward", Icon::ArrowLeft)41 IconButton::new("navigate_backward", IconName::ArrowLeft)
42 .icon_size(IconSize::Small),42 .icon_size(IconSize::Small),
43 )43 )
44 .start_child(44 .start_child(
45 IconButton::new("navigate_forward", Icon::ArrowRight)45 IconButton::new("navigate_forward", IconName::ArrowRight)
46 .icon_size(IconSize::Small),46 .icon_size(IconSize::Small),
47 )47 )
48 .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small))
49 .end_child(48 .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),
51 )54 )
52 .children(tabs),55 .children(tabs),
53 ),56 ),
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.
2pub trait Disableable {2pub trait Disableable {
3 /// Sets whether the element is disabled.3 /// Sets whether the element is disabled.
4 fn disabled(self, disabled: bool) -> Self;4 fn disabled(self, disabled: bool) -> Self;
crates/ui/src/fixed.rs+1−1
@@ -1,6 +1,6 @@
1use gpui::DefiniteLength;1use gpui::DefiniteLength;
2 2
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.
4pub trait FixedWidth {4pub trait FixedWidth {
5 /// Sets the width of the element.5 /// Sets the width of the element.
6 fn width(self, width: DefiniteLength) -> Self;6 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
1pub use gpui::prelude::*;3pub use gpui::prelude::*;
2pub use gpui::{4pub use gpui::{
3 div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,5 div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
@@ -15,6 +17,6 @@ pub use crate::{h_stack, v_stack}; · 11 unmodified lines
15pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};17pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
16pub use crate::{ButtonCommon, Color, StyledExt};18pub use crate::{ButtonCommon, Color, StyledExt};
17pub use crate::{Headline, HeadlineSize};19pub use crate::{Headline, HeadlineSize};
18pub use crate::{Icon, IconElement, IconPosition, IconSize};20pub use crate::{Icon, IconName, IconPosition, IconSize};
19pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};21pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};
20pub use theme::ActiveTheme;22pub use theme::ActiveTheme;
crates/ui/src/selectable.rs+9−0
@@ -1,18 +1,27 @@
1/// A trait for elements that can be selected.1/// 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.
2pub trait Selectable {4pub trait Selectable {
3 /// Sets whether the element is selected.5 /// Sets whether the element is selected.
4 fn selected(self, selected: bool) -> Self;6 fn selected(self, selected: bool) -> Self;
5}7}
6 8
9/// Represents the selection status of an element.
7#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]10#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
8pub enum Selection {11pub enum Selection {
12 /// The element is not selected.
9 #[default]13 #[default]
10 Unselected,14 Unselected,
15 /// The selection state of the element is indeterminate.
11 Indeterminate,16 Indeterminate,
17 /// The element is selected.
12 Selected,18 Selected,
13}19}
14 20
15impl Selection {21impl Selection {
22 /// Returns the inverse of the current selection status.
23 ///
24 /// Indeterminate states become selected if inverted.
16 pub fn inverse(&self) -> Self {25 pub fn inverse(&self) -> Self {
17 match self {26 match self {
18 Self::Unselected | Self::Indeterminate => Self::Selected,27 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
14 .shadow(index.shadow())14 .shadow(index.shadow())
15}15}
16 16
17/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.17/// Extends [`gpui::Styled`] with Zed-specific styling methods.
18pub trait StyledExt: Styled + Sized {18pub trait StyledExt: Styled + Sized {
19 /// Horizontally stacks elements.19 /// Horizontally stacks elements.
20 ///20 ///
@@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines
30 self.flex().flex_col()30 self.flex().flex_col()
31 }31 }
32 32
33 /// Sets the text size using a [`UiTextSize`].
33 fn text_ui_size(self, size: UiTextSize) -> Self {34 fn text_ui_size(self, size: UiTextSize) -> Self {
34 self.text_size(size.rems())35 self.text_size(size.rems())
35 }36 }
@@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
40 ///41 ///
41 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.42 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
42 ///43 ///
43 /// Use [`text_ui_sm`] for regular-sized text.44 /// Use `text_ui_sm` for smaller text.
44 fn text_ui(self) -> Self {45 fn text_ui(self) -> Self {
45 self.text_size(UiTextSize::default().rems())46 self.text_size(UiTextSize::default().rems())
46 }47 }
@@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
51 ///52 ///
52 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.53 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
53 ///54 ///
54 /// Use [`text_ui`] for regular-sized text.55 /// Use `text_ui` for regular-sized text.
55 fn text_ui_sm(self) -> Self {56 fn text_ui_sm(self) -> Self {
56 self.text_size(UiTextSize::Small.rems())57 self.text_size(UiTextSize::Small.rems())
57 }58 }
@@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines
62 ///63 ///
63 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.64 /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
64 ///65 ///
65 /// Use [`text_ui`] for regular-sized text.66 /// Use `text_ui` for regular-sized text.
66 fn text_ui_xs(self) -> Self {67 fn text_ui_xs(self) -> Self {
67 self.text_size(UiTextSize::XSmall.rems())68 self.text_size(UiTextSize::XSmall.rems())
68 }69 }
@@ -78,7 +79,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines
78 self.text_size(settings.buffer_font_size(cx))79 self.text_size(settings.buffer_font_size(cx))
79 }80 }
80 81
81 /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements82 /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
82 ///83 ///
83 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`84 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
84 ///85 ///
@@ -87,7 +88,7 @@ pub trait StyledExt: Styled + Sized { · 2 unmodified lines
87 elevated(self, cx, ElevationIndex::Surface)88 elevated(self, cx, ElevationIndex::Surface)
88 }89 }
89 90
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.
91 ///92 ///
92 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`93 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
93 ///94 ///
@@ -100,7 +101,7 @@ pub trait StyledExt: Styled + Sized { · 6 unmodified lines
100 ///101 ///
101 /// 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.102 /// 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.
102 ///103 ///
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.
104 ///105 ///
105 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`106 /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
106 ///107 ///
@@ -119,26 +120,32 @@ pub trait StyledExt: Styled + Sized { · 12 unmodified lines
119 self.border_color(cx.theme().colors().border_variant)120 self.border_color(cx.theme().colors().border_variant)
120 }121 }
121 122
123 /// Sets the background color to red for debugging when building UI.
122 fn debug_bg_red(self) -> Self {124 fn debug_bg_red(self) -> Self {
123 self.bg(hsla(0. / 360., 1., 0.5, 1.))125 self.bg(hsla(0. / 360., 1., 0.5, 1.))
124 }126 }
125 127
128 /// Sets the background color to green for debugging when building UI.
126 fn debug_bg_green(self) -> Self {129 fn debug_bg_green(self) -> Self {
127 self.bg(hsla(120. / 360., 1., 0.5, 1.))130 self.bg(hsla(120. / 360., 1., 0.5, 1.))
128 }131 }
129 132
133 /// Sets the background color to blue for debugging when building UI.
130 fn debug_bg_blue(self) -> Self {134 fn debug_bg_blue(self) -> Self {
131 self.bg(hsla(240. / 360., 1., 0.5, 1.))135 self.bg(hsla(240. / 360., 1., 0.5, 1.))
132 }136 }
133 137
138 /// Sets the background color to yellow for debugging when building UI.
134 fn debug_bg_yellow(self) -> Self {139 fn debug_bg_yellow(self) -> Self {
135 self.bg(hsla(60. / 360., 1., 0.5, 1.))140 self.bg(hsla(60. / 360., 1., 0.5, 1.))
136 }141 }
137 142
143 /// Sets the background color to cyan for debugging when building UI.
138 fn debug_bg_cyan(self) -> Self {144 fn debug_bg_cyan(self) -> Self {
139 self.bg(hsla(160. / 360., 1., 0.5, 1.))145 self.bg(hsla(160. / 360., 1., 0.5, 1.))
140 }146 }
141 147
148 /// Sets the background color to magenta for debugging when building UI.
142 fn debug_bg_magenta(self) -> Self {149 fn debug_bg_magenta(self) -> Self {
143 self.bg(hsla(300. / 360., 1., 0.5, 1.))150 self.bg(hsla(300. / 360., 1., 0.5, 1.))
144 }151 }
crates/ui/src/styles/color.rs+1−0
@@ -1,6 +1,7 @@
1use gpui::{Hsla, WindowContext};1use gpui::{Hsla, WindowContext};
2use theme::ActiveTheme;2use theme::ActiveTheme;
3 3
4/// Sets a color that has a consistent meaning across all themes.
4#[derive(Debug, Default, PartialEq, Copy, Clone)]5#[derive(Debug, Default, PartialEq, Copy, Clone)]
5pub enum Color {6pub enum Color {
6 #[default]7 #[default]
crates/ui/src/styles/elevation.rs+1−0
@@ -85,6 +85,7 @@ impl LayerIndex { · 84 unmodified lines
85 }85 }
86}86}
87 87
88/// An appropriate z-index for the given layer based on its intended useage.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum ElementIndex {90pub enum ElementIndex {
90 Effect,91 Effect,
crates/ui/src/styles/typography.rs+1−0
@@ -38,6 +38,7 @@ impl UiTextSize { · 37 unmodified lines
38 }38 }
39}39}
40 40
41/// The size of a [`Headline`] element
41#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]42#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
42pub enum HeadlineSize {43pub enum HeadlineSize {
43 XSmall,44 XSmall,
crates/ui/src/ui.rs+0−2
@@ -3,8 +3,6 @@ · 2 unmodified lines
3//! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.3//! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.
4//!4//!
5 5
6#![doc = include_str!("../docs/building-ui.md")]
7
8mod clickable;6mod clickable;
9mod components;7mod components;
10mod disableable;8mod 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
1mod format_distance;3mod format_distance;
2 4
3pub use format_distance::*;5pub use format_distance::*;
crates/ui/src/utils/format_distance.rs+10−10
@@ -7,10 +7,10 @@ pub enum DateTimeType { · 6 unmodified lines
7}7}
8 8
9impl DateTimeType {9impl DateTimeType {
10 /// Converts the DateTimeType to a NaiveDateTime.10 /// Converts the [`DateTimeType`] to a [`NaiveDateTime`].
11 ///11 ///
12 /// If the DateTimeType is already a NaiveDateTime, it will be returned as is.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.13 /// If the [`DateTimeType`] is a [`DateTime<Local>`], it will be converted to a [`NaiveDateTime`].
14 pub fn to_naive(&self) -> NaiveDateTime {14 pub fn to_naive(&self) -> NaiveDateTime {
15 match self {15 match self {
16 DateTimeType::Naive(naive) => *naive,16 DateTimeType::Naive(naive) => *naive,
@@ -68,13 +68,13 @@ impl FormatDistance { · 51 unmodified lines
68 }68 }
69}69}
70 70
71/// Calculates the distance in seconds between two NaiveDateTime objects.71/// Calculates the distance in seconds between two [`NaiveDateTime`] objects.
72/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.72/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
73///73///
74/// ## Arguments74/// ## Arguments
75///75///
76/// * `date` - A NaiveDateTime object representing the date of interest76/// * `date` - A [NaiveDateTime`] object representing the date of interest
77/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made77/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made
78fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {78fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
79 let duration = date.signed_duration_since(base_date);79 let duration = date.signed_duration_since(base_date);
80 -duration.num_seconds()80 -duration.num_seconds()
@@ -233,12 +233,12 @@ fn distance_string( · 152 unmodified lines
233///233///
234/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.234/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.
235///235///
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.
237///237///
238/// # Arguments238/// # Arguments
239///239///
240/// * `date` - The NaiveDateTime to compare.240/// * `date` - The [`NaiveDateTime`] to compare.
241/// * `base_date` - The NaiveDateTime to compare against.241/// * `base_date` - The [`NaiveDateTime`] to compare against.
242/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed242/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
243/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future243/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
244///244///
@@ -274,7 +274,7 @@ pub fn format_distance( · 29 unmodified lines
274///274///
275/// # Arguments275/// # Arguments
276///276///
277/// * `datetime` - The NaiveDateTime to compare with the current time.277/// * `datetime` - The [`NaiveDateTime`] to compare with the current time.
278/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed278/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
279/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future279/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
280///280///
crates/workspace/src/dock.rs+4−5
@@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> { · 27 unmodified lines
28 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);28 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
29 fn size(&self, cx: &WindowContext) -> Pixels;29 fn size(&self, cx: &WindowContext) -> Pixels;
30 fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);30 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>;
32 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;32 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
33 fn toggle_action(&self) -> Box<dyn Action>;33 fn toggle_action(&self) -> Box<dyn Action>;
34 fn icon_label(&self, _: &WindowContext) -> Option<String> {34 fn icon_label(&self, _: &WindowContext) -> Option<String> {
@@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { · 17 unmodified lines
52 fn set_active(&self, active: bool, cx: &mut WindowContext);52 fn set_active(&self, active: bool, cx: &mut WindowContext);
53 fn size(&self, cx: &WindowContext) -> Pixels;53 fn size(&self, cx: &WindowContext) -> Pixels;
54 fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);54 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>;
56 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;56 fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
57 fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;57 fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
58 fn icon_label(&self, cx: &WindowContext) -> Option<String>;58 fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -104,7 +104,7 @@ where · 45 unmodified lines
104 self.update(cx, |this, cx| this.set_size(size, cx))104 self.update(cx, |this, cx| this.set_size(size, cx))
105 }105 }
106 106
107 fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {107 fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
108 self.read(cx).icon(cx)108 self.read(cx).icon(cx)
109 }109 }
110 110
@@ -395,7 +395,6 @@ impl Dock { · 284 unmodified lines
395 })395 })
396 .ok();396 .ok();
397 }397 }
398 // todo!() we do not use this event in the production code (even in zed1), remove it
399 PanelEvent::Activate => {398 PanelEvent::Activate => {
400 if let Some(ix) = this399 if let Some(ix) = this
401 .panel_entries400 .panel_entries
@@ -775,7 +774,7 @@ pub mod test { · 373 unmodified lines
775 self.size = size.unwrap_or(px(300.));774 self.size = size.unwrap_or(px(300.));
776 }775 }
777 776
778 fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {777 fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
779 None778 None
780 }779 }
781 780
crates/workspace/src/modal_layer.rs+4−0
@@ -101,6 +101,10 @@ impl ModalLayer { · 100 unmodified lines
101 let active_modal = self.active_modal.as_ref()?;101 let active_modal = self.active_modal.as_ref()?;
102 active_modal.modal.view().downcast::<V>().ok()102 active_modal.modal.view().downcast::<V>().ok()
103 }103 }
104
105 pub fn has_active_modal(&self) -> bool {
106 self.active_modal.is_some()
107 }
104}108}
105 109
106impl Render for ModalLayer {110impl Render for ModalLayer {
crates/workspace/src/notifications.rs+2−101
@@ -175,7 +175,7 @@ pub mod simple_message_notification { · 174 unmodified lines
175 };175 };
176 use std::sync::Arc;176 use std::sync::Arc;
177 use ui::prelude::*;177 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};
179 179
180 pub struct MessageNotification {180 pub struct MessageNotification {
181 message: SharedString,181 message: SharedString,
@@ -230,7 +230,7 @@ pub mod simple_message_notification { · 48 unmodified lines
230 .child(230 .child(
231 div()231 div()
232 .id("cancel")232 .id("cancel")
233 .child(IconElement::new(Icon::Close))233 .child(Icon::new(IconName::Close))
234 .cursor_pointer()234 .cursor_pointer()
235 .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),235 .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
236 ),236 ),
@@ -247,105 +247,6 @@ pub mod simple_message_notification { · 10 unmodified lines
247 }))247 }))
248 }248 }
249 }249 }
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 // }
349}250}
350 251
351pub trait NotifyResultExt {252pub trait NotifyResultExt {
crates/workspace/src/pane.rs+9−9
@@ -31,8 +31,8 @@ use std::{ · 30 unmodified lines
31use theme::ThemeSettings;31use theme::ThemeSettings;
32 32
33use ui::{33use ui::{
34 prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label,34 prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator,
35 Tab, TabBar, TabPosition, Tooltip,35 Label, Tab, TabBar, TabPosition, Tooltip,
36};36};
37use ui::{v_stack, ContextMenu};37use ui::{v_stack, ContextMenu};
38use util::{maybe, truncate_and_remove_front, ResultExt};38use util::{maybe, truncate_and_remove_front, ResultExt};
@@ -384,7 +384,7 @@ impl Pane { · 345 unmodified lines
384 h_stack()384 h_stack()
385 .gap_2()385 .gap_2()
386 .child(386 .child(
387 IconButton::new("plus", Icon::Plus)387 IconButton::new("plus", IconName::Plus)
388 .icon_size(IconSize::Small)388 .icon_size(IconSize::Small)
389 .icon_color(Color::Muted)389 .icon_color(Color::Muted)
390 .on_click(cx.listener(|pane, _, cx| {390 .on_click(cx.listener(|pane, _, cx| {
@@ -406,7 +406,7 @@ impl Pane { · 15 unmodified lines
406 el.child(Self::render_menu_overlay(new_item_menu))406 el.child(Self::render_menu_overlay(new_item_menu))
407 })407 })
408 .child(408 .child(
409 IconButton::new("split", Icon::Split)409 IconButton::new("split", IconName::Split)
410 .icon_size(IconSize::Small)410 .icon_size(IconSize::Small)
411 .icon_color(Color::Muted)411 .icon_color(Color::Muted)
412 .on_click(cx.listener(|pane, _, cx| {412 .on_click(cx.listener(|pane, _, cx| {
@@ -427,11 +427,11 @@ impl Pane { · 14 unmodified lines
427 )427 )
428 .child({428 .child({
429 let zoomed = pane.is_zoomed();429 let zoomed = pane.is_zoomed();
430 IconButton::new("toggle_zoom", Icon::Maximize)430 IconButton::new("toggle_zoom", IconName::Maximize)
431 .icon_size(IconSize::Small)431 .icon_size(IconSize::Small)
432 .icon_color(Color::Muted)432 .icon_color(Color::Muted)
433 .selected(zoomed)433 .selected(zoomed)
434 .selected_icon(Icon::Minimize)434 .selected_icon(IconName::Minimize)
435 .on_click(cx.listener(|pane, _, cx| {435 .on_click(cx.listener(|pane, _, cx| {
436 pane.toggle_zoom(&crate::ToggleZoom, cx);436 pane.toggle_zoom(&crate::ToggleZoom, cx);
437 }))437 }))
@@ -1570,7 +1570,7 @@ impl Pane { · 1132 unmodified lines
1570 })1570 })
1571 .start_slot::<Indicator>(indicator)1571 .start_slot::<Indicator>(indicator)
1572 .end_slot(1572 .end_slot(
1573 IconButton::new("close tab", Icon::Close)1573 IconButton::new("close tab", IconName::Close)
1574 .icon_color(Color::Muted)1574 .icon_color(Color::Muted)
1575 .size(ButtonSize::None)1575 .size(ButtonSize::None)
1576 .icon_size(IconSize::XSmall)1576 .icon_size(IconSize::XSmall)
@@ -1676,7 +1676,7 @@ impl Pane { · 99 unmodified lines
1676 h_stack()1676 h_stack()
1677 .gap_2()1677 .gap_2()
1678 .child(1678 .child(
1679 IconButton::new("navigate_backward", Icon::ArrowLeft)1679 IconButton::new("navigate_backward", IconName::ArrowLeft)
1680 .icon_size(IconSize::Small)1680 .icon_size(IconSize::Small)
1681 .on_click({1681 .on_click({
1682 let view = cx.view().clone();1682 let view = cx.view().clone();
@@ -1686,7 +1686,7 @@ impl Pane { · 3 unmodified lines
1686 .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),1686 .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),
1687 )1687 )
1688 .child(1688 .child(
1689 IconButton::new("navigate_forward", Icon::ArrowRight)1689 IconButton::new("navigate_forward", IconName::ArrowRight)
1690 .icon_size(IconSize::Small)1690 .icon_size(IconSize::Small)
1691 .on_click({1691 .on_click({
1692 let view = cx.view().clone();1692 let view = cx.view().clone();
crates/workspace/src/pane_group.rs+46−22
@@ -12,7 +12,7 @@ use serde::Deserialize; · 11 unmodified lines
12use std::sync::Arc;12use std::sync::Arc;
13use ui::{prelude::*, Button};13use ui::{prelude::*, Button};
14 14
15const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)15const HANDLE_HITBOX_SIZE: f32 = 4.0;
16const HORIZONTAL_MIN_SIZE: f32 = 80.;16const HORIZONTAL_MIN_SIZE: f32 = 80.;
17const VERTICAL_MIN_SIZE: f32 = 100.;17const VERTICAL_MIN_SIZE: f32 = 100.;
18 18
@@ -579,12 +579,15 @@ mod element { · 560 unmodified lines
579 Size, Style, WeakView, WindowContext,579 Size, Style, WeakView, WindowContext,
580 };580 };
581 use parking_lot::Mutex;581 use parking_lot::Mutex;
582 use settings::Settings;
582 use smallvec::SmallVec;583 use smallvec::SmallVec;
583 use ui::prelude::*;584 use ui::prelude::*;
584 use util::ResultExt;585 use util::ResultExt;
585 586
586 use crate::Workspace;587 use crate::Workspace;
587 588
589 use crate::WorkspaceSettings;
590
588 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};591 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
589 592
590 const DIVIDER_SIZE: f32 = 1.0;593 const DIVIDER_SIZE: f32 = 1.0;
@@ -704,7 +707,6 @@ mod element { · 113 unmodified lines
704 proposed_current_pixel_change -= current_pixel_change;707 proposed_current_pixel_change -= current_pixel_change;
705 }708 }
706 709
707 // todo!(schedule serialize)
708 workspace710 workspace
709 .update(cx, |this, cx| this.schedule_serialize(cx))711 .update(cx, |this, cx| this.schedule_serialize(cx))
710 .log_err();712 .log_err();
@@ -834,20 +836,39 @@ mod element { · 123 unmodified lines
834 debug_assert!(flexes.len() == len);836 debug_assert!(flexes.len() == len);
835 debug_assert!(flex_values_in_bounds(flexes.as_slice()));837 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
836 838
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
837 let mut origin = bounds.origin;852 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;
839 854
840 let mut bounding_boxes = self.bounding_boxes.lock();855 let mut bounding_boxes = self.bounding_boxes.lock();
841 bounding_boxes.clear();856 bounding_boxes.clear();
842 857
843 for (ix, child) in self.children.iter_mut().enumerate() {858 for (ix, child) in self.children.iter_mut().enumerate() {
844 //todo!(active_pane_magnification)859 let child_flex = active_pane_magnification
845 // If using active pane magnification, need to switch to using860 .map(|magnification| {
846 // 1 for all non-active panes, and then the magnification for the861 if self.active_pane_ix == Some(ix) {
847 // active pane.862 magnification
863 } else {
864 1.
865 }
866 })
867 .unwrap_or_else(|| flexes[ix]);
868
848 let child_size = bounds869 let child_size = bounds
849 .size870 .size
850 .apply_along(self.axis, |_| space_per_flex * flexes[ix]);871 .apply_along(self.axis, |_| space_per_flex * child_flex);
851 872
852 let child_bounds = Bounds {873 let child_bounds = Bounds {
853 origin,874 origin,
@@ -857,20 +878,23 @@ mod element { · 3 unmodified lines
857 cx.with_z_index(0, |cx| {878 cx.with_z_index(0, |cx| {
858 child.draw(origin, child_size.into(), cx);879 child.draw(origin, child_size.into(), cx);
859 });880 });
860 cx.with_z_index(1, |cx| {881
861 if ix < len - 1 {882 if active_pane_magnification.is_none() {
862 Self::push_handle(883 cx.with_z_index(1, |cx| {
863 self.flexes.clone(),884 if ix < len - 1 {
864 state.clone(),885 Self::push_handle(
865 self.axis,886 self.flexes.clone(),
866 ix,887 state.clone(),
867 child_bounds,888 self.axis,
868 bounds,889 ix,
869 self.workspace.clone(),890 child_bounds,
870 cx,891 bounds,
871 );892 self.workspace.clone(),
872 }893 cx,
873 });894 );
895 }
896 });
897 }
874 898
875 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));899 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
876 }900 }
crates/workspace/src/shared_screen.rs+2−2
@@ -12,7 +12,7 @@ use gpui::{ · 11 unmodified lines
12 WindowContext,12 WindowContext,
13};13};
14use std::sync::{Arc, Weak};14use std::sync::{Arc, Weak};
15use ui::{h_stack, prelude::*, Icon, IconElement, Label};15use ui::{h_stack, prelude::*, Icon, IconName, Label};
16 16
17pub enum Event {17pub enum Event {
18 Close,18 Close,
@@ -100,7 +100,7 @@ impl Item for SharedScreen { · 81 unmodified lines
100 ) -> gpui::AnyElement {100 ) -> gpui::AnyElement {
101 h_stack()101 h_stack()
102 .gap_1()102 .gap_1()
103 .child(IconElement::new(Icon::Screen))103 .child(Icon::new(IconName::Screen))
104 .child(104 .child(
105 Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {105 Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
106 Color::Default106 Color::Default
crates/workspace/src/toolbar.rs+0−83
@@ -133,82 +133,6 @@ impl Render for Toolbar { · 132 unmodified lines
133 }133 }
134}134}
135 135
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
212impl Toolbar {136impl Toolbar {
213 pub fn new() -> Self {137 pub fn new() -> Self {
214 Self {138 Self {
@@ -312,10 +236,3 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> { · 97 unmodified lines
312 self.read(cx).row_count(cx)236 self.read(cx).row_count(cx)
313 }237 }
314}238}
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
943 cx: &mut ViewContext<Workspace>,943 cx: &mut ViewContext<Workspace>,
944 ) -> Task<Result<()>> {944 ) -> Task<Result<()>> {
945 let to_load = if let Some(pane) = pane.upgrade() {945 let to_load = if let Some(pane) = pane.upgrade() {
946 // todo!("focus")
947 // cx.focus(&pane);
948
949 pane.update(cx, |pane, cx| {946 pane.update(cx, |pane, cx| {
947 pane.focus(cx);
950 loop {948 loop {
951 // Retrieve the weak item handle from the history.949 // Retrieve the weak item handle from the history.
952 let entry = pane.nav_history_mut().pop(mode, cx)?;950 let entry = pane.nav_history_mut().pop(mode, cx)?;
@@ -1631,8 +1629,7 @@ impl Workspace { · 678 unmodified lines
1631 });1629 });
1632 }1630 }
1633 1631
1634 // todo!("focus")1632 cx.focus_self();
1635 // cx.focus_self();
1636 cx.notify();1633 cx.notify();
1637 self.serialize_workspace(cx);1634 self.serialize_workspace(cx);
1638 }1635 }
@@ -1713,6 +1710,7 @@ impl Workspace { · 74 unmodified lines
1713 cx.notify();1710 cx.notify();
1714 }1711 }
1715 1712
1713 // todo!()
1716 // #[cfg(any(test, feature = "test-support"))]1714 // #[cfg(any(test, feature = "test-support"))]
1717 // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {1715 // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1718 // self.zoomed.and_then(|view| view.upgrade(cx))1716 // self.zoomed.and_then(|view| view.upgrade(cx))
@@ -2992,7 +2990,6 @@ impl Workspace { · 1273 unmodified lines
2992 cx.notify();2990 cx.notify();
2993 }2991 }
2994 2992
2995 #[allow(unused)]
2996 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {2993 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2997 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {2994 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2998 cx.background_executor()2995 cx.background_executor()
@@ -3386,6 +3383,10 @@ impl Workspace { · 387 unmodified lines
3386 div3383 div
3387 }3384 }
3388 3385
3386 pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3387 self.modal_layer.read(cx).has_active_modal()
3388 }
3389
3389 pub fn active_modal<V: ManagedView + 'static>(3390 pub fn active_modal<V: ManagedView + 'static>(
3390 &mut self,3391 &mut self,
3391 cx: &ViewContext<Self>,3392 cx: &ViewContext<Self>,
crates/zed/src/zed.rs+0−1
@@ -764,7 +764,6 @@ fn open_bundled_file( · 763 unmodified lines
764 .detach_and_log_err(cx);764 .detach_and_log_err(cx);
765}765}
766 766
767// todo!()
768#[cfg(test)]767#[cfg(test)]
769mod tests {768mod tests {
770 use super::*;769 use super::*;