Unified · Split
| @@ -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", |
| @@ -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 | None | 2355 | 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 | None | 2688 | 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 | } |
| @@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines | |||
| 4 | }; | 4 | }; |
| 5 | use menu::Cancel; | 5 | use menu::Cancel; |
| 6 | use util::channel::ReleaseChannel; | 6 | use util::channel::ReleaseChannel; |
| 7 | use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; | 7 | use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; |
| 8 | 8 | ||
| 9 | pub struct UpdateNotification { | 9 | pub 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 | ), |
| @@ -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)] |
| 13 | pub struct CallSettingsContent { | 14 | pub 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 | ||
| @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; · 2 unmodified lines | |||
| 3 | use collections::{hash_map::Entry, HashMap, HashSet}; | 3 | use collections::{hash_map::Entry, HashMap, HashSet}; |
| 4 | use feature_flags::FeatureFlagAppExt; | 4 | use feature_flags::FeatureFlagAppExt; |
| 5 | use futures::{channel::mpsc, Future, StreamExt}; | 5 | use futures::{channel::mpsc, Future, StreamExt}; |
| 6 | use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task}; | 6 | use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task}; |
| 7 | use postage::{sink::Sink, watch}; | 7 | use postage::{sink::Sink, watch}; |
| 8 | use rpc::proto::{RequestMessage, UsersResponse}; | 8 | use rpc::proto::{RequestMessage, UsersResponse}; |
| 9 | use std::sync::{Arc, Weak}; | 9 | use std::sync::{Arc, Weak}; |
| @@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); · 9 unmodified lines | |||
| 19 | pub struct User { | 19 | pub 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)] |
| @@ -9,6 +9,8 @@ path = "src/collab_ui.rs" · 8 unmodified lines | |||
| 9 | doctest = false | 9 | doctest = false |
| 10 | 10 | ||
| 11 | [features] | 11 | [features] |
| 12 | default = [] | ||
| 13 | stories = ["dep:story"] | ||
| 12 | test-support = [ | 14 | test-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 | |||
| 44 | recent_projects = { path = "../recent_projects" } | 46 | recent_projects = { path = "../recent_projects" } |
| 45 | rpc = { path = "../rpc" } | 47 | rpc = { path = "../rpc" } |
| 46 | settings = { path = "../settings" } | 48 | settings = { path = "../settings" } |
| 49 | story = { path = "../story", optional = true } | ||
| 47 | feature_flags = { path = "../feature_flags"} | 50 | feature_flags = { path = "../feature_flags"} |
| 48 | theme = { path = "../theme" } | 51 | theme = { path = "../theme" } |
| 49 | theme_selector = { path = "../theme_selector" } | 52 | theme_selector = { path = "../theme_selector" } |
| @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; · 19 unmodified lines | |||
| 20 | use settings::{Settings, SettingsStore}; | 20 | use settings::{Settings, SettingsStore}; |
| 21 | use std::sync::Arc; | 21 | use std::sync::Arc; |
| 22 | use time::{OffsetDateTime, UtcOffset}; | 22 | use time::{OffsetDateTime, UtcOffset}; |
| 23 | use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; | 23 | use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; |
| 24 | use util::{ResultExt, TryFutureExt}; | 24 | use util::{ResultExt, TryFutureExt}; |
| 25 | use workspace::{ | 25 | use 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> { |
| @@ -31,7 +31,7 @@ use smallvec::SmallVec; · 30 unmodified lines | |||
| 31 | use std::{mem, sync::Arc}; | 31 | use std::{mem, sync::Arc}; |
| 32 | use theme::{ActiveTheme, ThemeSettings}; | 32 | use theme::{ActiveTheme, ThemeSettings}; |
| 33 | use ui::{ | 33 | use 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 | }; |
| 37 | use util::{maybe, ResultExt, TryFutureExt}; | 37 | use 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::Default | 2280 | 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::Default | 2292 | 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 | .button | 2537 | .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::Public | 2678 | IconName::Public |
| 2675 | } else { | 2679 | } else { |
| 2676 | Icon::Hash | 2680 | IconName::Hash |
| 2677 | }, | 2681 | }, |
| 2678 | ) | 2682 | ) |
| 2679 | .size(IconSize::Small) | 2683 | .size(IconSize::Small) |
| @@ -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( |
| @@ -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 | } |
| @@ -15,7 +15,7 @@ use std::sync::Arc; · 14 unmodified lines | |||
| 15 | use theme::{ActiveTheme, PlayerColors}; | 15 | use theme::{ActiveTheme, PlayerColors}; |
| 16 | use ui::{ | 16 | use 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 | }; |
| 20 | use util::ResultExt; | 20 | use util::ResultExt; |
| 21 | use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; | 21 | use 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::MicMute | 233 | ui::IconName::MicMute |
| 234 | } else { | 234 | } else { |
| 235 | ui::Icon::Mic | 235 | 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::AudioOff | 259 | ui::IconName::AudioOff |
| 260 | } else { | 260 | } else { |
| 261 | ui::Icon::AudioOn | 261 | 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)), |
| @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; · 18 unmodified lines | |||
| 19 | use settings::{Settings, SettingsStore}; | 19 | use settings::{Settings, SettingsStore}; |
| 20 | use std::{sync::Arc, time::Duration}; | 20 | use std::{sync::Arc, time::Duration}; |
| 21 | use time::{OffsetDateTime, UtcOffset}; | 21 | use time::{OffsetDateTime, UtcOffset}; |
| 22 | use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label}; | 22 | use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; |
| 23 | use util::{ResultExt, TryFutureExt}; | 23 | use util::{ResultExt, TryFutureExt}; |
| 24 | use workspace::{ | 24 | use 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).button | 659 | (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| { |
| @@ -1,9 +1,16 @@ | |||
| 1 | mod collab_notification; | ||
| 2 | pub mod incoming_call_notification; | ||
| 3 | pub mod project_shared_notification; | ||
| 4 | |||
| 5 | #[cfg(feature = "stories")] | ||
| 6 | mod stories; | ||
| 7 | |||
| 1 | use gpui::AppContext; | 8 | use gpui::AppContext; |
| 2 | use std::sync::Arc; | 9 | use std::sync::Arc; |
| 3 | use workspace::AppState; | 10 | use workspace::AppState; |
| 4 | 11 | ||
| 5 | pub mod incoming_call_notification; | 12 | #[cfg(feature = "stories")] |
| 6 | pub mod project_shared_notification; | 13 | pub use stories::*; |
| 7 | 14 | ||
| 8 | pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { | 15 | pub 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); |
| @@ -0,0 +1,52 @@ | |||
| 1 | use gpui::{img, prelude::*, AnyElement, SharedUrl}; | ||
| 2 | use smallvec::SmallVec; | ||
| 3 | use ui::prelude::*; | ||
| 4 | |||
| 5 | #[derive(IntoElement)] | ||
| 6 | pub struct CollabNotification { | ||
| 7 | avatar_uri: SharedUrl, | ||
| 8 | accept_button: Button, | ||
| 9 | dismiss_button: Button, | ||
| 10 | children: SmallVec<[AnyElement; 2]>, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl CollabNotification { | ||
| 14 | pub fn new( | ||
| 15 | avatar_uri: impl Into<SharedUrl>, | ||
| 16 | accept_button: Button, | ||
| 17 | dismiss_button: Button, | ||
| 18 | ) -> Self { | ||
| 19 | Self { | ||
| 20 | avatar_uri: avatar_uri.into(), | ||
| 21 | accept_button, | ||
| 22 | dismiss_button, | ||
| 23 | children: SmallVec::new(), | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | impl ParentElement for CollabNotification { | ||
| 29 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { | ||
| 30 | &mut self.children | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | impl RenderOnce for CollabNotification { | ||
| 35 | fn render(self, cx: &mut WindowContext) -> impl IntoElement { | ||
| 36 | h_stack() | ||
| 37 | .text_ui() | ||
| 38 | .justify_between() | ||
| 39 | .size_full() | ||
| 40 | .overflow_hidden() | ||
| 41 | .elevation_3(cx) | ||
| 42 | .p_2() | ||
| 43 | .gap_2() | ||
| 44 | .child(img(self.avatar_uri).w_12().h_12().rounded_full()) | ||
| 45 | .child(v_stack().overflow_hidden().children(self.children)) | ||
| 46 | .child( | ||
| 47 | v_stack() | ||
| 48 | .child(self.accept_button) | ||
| 49 | .child(self.dismiss_button), | ||
| 50 | ) | ||
| 51 | } | ||
| 52 | } | ||
| @@ -1,15 +1,12 @@ | |||
| 1 | use crate::notification_window_options; | 1 | use crate::notification_window_options; |
| 2 | use crate::notifications::collab_notification::CollabNotification; | ||
| 2 | use call::{ActiveCall, IncomingCall}; | 3 | use call::{ActiveCall, IncomingCall}; |
| 3 | use futures::StreamExt; | 4 | use futures::StreamExt; |
| 4 | use gpui::{ | 5 | use gpui::{prelude::*, AppContext, WindowHandle}; |
| 5 | img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext, | ||
| 6 | VisualContext as _, WindowHandle, | ||
| 7 | }; | ||
| 8 | use settings::Settings; | 6 | use settings::Settings; |
| 9 | use std::sync::{Arc, Weak}; | 7 | use std::sync::{Arc, Weak}; |
| 10 | use theme::ThemeSettings; | 8 | use theme::ThemeSettings; |
| 11 | use ui::prelude::*; | 9 | use ui::{prelude::*, Button, Label}; |
| 12 | use ui::{h_stack, v_stack, Button, Label}; | ||
| 13 | use util::ResultExt; | 10 | use util::ResultExt; |
| 14 | use workspace::AppState; | 11 | use 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_login | 143 | 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 | } |
| @@ -1,12 +1,13 @@ | |||
| 1 | use crate::notification_window_options; | 1 | use crate::notification_window_options; |
| 2 | use crate::notifications::collab_notification::CollabNotification; | ||
| 2 | use call::{room, ActiveCall}; | 3 | use call::{room, ActiveCall}; |
| 3 | use client::User; | 4 | use client::User; |
| 4 | use collections::HashMap; | 5 | use collections::HashMap; |
| 5 | use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext}; | 6 | use gpui::{AppContext, Size}; |
| 6 | use settings::Settings; | 7 | use settings::Settings; |
| 7 | use std::sync::{Arc, Weak}; | 8 | use std::sync::{Arc, Weak}; |
| 8 | use theme::ThemeSettings; | 9 | use theme::ThemeSettings; |
| 9 | use ui::{h_stack, prelude::*, v_stack, Button, Label}; | 10 | use ui::{prelude::*, Button, Label}; |
| 10 | use workspace::AppState; | 11 | use workspace::AppState; |
| 11 | 12 | ||
| 12 | pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { | 13 | pub 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 | } |
| @@ -0,0 +1,3 @@ | |||
| 1 | mod collab_notification; | ||
| 2 | |||
| 3 | pub use collab_notification::*; | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | use gpui::prelude::*; | ||
| 2 | use story::{StoryContainer, StoryItem, StorySection}; | ||
| 3 | use ui::prelude::*; | ||
| 4 | |||
| 5 | use crate::notifications::collab_notification::CollabNotification; | ||
| 6 | |||
| 7 | pub struct CollabNotificationStory; | ||
| 8 | |||
| 9 | impl Render for CollabNotificationStory { | ||
| 10 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { | ||
| 11 | let window_container = |width, height| div().w(px(width)).h(px(height)); | ||
| 12 | |||
| 13 | StoryContainer::new( | ||
| 14 | "CollabNotification Story", | ||
| 15 | "crates/collab_ui/src/notifications/stories/collab_notification.rs", | ||
| 16 | ) | ||
| 17 | .child( | ||
| 18 | StorySection::new().child(StoryItem::new( | ||
| 19 | "Incoming Call Notification", | ||
| 20 | window_container(400., 72.).child( | ||
| 21 | CollabNotification::new( | ||
| 22 | "https://avatars.githubusercontent.com/u/1486634?v=4", | ||
| 23 | Button::new("accept", "Accept"), | ||
| 24 | Button::new("decline", "Decline"), | ||
| 25 | ) | ||
| 26 | .child( | ||
| 27 | v_stack() | ||
| 28 | .overflow_hidden() | ||
| 29 | .child(Label::new("maxdeviant is sharing a project in Zed")), | ||
| 30 | ), | ||
| 31 | ), | ||
| 32 | )), | ||
| 33 | ) | ||
| 34 | .child( | ||
| 35 | StorySection::new().child(StoryItem::new( | ||
| 36 | "Project Shared Notification", | ||
| 37 | window_container(400., 72.).child( | ||
| 38 | CollabNotification::new( | ||
| 39 | "https://avatars.githubusercontent.com/u/1714999?v=4", | ||
| 40 | Button::new("open", "Open"), | ||
| 41 | Button::new("dismiss", "Dismiss"), | ||
| 42 | ) | ||
| 43 | .child(Label::new("iamnbutler")) | ||
| 44 | .child(Label::new("is sharing a project in Zed:")) | ||
| 45 | .child(Label::new("zed")), | ||
| 46 | ), | ||
| 47 | )), | ||
| 48 | ) | ||
| 49 | } | ||
| 50 | } | ||
| @@ -17,7 +17,9 @@ use util::{paths, ResultExt}; · 16 unmodified lines | |||
| 17 | use workspace::{ | 17 | use 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 | }; |
| 23 | use zed_actions::OpenBrowser; | 25 | use 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::Copilot | 59 | IconName::Copilot |
| 58 | } else { | 60 | } else { |
| 59 | Icon::CopilotDisabled | 61 | 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 { |
| @@ -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 | }; |
| 7 | use ui::{prelude::*, Button, Icon, Label}; | 7 | use ui::{prelude::*, Button, IconName, Label}; |
| 8 | use workspace::ModalView; | 8 | use workspace::ModalView; |
| 9 | 9 | ||
| 10 | const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; | 10 | const 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) |
| @@ -36,7 +36,7 @@ use std::{ · 35 unmodified lines | |||
| 36 | }; | 36 | }; |
| 37 | use theme::ActiveTheme; | 37 | use theme::ActiveTheme; |
| 38 | pub use toolbar_controls::ToolbarControls; | 38 | pub use toolbar_controls::ToolbarControls; |
| 39 | use ui::{h_stack, prelude::*, Icon, IconElement, Label}; | 39 | use ui::{h_stack, prelude::*, Icon, IconName, Label}; |
| 40 | use util::TryFutureExt; | 40 | use util::TryFutureExt; |
| 41 | use workspace::{ | 41 | use 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::Default | 666 | 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::Default | 680 | 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 | }), |
| @@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines | |||
| 6 | }; | 6 | }; |
| 7 | use language::Diagnostic; | 7 | use language::Diagnostic; |
| 8 | use lsp::LanguageServerId; | 8 | use lsp::LanguageServerId; |
| 9 | use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip}; | 9 | use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; |
| 10 | use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; | 10 | use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; |
| 11 | 11 | ||
| 12 | use crate::{Deploy, ProjectDiagnosticsEditor}; | 12 | use 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) |
| @@ -1,7 +1,7 @@ | |||
| 1 | use crate::ProjectDiagnosticsEditor; | 1 | use crate::ProjectDiagnosticsEditor; |
| 2 | use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; | 2 | use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; |
| 3 | use ui::prelude::*; | 3 | use ui::prelude::*; |
| 4 | use ui::{Icon, IconButton, Tooltip}; | 4 | use ui::{IconButton, IconName, Tooltip}; |
| 5 | use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; | 5 | use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; |
| 6 | 6 | ||
| 7 | pub struct ToolbarControls { | 7 | pub 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()) { |
| @@ -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); |
| @@ -99,8 +99,8 @@ use sum_tree::TreeMap; · 98 unmodified lines | |||
| 99 | use text::{OffsetUtf16, Rope}; | 99 | use text::{OffsetUtf16, Rope}; |
| 100 | use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; | 100 | use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; |
| 101 | use ui::{ | 101 | use 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 | }; |
| 105 | use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; | 105 | use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; |
| 106 | use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; | 106 | use 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)] |
| 511 | pub struct EditorStyle { | 511 | pub 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 | ||
| 522 | impl Default for EditorStyle { | ||
| 523 | fn default() -> Self { | ||
| 524 | Self { | ||
| 525 | background: Hsla::default(), | ||
| 526 | local_player: PlayerColor::default(), | ||
| 527 | text: TextStyle::default(), | ||
| 528 | scrollbar_width: Pixels::default(), | ||
| 529 | syntax: Default::default(), | ||
| 530 | // HACK: Status colors don't have a real default. | ||
| 531 | // We should look into removing the status colors from the editor | ||
| 532 | // style and retrieve them directly from the theme. | ||
| 533 | status: StatusColors::dark(), | ||
| 534 | inlays_style: HighlightStyle::default(), | ||
| 535 | suggestions_style: HighlightStyle::default(), | ||
| 536 | } | ||
| 537 | } | ||
| 538 | } | ||
| 539 | |||
| 522 | type CompletionId = usize; | 540 | type 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_data | 4275 | 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) |
| @@ -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::Bubble | 2431 | 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(); |
| @@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; · 15 unmodified lines | |||
| 16 | use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; | 16 | use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; |
| 17 | use settings::Settings; | 17 | use settings::Settings; |
| 18 | use std::{ops::Range, sync::Arc, time::Duration}; | 18 | use std::{ops::Range, sync::Arc, time::Duration}; |
| 19 | use ui::{StyledExt, Tooltip}; | 19 | use ui::{prelude::*, Tooltip}; |
| 20 | use util::TryFutureExt; | 20 | use util::TryFutureExt; |
| 21 | use workspace::Workspace; | 21 | use 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 | ||
| @@ -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_anchor | 387 | let snapshot = &self.buffer().read(cx).snapshot(cx); |
| 388 | .anchor | 388 | 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_manager | 393 | 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 | } |
| @@ -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"))] |
| 62 | pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor { | 62 | pub(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 | ||
| 67 | pub(crate) fn build_editor_with_project( | 66 | pub(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 | } |
| @@ -1,5 +1,5 @@ | |||
| 1 | use gpui::{Render, ViewContext, WeakView}; | 1 | use gpui::{Render, ViewContext, WeakView}; |
| 2 | use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; | 2 | use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip}; |
| 3 | use workspace::{item::ItemHandle, StatusItemView, Workspace}; | 3 | use workspace::{item::ItemHandle, StatusItemView, Workspace}; |
| 4 | 4 | ||
| 5 | use crate::{feedback_modal::FeedbackModal, GiveFeedback}; | 5 | use 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) |
| @@ -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), |
| @@ -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 | ``` |
| @@ -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 = self | 139 | 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 str | 218 | fn debug_name() -> &'static str |
| 208 | where | 219 | where |
| 209 | Self: ::std::marker::Sized | 220 | Self: ::std::marker::Sized |
| @@ -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)] | ||
| 48 | pub struct AppCell { | 49 | pub struct AppCell { |
| 49 | app: RefCell<AppContext>, | 50 | app: RefCell<AppContext>, |
| 50 | } | 51 | } |
| 51 | 52 | ||
| 52 | impl AppCell { | 53 | impl 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)] |
| 73 | pub struct AppRef<'a>(Ref<'a, AppContext>); | 77 | pub 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)] |
| 85 | pub struct AppRefMut<'a>(RefMut<'a, AppContext>); | 90 | pub 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. | ||
| 96 | pub struct App(Rc<AppCell>); | 103 | pub struct App(Rc<AppCell>); |
| 97 | 104 | ||
| 98 | /// Represents an application before it is fully launched. Once your app is | 105 | /// Represents an application before it is fully launched. Once your app is |
| @@ -136,6 +143,8 @@ impl App { · 37 unmodified lines | |||
| 136 | self | 143 | 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) -> &Self | 148 | pub fn on_reopen<F>(&self, mut callback: F) -> &Self |
| 140 | where | 149 | where |
| 141 | F: 'static + FnMut(&mut AppContext), | 150 | F: 'static + FnMut(&mut AppContext), |
| @@ -149,18 +158,22 @@ impl App { · 7 unmodified lines | |||
| 149 | self | 158 | 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 | |||
| 174 | type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>; | 187 | type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>; |
| 175 | type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>; | 188 | type 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 | |||
| 183 | pub struct AppContext { | 190 | pub 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 | app | 299 | 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 | result | 349 | 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 | subscription | 390 | 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 | ) -> Subscription | 399 | ) -> Subscription |
| 388 | where | 400 | 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 | subscription | 438 | 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.windows | 445 | 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 effects | 583 | /// Called at the end of [`AppContext::update`] to complete any side effects |
| 569 | /// such as notifying observers, emitting events, etc. Effects can themselves | 584 | /// 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) { |
| @@ -82,6 +82,7 @@ impl Context for AsyncAppContext { · 81 unmodified lines | |||
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | impl AsyncAppContext { | 84 | impl 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 = self | 87 | let app = self |
| 87 | .app | 88 | .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_executor | 98 | &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_executor | 103 | &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 = self | 108 | let app = self |
| 105 | .app | 109 | .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 | where | 135 | where |
| 130 | Fut: Future<Output = R> + 'static, | 136 | Fut: Future<Output = R> + 'static, |
| @@ -19,7 +19,10 @@ use std::{ · 18 unmodified lines | |||
| 19 | #[cfg(any(test, feature = "test-support"))] | 19 | #[cfg(any(test, feature = "test-support"))] |
| 20 | use collections::HashMap; | 20 | use collections::HashMap; |
| 21 | 21 | ||
| 22 | slotmap::new_key_type! { pub struct EntityId; } | 22 | slotmap::new_key_type! { |
| 23 | /// A unique identifier for a model or view across the application. | ||
| 24 | pub struct EntityId; | ||
| 25 | } | ||
| 23 | 26 | ||
| 24 | impl EntityId { | 27 | impl EntityId { |
| 25 | pub fn as_u64(self) -> u64 { | 28 | pub fn as_u64(self) -> u64 { |
| @@ -1,3 +1,5 @@ | |||
| 1 | #![deny(missing_docs)] | ||
| 2 | |||
| 1 | use crate::{ | 3 | use 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 | |||
| 9 | use futures::{Stream, StreamExt}; | 11 | use futures::{Stream, StreamExt}; |
| 10 | use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; | 12 | use 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)] |
| 13 | pub struct TestAppContext { | 17 | pub 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 | ||
| 78 | impl TestAppContext { | 86 | impl 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_executor | 131 | &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 | where | 149 | 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_handle | 161 | .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 | where | 168 | 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_system | 183 | &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 | where | 227 | 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 | where | 288 | 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 keystrokes | 303 | 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.app | 339 | 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 | rx | 366 | 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 | rx | 385 | 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 | ||
| 368 | impl<T: Send> Model<T> { | 418 | impl<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) -> Evt | 420 | pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt |
| 370 | where | 421 | 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 | ||
| 397 | impl<V: 'static> View<V> { | 448 | impl<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 | ||
| 423 | impl<V> View<V> { | 475 | impl<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 | .await | 529 | .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 | .await | 534 | .await |
| @@ -488,6 +540,8 @@ impl<V> View<V> { · 5 unmodified lines | |||
| 488 | 540 | ||
| 489 | use derive_more::{Deref, DerefMut}; | 541 | use 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. | ||
| 491 | pub struct VisualTestContext { | 545 | pub 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 | ||
| 498 | impl<'a> VisualTestContext { | 552 | impl<'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 | where | 575 | 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 = self | 609 | let handler = self |
| @@ -672,6 +740,7 @@ impl VisualContext for VisualTestContext { · 130 unmodified lines | |||
| 672 | } | 740 | } |
| 673 | 741 | ||
| 674 | impl AnyWindowHandle { | 742 | impl 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. | ||
| 684 | pub struct EmptyView {} | 754 | pub struct EmptyView {} |
| 685 | 755 | ||
| 686 | impl Render for EmptyView { | 756 | impl Render for EmptyView { |
| @@ -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. |
| 120 | pub trait RenderOnce: 'static { | 120 | pub 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. |
| 228 | impl<E: Element> DrawableElement<E> { | 228 | impl<E: Element> DrawableElement<E> { |
| 229 | fn new(element: E) -> Self { | 229 | fn new(element: E) -> Self { |
| 230 | DrawableElement { | 230 | DrawableElement { |
| @@ -2,7 +2,7 @@ use std::sync::Arc; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::{ | 3 | use 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 | }; |
| 8 | use futures::FutureExt; | 8 | use futures::FutureExt; |
| @@ -12,13 +12,13 @@ use util::ResultExt; · 3 unmodified lines | |||
| 12 | #[derive(Clone, Debug)] | 12 | #[derive(Clone, Debug)] |
| 13 | pub enum ImageSource { | 13 | pub 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 | ||
| 20 | impl From<SharedString> for ImageSource { | 20 | impl 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 | } |
| @@ -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 | ||
| @@ -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)] |
| 37 | pub enum Task<T> { | 43 | pub enum Task<T> { |
| @@ -40,10 +46,12 @@ pub enum Task<T> { · 2 unmodified lines | |||
| 40 | } | 46 | } |
| 41 | 47 | ||
| 42 | impl<T> Task<T> { | 48 | impl<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 | ||
| 98 | type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>; | 108 | type 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. | ||
| 100 | impl BackgroundExecutor { | 114 | impl 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 | value | 167 | 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 | where | 263 | 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. | ||
| 324 | impl ForegroundExecutor { | 364 | impl 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 returns | 372 | /// 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 | where | 374 | 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`]. | ||
| 353 | pub struct Scope<'a> { | 393 | pub 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>>>, |
| @@ -18,6 +18,7 @@ mod platform; · 17 unmodified lines | |||
| 18 | pub mod prelude; | 18 | pub mod prelude; |
| 19 | mod scene; | 19 | mod scene; |
| 20 | mod shared_string; | 20 | mod shared_string; |
| 21 | mod shared_url; | ||
| 21 | mod style; | 22 | mod style; |
| 22 | mod styled; | 23 | mod styled; |
| 23 | mod subscription; | 24 | mod subscription; |
| @@ -67,6 +68,7 @@ pub use refineable::*; · 43 unmodified lines | |||
| 67 | pub use scene::*; | 68 | pub use scene::*; |
| 68 | use seal::Sealed; | 69 | use seal::Sealed; |
| 69 | pub use shared_string::*; | 70 | pub use shared_string::*; |
| 71 | pub use shared_url::*; | ||
| 70 | pub use smol::Timer; | 72 | pub use smol::Timer; |
| 71 | pub use style::*; | 73 | pub use style::*; |
| 72 | pub use styled::*; | 74 | pub use styled::*; |
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::{ImageData, ImageId, SharedString}; | 1 | use crate::{ImageData, ImageId, SharedUrl}; |
| 2 | use collections::HashMap; | 2 | use collections::HashMap; |
| 3 | use futures::{ | 3 | use futures::{ |
| 4 | future::{BoxFuture, Shared}, | 4 | future::{BoxFuture, Shared}, |
| @@ -44,7 +44,7 @@ impl From<ImageError> for Error { · 39 unmodified lines | |||
| 44 | 44 | ||
| 45 | pub struct ImageCache { | 45 | pub 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 | ||
| 50 | type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>; | 50 | type 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(); |
| @@ -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. |
| 39 | pub struct ElementInputHandler<V> { | 39 | pub struct ElementInputHandler<V> { |
| 40 | view: View<V>, | 40 | view: View<V>, |
| @@ -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)] |
| @@ -192,8 +192,8 @@ impl DispatchTree { · 191 unmodified lines | |||
| 192 | keymap | 192 | 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)] | ||
| 288 | mod tests { | ||
| 289 | use std::{rc::Rc, sync::Arc}; | ||
| 290 | |||
| 291 | use parking_lot::Mutex; | ||
| 292 | |||
| 293 | use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap}; | ||
| 294 | |||
| 295 | #[derive(PartialEq, Eq)] | ||
| 296 | struct TestAction; | ||
| 297 | |||
| 298 | impl Action for TestAction { | ||
| 299 | fn name(&self) -> &'static str { | ||
| 300 | "test::TestAction" | ||
| 301 | } | ||
| 302 | |||
| 303 | fn debug_name() -> &'static str | ||
| 304 | where | ||
| 305 | Self: ::std::marker::Sized, | ||
| 306 | { | ||
| 307 | "test::TestAction" | ||
| 308 | } | ||
| 309 | |||
| 310 | fn partial_eq(&self, action: &dyn Action) -> bool { | ||
| 311 | action | ||
| 312 | .as_any() | ||
| 313 | .downcast_ref::<Self>() | ||
| 314 | .map_or(false, |a| self == a) | ||
| 315 | } | ||
| 316 | |||
| 317 | fn boxed_clone(&self) -> std::boxed::Box<dyn Action> { | ||
| 318 | Box::new(TestAction) | ||
| 319 | } | ||
| 320 | |||
| 321 | fn as_any(&self) -> &dyn ::std::any::Any { | ||
| 322 | self | ||
| 323 | } | ||
| 324 | |||
| 325 | fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>> | ||
| 326 | where | ||
| 327 | Self: Sized, | ||
| 328 | { | ||
| 329 | Ok(Box::new(TestAction)) | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | #[test] | ||
| 334 | fn test_keybinding_for_action_bounds() { | ||
| 335 | let keymap = Keymap::new(vec![KeyBinding::new( | ||
| 336 | "cmd-n", | ||
| 337 | TestAction, | ||
| 338 | Some("ProjectPanel"), | ||
| 339 | )]); | ||
| 340 | |||
| 341 | let mut registry = ActionRegistry::default(); | ||
| 342 | |||
| 343 | registry.load_action::<TestAction>(); | ||
| 344 | |||
| 345 | let keymap = Arc::new(Mutex::new(keymap)); | ||
| 346 | |||
| 347 | let tree = DispatchTree::new(keymap, Rc::new(registry)); | ||
| 348 | |||
| 349 | let contexts = vec![ | ||
| 350 | KeyContext::parse("Workspace").unwrap(), | ||
| 351 | KeyContext::parse("ProjectPanel").unwrap(), | ||
| 352 | ]; | ||
| 353 | |||
| 354 | let keybinding = tree.bindings_for_action(&TestAction, &contexts); | ||
| 355 | |||
| 356 | assert!(keybinding[0].action.partial_eq(&TestAction)) | ||
| 357 | } | ||
| 358 | } | ||
| @@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); · 13 unmodified lines | |||
| 14 | unsafe impl Send for MacDisplay {} | 14 | unsafe impl Send for MacDisplay {} |
| 15 | 15 | ||
| 16 | impl MacDisplay { | 16 | impl 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 | } |
| @@ -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> { |
| @@ -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. | ||
| 18 | pub struct TestPlatform { | 19 | pub 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") | ||
| 107 | impl Platform for TestPlatform { | 107 | impl 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() |
| @@ -0,0 +1,25 @@ | |||
| 1 | use derive_more::{Deref, DerefMut}; | ||
| 2 | |||
| 3 | use crate::SharedString; | ||
| 4 | |||
| 5 | /// A [`SharedString`] containing a URL. | ||
| 6 | #[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)] | ||
| 7 | pub struct SharedUrl(SharedString); | ||
| 8 | |||
| 9 | impl std::fmt::Debug for SharedUrl { | ||
| 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 11 | self.0.fmt(f) | ||
| 12 | } | ||
| 13 | } | ||
| 14 | |||
| 15 | impl std::fmt::Display for SharedUrl { | ||
| 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 17 | write!(f, "{}", self.0.as_ref()) | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | impl<T: Into<SharedString>> From<T> for SharedUrl { | ||
| 22 | fn from(value: T) -> Self { | ||
| 23 | Self(value.into()) | ||
| 24 | } | ||
| 25 | } | ||
| @@ -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, subscriptions | 40 | /// 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 latter | 42 | /// 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, |
| @@ -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 | //! ``` | ||
| 1 | use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; | 28 | use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; |
| 2 | use futures::StreamExt as _; | 29 | use futures::StreamExt as _; |
| 3 | use rand::prelude::*; | 30 | use 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` | ||
| 71 | pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> { | 99 | pub 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| { |
| @@ -1,3 +1,5 @@ | |||
| 1 | #![deny(missing_docs)] | ||
| 2 | |||
| 1 | use crate::{ | 3 | use 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 | ||
| 87 | impl DispatchPhase { | 89 | impl 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::Bubble | 92 | 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::Capture | 97 | 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 | ||
| 106 | slotmap::new_key_type! { pub struct FocusId; } | 110 | slotmap::new_key_type! { |
| 111 | /// A globally unique identifier for a focusable element. | ||
| 112 | pub struct FocusId; | ||
| 113 | } | ||
| 107 | 114 | ||
| 108 | thread_local! { | 115 | thread_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 easily | 238 | /// FocusableView allows users of your view to easily |
| 232 | /// focus it (using cx.focus_view(view)) | 239 | /// focus it (using cx.focus_view(view)) |
| 233 | pub trait FocusableView: 'static + Render { | 240 | pub 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 | ||
| 241 | impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {} | 249 | impl<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. | ||
| 243 | pub struct DismissEvent; | 252 | pub struct DismissEvent; |
| 244 | 253 | ||
| 245 | // Holds the state for a specific window. | 254 | // Holds the state for a specific window. |
| 255 | #[doc(hidden)] | ||
| 246 | pub struct Window { | 256 | pub 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)] |
| 436 | pub struct ContentMask<P: Clone + Default + Debug> { | 446 | pub 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. Derefs | 466 | /// Provides access to application state in the context of a single window. Derefs |
| 456 | /// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes | 467 | /// 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. |
| 458 | pub struct WindowContext<'a> { | 469 | pub 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 focus | 496 | /// 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.window | 504 | self.window |
| 494 | .focus | 505 | .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.bounds | 812 | 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_size | 817 | 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.active | 822 | 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.platform | 842 | 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 = self | 968 | let target = self |
| 941 | .focused() | 969 | .focused() |
| @@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> { · 20 unmodified lines | |||
| 962 | self.window.modifiers | 990 | 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 | true | 1020 | 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.window | 1683 | self.window |
| 1654 | .rendered_frame | 1684 | .rendered_frame |
| @@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> { · 60 unmodified lines | |||
| 1715 | subscription | 1745 | 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 = self | 1777 | let node_id = self |
| 1741 | .window | 1778 | .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.window | 1796 | self.window |
| 1759 | .rendered_frame | 1797 | .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.window | 1892 | 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`] | ||
| 2020 | pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { | 2065 | pub 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 | ||
| 2280 | impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {} | 2329 | impl<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>`. | ||
| 2282 | pub struct ViewContext<'a, V> { | 2335 | pub 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.view | 2379 | 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.model | 2384 | &self.view.model |
| 2329 | } | 2385 | } |
| @@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines | |||
| 2333 | &mut self.window_cx | 2389 | &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 | where | 2394 | 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 | subscription | 2441 | 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 | subscription | 2501 | 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 | subscription | 2528 | 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 | subscription | 2553 | 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 | subscription | 2686 | 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) -> R | 2706 | pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R |
| 2636 | where | 2707 | where |
| 2637 | G: 'static, | 2708 | G: 'static, |
| @@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 4 unmodified lines | |||
| 2642 | result | 2713 | 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 | subscription | 2732 | 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 | where | 2780 | 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 | where | 2794 | 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)] |
| 2830 | slotmap::new_key_type! { pub struct WindowId; } | 2914 | slotmap::new_key_type! { |
| 2915 | /// A unique identifier for a window. | ||
| 2916 | pub struct WindowId; | ||
| 2917 | } | ||
| 2831 | 2918 | ||
| 2832 | impl WindowId { | 2919 | impl 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)] |
| 2839 | pub struct WindowHandle<V> { | 2929 | pub struct WindowHandle<V> { |
| 2840 | #[deref] | 2930 | #[deref] |
| @@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> { · 3 unmodified lines | |||
| 2844 | } | 2934 | } |
| 2845 | 2935 | ||
| 2846 | impl<V: 'static + Render> WindowHandle<V> { | 2936 | impl<V: 'static + Render> WindowHandle<V> { |
| 2937 | /// Create a new handle from a window ID. | ||
| 2938 | /// This does not check if the root type of the window is `V`. | ||
| 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 | where | 2953 | 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 = cx | 2986 | let x = cx |
| 2886 | .windows | 2987 | .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 | where | 3005 | 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 | where | 3015 | 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.windows | 3025 | 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)] |
| 2950 | pub struct AnyWindowHandle { | 3061 | pub 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 | ||
| 2955 | impl AnyWindowHandle { | 3066 | impl AnyWindowHandle { |
| 3067 | /// Get the ID of this window. | ||
| 2956 | pub fn window_id(&self) -> WindowId { | 3068 | pub fn window_id(&self) -> WindowId { |
| 2957 | self.id | 3069 | 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 | where | 3103 | 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)] |
| 3003 | pub enum ElementId { | 3127 | pub 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)] |
| 3079 | pub struct PaintQuad { | 3209 | pub struct PaintQuad { |
| 3080 | bounds: Bounds<Pixels>, | 3210 | bounds: Bounds<Pixels>, |
| @@ -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 str | 36 | fn debug_name() -> &'static str |
| 37 | where | 37 | 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 | where | 44 | where |
| 45 | Self: Sized, | 45 | Self: Sized, |
| 46 | { | 46 | { |
| 47 | todo!() | 47 | unimplemented!() |
| 48 | } | 48 | } |
| 49 | } | 49 | } |
| 50 | } | 50 | } |
| @@ -7,26 +7,56 @@ mod test; · 6 unmodified lines | |||
| 7 | use proc_macro::TokenStream; | 7 | use 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. | ||
| 10 | pub fn register_action(ident: TokenStream) -> TokenStream { | 13 | pub 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. | ||
| 15 | pub fn derive_into_element(input: TokenStream) -> TokenStream { | 20 | pub 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)] | ||
| 20 | pub fn derive_render(input: TokenStream) -> TokenStream { | 26 | pub 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)] | ||
| 25 | pub fn style_helpers(input: TokenStream) -> TokenStream { | 33 | pub 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. | ||
| 30 | pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { | 60 | pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { |
| 31 | test::test(args, function) | 61 | test::test(args, function) |
| 32 | } | 62 | } |
| @@ -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 | ||
| @@ -30,7 +30,7 @@ use std::{ · 29 unmodified lines | |||
| 30 | sync::Arc, | 30 | sync::Arc, |
| 31 | }; | 31 | }; |
| 32 | use theme::ThemeSettings; | 32 | use theme::ThemeSettings; |
| 33 | use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem}; | 33 | use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; |
| 34 | use unicase::UniCase; | 34 | use unicase::UniCase; |
| 35 | use util::{maybe, ResultExt, TryFutureExt}; | 35 | use util::{maybe, ResultExt, TryFutureExt}; |
| 36 | use workspace::{ | 36 | use 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> { |
| @@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines | |||
| 6 | Subscription, View, ViewContext, WeakView, | 6 | Subscription, View, ViewContext, WeakView, |
| 7 | }; | 7 | }; |
| 8 | use search::{buffer_search, BufferSearchBar}; | 8 | use search::{buffer_search, BufferSearchBar}; |
| 9 | use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip}; | 9 | use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip}; |
| 10 | use workspace::{ | 10 | use 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)] |
| 108 | struct QuickActionBarButton { | 108 | struct 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 | |||
| 117 | impl QuickActionBarButton { | 117 | impl 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>, |
| @@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; · 24 unmodified lines | |||
| 25 | #[cfg(not(test))] | 25 | #[cfg(not(test))] |
| 26 | const CHUNK_BASE: usize = 16; | 26 | const CHUNK_BASE: usize = 16; |
| 27 | 27 | ||
| 28 | /// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances | 28 | /// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances |
| 29 | /// containing the same text will produce the same fingerprint. This hash function is special in that | 29 | /// containing the same text will produce the same fingerprint. This hash function is special in that |
| 30 | /// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting | 30 | /// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting |
| 31 | /// hash being equivalent to hashing all the text contained in the [Rope] at once. | 31 | /// hash being equivalent to hashing all the text contained in the [`Rope`] at once. |
| 32 | pub type RopeFingerprint = HashMatrix; | 32 | pub type RopeFingerprint = HashMatrix; |
| 33 | 33 | ||
| 34 | #[derive(Clone, Default)] | 34 | #[derive(Clone, Default)] |
| @@ -76,30 +76,35 @@ impl Notification { · 75 unmodified lines | |||
| 76 | } | 76 | } |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | #[test] | 79 | #[cfg(test)] |
| 80 | fn test_notification() { | 80 | mod 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 are | 105 | // 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 | } |
| @@ -21,7 +21,7 @@ use settings::Settings; · 20 unmodified lines | |||
| 21 | use std::{any::Any, sync::Arc}; | 21 | use std::{any::Any, sync::Arc}; |
| 22 | use theme::ThemeSettings; | 22 | use theme::ThemeSettings; |
| 23 | 23 | ||
| 24 | use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip}; | 24 | use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; |
| 25 | use util::ResultExt; | 25 | use util::ResultExt; |
| 26 | use workspace::{ | 26 | use workspace::{ |
| 27 | item::ItemHandle, | 27 | item::ItemHandle, |
| @@ -43,7 +43,7 @@ pub enum Event { · 15 unmodified lines | |||
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | pub fn init(cx: &mut AppContext) { | 45 | pub fn init(cx: &mut AppContext) { |
| 46 | cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor)) | 46 | cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) |
| 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); |
| @@ -38,7 +38,7 @@ use std::{ · 37 unmodified lines | |||
| 38 | use theme::ThemeSettings; | 38 | use theme::ThemeSettings; |
| 39 | 39 | ||
| 40 | use ui::{ | 40 | use ui::{ |
| 41 | h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize, | 41 | h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, |
| 42 | Selectable, ToggleButton, Tooltip, | 42 | Selectable, ToggleButton, Tooltip, |
| 43 | }; | 43 | }; |
| 44 | use util::{paths::PathMatcher, ResultExt as _}; | 44 | use 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_query | 431 | 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::Default | 437 | Color::Default |
| 437 | } else { | 438 | } else { |
| 438 | Color::Muted | 439 | 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 | this | 1797 | 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() { |
| @@ -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 | } |
| @@ -3,7 +3,7 @@ use ui::IconButton; · 2 unmodified lines | |||
| 3 | use ui::{prelude::*, Tooltip}; | 3 | use ui::{prelude::*, Tooltip}; |
| 4 | 4 | ||
| 5 | pub(super) fn render_nav_button( | 5 | pub(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, |
| @@ -14,6 +14,7 @@ anyhow.workspace = true · 13 unmodified lines | |||
| 14 | backtrace-on-stack-overflow = "0.3.0" | 14 | backtrace-on-stack-overflow = "0.3.0" |
| 15 | chrono = "0.4" | 15 | chrono = "0.4" |
| 16 | clap = { version = "4.4", features = ["derive", "string"] } | 16 | clap = { version = "4.4", features = ["derive", "string"] } |
| 17 | collab_ui = { path = "../collab_ui", features = ["stories"] } | ||
| 17 | strum = { version = "0.25.0", features = ["derive"] } | 18 | strum = { version = "0.25.0", features = ["derive"] } |
| 18 | dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } | 19 | dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } |
| 19 | editor = { path = "../editor" } | 20 | editor = { path = "../editor" } |
| @@ -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(), |
| @@ -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() |
| @@ -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_panel | 77 | 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> { |
| @@ -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 | }; |
| 22 | use terminal_element::TerminalElement; | 22 | use terminal_element::TerminalElement; |
| 23 | use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; | 23 | use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; |
| 24 | use util::{paths::PathLikeWithPosition, ResultExt}; | 24 | use util::{paths::PathLikeWithPosition, ResultExt}; |
| 25 | use workspace::{ | 25 | use 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::Default | 695 | Color::Default |
| 696 | } else { | 696 | } else { |
| @@ -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 | true | 96 | true |
| @@ -2,7 +2,9 @@ use gpui::{AppContext, Hsla, SharedString}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::{ActiveTheme, Appearance}; | 3 | use 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)] |
| 7 | pub struct ColorScaleStep(usize); | 9 | pub 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. | ||
| 40 | pub struct ColorScale(Vec<Hsla>); | 46 | pub struct ColorScale(Vec<Hsla>); |
| 41 | 47 | ||
| 42 | impl FromIterator<Hsla> for ColorScale { | 48 | impl 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. | ||
| 232 | pub struct ColorScaleSet { | 239 | pub struct ColorScaleSet { |
| 233 | name: SharedString, | 240 | name: SharedString, |
| 234 | light: ColorScale, | 241 | light: ColorScale, |
| @@ -27,7 +27,7 @@ pub struct ThemeSettings { · 26 unmodified lines | |||
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | #[derive(Default)] | 29 | #[derive(Default)] |
| 30 | pub struct AdjustedBufferFontSize(Pixels); | 30 | pub(crate) struct AdjustedBufferFontSize(Pixels); |
| 31 | 31 | ||
| 32 | #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] | 32 | #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] |
| 33 | pub struct ThemeSettingsContent { | 33 | pub struct ThemeSettingsContent { |
| @@ -2,11 +2,12 @@ use gpui::Hsla; · 1 unmodified line | |||
| 2 | use refineable::Refineable; | 2 | use refineable::Refineable; |
| 3 | use std::sync::Arc; | 3 | use std::sync::Arc; |
| 4 | 4 | ||
| 5 | use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors}; | 5 | use 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)] |
| 9 | pub struct ThemeColors { | 9 | pub 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 sections | 12 | /// 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>, |
| @@ -78,15 +78,6 @@ pub struct StatusColors { · 77 unmodified lines | |||
| 78 | pub warning_border: Hsla, | 78 | pub warning_border: Hsla, |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | impl Default for StatusColors { | ||
| 82 | /// Don't use this! | ||
| 83 | /// We have to have a default to be `[refineable::Refinable]`. | ||
| 84 | /// todo!("Find a way to not need this for Refinable") | ||
| 85 | fn default() -> Self { | ||
| 86 | Self::dark() | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | pub struct DiagnosticColors { | 81 | pub struct DiagnosticColors { |
| 91 | pub error: Hsla, | 82 | pub error: Hsla, |
| 92 | pub warning: Hsla, | 83 | pub warning: Hsla, |
| @@ -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 | |||
| 1 | mod default_colors; | 9 | mod default_colors; |
| 2 | mod default_theme; | 10 | mod default_theme; |
| 3 | mod one_themes; | 11 | mod one_themes; |
| @@ -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. | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | use gpui::{ClickEvent, WindowContext}; | 1 | use 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. |
| 4 | pub trait Clickable { | 4 | pub 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; |
| @@ -1,13 +1,26 @@ | |||
| 1 | use crate::prelude::*; | 1 | use crate::prelude::*; |
| 2 | use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; | 2 | use 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)] |
| 5 | pub enum Shape { | 6 | pub 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)] |
| 12 | pub struct Avatar { | 25 | pub struct Avatar { |
| 13 | image: Img, | 26 | image: Img, |
| @@ -18,7 +31,7 @@ pub struct Avatar { · 4 unmodified lines | |||
| 18 | impl RenderOnce for Avatar { | 31 | impl 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 | self | 97 | 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 | self | 109 | self |
| @@ -2,11 +2,69 @@ use gpui::{AnyView, DefiniteLength}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::{prelude::*, IconPosition, KeyBinding}; | 3 | use crate::{prelude::*, IconPosition, KeyBinding}; |
| 4 | use crate::{ | 4 | use crate::{ |
| 5 | ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, | 5 | ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, |
| 6 | }; | 6 | }; |
| 7 | 7 | ||
| 8 | use super::button_icon::ButtonIcon; | 8 | use 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)] |
| 11 | pub struct Button { | 69 | pub 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 | ||
| 25 | impl Button { | 83 | impl Button { |
| 84 | /// Creates a new [`Button`] with a specified identifier and label. | ||
| 85 | /// | ||
| 86 | /// This is the primary constructor for a [`Button`] component. It initializes | ||
| 87 | /// the button with the provided identifier and label text, setting all other | ||
| 88 | /// properties to their default values, which can be customized using the | ||
| 89 | /// builder pattern methods provided by this struct. | ||
| 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 | self | 109 | 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 | self | 115 | 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 | self | 121 | 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 | self | 127 | 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 | self | 133 | 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 | self | 139 | 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 | self | 145 | 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 | self | 151 | 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 | self | 157 | self |
| @@ -86,6 +159,22 @@ impl Button { · 1 unmodified line | |||
| 86 | } | 159 | } |
| 87 | 160 | ||
| 88 | impl Selectable for Button { | 161 | impl Selectable for Button { |
| 162 | /// Sets the selected state of the button. | ||
| 163 | /// | ||
| 164 | /// This method allows the selection state of the button to be specified. | ||
| 165 | /// It modifies the button's appearance to reflect its selected state. | ||
| 166 | /// | ||
| 167 | /// # Examples | ||
| 168 | /// | ||
| 169 | /// ``` | ||
| 170 | /// Button::new("button_id", "Click me!") | ||
| 171 | /// .selected(true) | ||
| 172 | /// .on_click(|event, cx| { | ||
| 173 | /// // Handle click event | ||
| 174 | /// }); | ||
| 175 | /// ``` | ||
| 176 | /// | ||
| 177 | /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. | ||
| 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 | self | 180 | self |
| @@ -93,6 +182,19 @@ impl Selectable for Button { · 1 unmodified line | |||
| 93 | } | 182 | } |
| 94 | 183 | ||
| 95 | impl SelectableButton for Button { | 184 | impl SelectableButton for Button { |
| 185 | /// Sets the style for the button when selected. | ||
| 186 | /// | ||
| 187 | /// # Examples | ||
| 188 | /// | ||
| 189 | /// ``` | ||
| 190 | /// Button::new("button_id", "Click me!") | ||
| 191 | /// .selected(true) | ||
| 192 | /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) | ||
| 193 | /// .on_click(|event, cx| { | ||
| 194 | /// // Handle click event | ||
| 195 | /// }); | ||
| 196 | /// ``` | ||
| 197 | /// This results in a button with a blue tinted background when selected. | ||
| 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 | self | 200 | self |
| @@ -100,6 +202,22 @@ impl SelectableButton for Button { · 1 unmodified line | |||
| 100 | } | 202 | } |
| 101 | 203 | ||
| 102 | impl Disableable for Button { | 204 | impl Disableable for Button { |
| 205 | /// Disables the button. | ||
| 206 | /// | ||
| 207 | /// This method allows the button to be disabled. When a button is disabled, | ||
| 208 | /// it doesn't react to user interactions and its appearance is updated to reflect this. | ||
| 209 | /// | ||
| 210 | /// # Examples | ||
| 211 | /// | ||
| 212 | /// ``` | ||
| 213 | /// Button::new("button_id", "Click me!") | ||
| 214 | /// .disabled(true) | ||
| 215 | /// .on_click(|event, cx| { | ||
| 216 | /// // Handle click event | ||
| 217 | /// }); | ||
| 218 | /// ``` | ||
| 219 | /// | ||
| 220 | /// This results in a button that is disabled and does not respond to click events. | ||
| 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 | self | 223 | self |
| @@ -107,6 +225,7 @@ impl Disableable for Button { · 1 unmodified line | |||
| 107 | } | 225 | } |
| 108 | 226 | ||
| 109 | impl Clickable for Button { | 227 | impl 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 | ||
| 119 | impl FixedWidth for Button { | 238 | impl FixedWidth for Button { |
| 239 | /// Sets a fixed width for the button. | ||
| 240 | /// | ||
| 241 | /// This function allows a button to have a fixed width instead of automatically growing or shrinking. | ||
| 242 | /// Sets a fixed width for the button. | ||
| 243 | /// | ||
| 244 | /// # Examples | ||
| 245 | /// | ||
| 246 | /// ``` | ||
| 247 | /// Button::new("button_id", "Click me!") | ||
| 248 | /// .width(DefiniteLength::Pixels(100)) | ||
| 249 | /// .on_click(|event, cx| { | ||
| 250 | /// // Handle click event | ||
| 251 | /// }); | ||
| 252 | /// ``` | ||
| 253 | /// | ||
| 254 | /// This sets the button's width to be exactly 100 pixels. | ||
| 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 | self | 257 | 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 | self | 275 | self |
| @@ -129,20 +277,42 @@ impl FixedWidth for Button { · 1 unmodified line | |||
| 129 | } | 277 | } |
| 130 | 278 | ||
| 131 | impl ButtonCommon for Button { | 279 | impl 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 | self | 288 | 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 | self | 294 | 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 | self | 318 | self |
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::{prelude::*, Icon, IconElement, IconSize}; | 1 | use 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)] |
| 8 | pub(super) struct ButtonIcon { | 8 | pub(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 | ||
| 18 | impl ButtonIcon { | 18 | impl 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 | self | 44 | 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 | self | 49 | self |
| 50 | } | 50 | } |
| @@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { · 37 unmodified lines | |||
| 88 | self.color | 88 | 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 | } |
| @@ -4,10 +4,12 @@ use smallvec::SmallVec; · 3 unmodified lines | |||
| 4 | 4 | ||
| 5 | use crate::prelude::*; | 5 | use crate::prelude::*; |
| 6 | 6 | ||
| 7 | /// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected. | ||
| 7 | pub trait SelectableButton: Selectable { | 8 | pub 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. | ||
| 11 | pub trait ButtonCommon: Clickable + Disableable { | 13 | pub 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)] |
| 97 | pub enum ButtonStyle { | 100 | pub enum ButtonStyle { |
| 98 | /// A filled button with a solid background color. Provides emphasis versus | 101 | /// 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 elements | 266 | /// 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)] |
| 266 | pub enum ButtonSize { | 270 | pub enum ButtonSize { |
| 267 | Large, | 271 | Large, |
| @@ -1,21 +1,21 @@ | |||
| 1 | use gpui::{AnyView, DefiniteLength}; | 1 | use gpui::{AnyView, DefiniteLength}; |
| 2 | 2 | ||
| 3 | use crate::{prelude::*, SelectableButton}; | 3 | use crate::{prelude::*, SelectableButton}; |
| 4 | use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; | 4 | use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize}; |
| 5 | 5 | ||
| 6 | use super::button_icon::ButtonIcon; | 6 | use super::button_icon::ButtonIcon; |
| 7 | 7 | ||
| 8 | #[derive(IntoElement)] | 8 | #[derive(IntoElement)] |
| 9 | pub struct IconButton { | 9 | pub 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 | ||
| 17 | impl IconButton { | 17 | impl 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 | self | 35 | 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 | self | 40 | self |
| 41 | } | 41 | } |
| @@ -1,7 +1,7 @@ | |||
| 1 | use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; | 1 | use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; |
| 2 | 2 | ||
| 3 | use crate::prelude::*; | 3 | use crate::prelude::*; |
| 4 | use crate::{Color, Icon, IconElement, Selection}; | 4 | use crate::{Color, Icon, IconName, Selection}; |
| 5 | 5 | ||
| 6 | pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>; | 6 | pub 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::Disabled | 52 | 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::Disabled | 61 | Color::Disabled |
| @@ -1,6 +1,6 @@ | |||
| 1 | use crate::{ | 1 | use 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 | }; |
| 5 | use gpui::{ | 5 | use 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 | self | 122 | 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() |
| @@ -1,6 +1,6 @@ | |||
| 1 | use gpui::ClickEvent; | 1 | use gpui::ClickEvent; |
| 2 | 2 | ||
| 3 | use crate::{prelude::*, Color, Icon, IconButton, IconSize}; | 3 | use crate::{prelude::*, Color, IconButton, IconName, IconSize}; |
| 4 | 4 | ||
| 5 | #[derive(IntoElement)] | 5 | #[derive(IntoElement)] |
| 6 | pub struct Disclosure { | 6 | pub 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) |
| @@ -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)] |
| 11 | pub enum DividerColor { | 12 | pub enum DividerColor { |
| 12 | Border, | 13 | Border, |
| @@ -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)] |
| 25 | pub enum Icon { | 25 | pub 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 | ||
| 114 | impl Icon { | 114 | impl 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)] |
| 208 | pub struct IconElement { | 208 | pub struct Icon { |
| 209 | path: SharedString, | 209 | path: SharedString, |
| 210 | color: Color, | 210 | color: Color, |
| 211 | size: IconSize, | 211 | size: IconSize, |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | impl RenderOnce for IconElement { | 214 | impl 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 | |||
| 224 | impl 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 | self | 238 | self |
| 249 | } | 239 | } |
| 250 | } | 240 | } |
| 241 | |||
| 242 | impl RenderOnce for Icon { | ||
| 243 | fn render(self, cx: &mut WindowContext) -> impl IntoElement { | ||
| 244 | svg() | ||
| 245 | .size(self.size.rems()) | ||
| 246 | .flex_none() | ||
| 247 | .path(self.path) | ||
| 248 | .text_color(self.color.color(cx)) | ||
| 249 | } | ||
| 250 | } | ||
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; | 1 | use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; |
| 2 | use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; | 2 | use 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)] |
| 122 | pub struct KeyIcon { | 122 | pub struct KeyIcon { |
| 123 | icon: Icon, | 123 | icon: IconName, |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | impl RenderOnce for KeyIcon { | 126 | impl 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 | ||
| 136 | impl KeyIcon { | 136 | impl 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 | } |
| @@ -2,6 +2,28 @@ use gpui::WindowContext; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; | 3 | use 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)] |
| 6 | pub struct Label { | 28 | pub struct Label { |
| 7 | base: LabelLike, | 29 | base: LabelLike, |
| @@ -9,6 +31,13 @@ pub struct Label { · 1 unmodified line | |||
| 9 | } | 31 | } |
| 10 | 32 | ||
| 11 | impl Label { | 33 | impl Label { |
| 34 | /// Create a new `Label` with the given text. | ||
| 35 | /// | ||
| 36 | /// # Examples | ||
| 37 | /// | ||
| 38 | /// ``` | ||
| 39 | /// let my_label = Label::new("Hello, World!"); | ||
| 40 | /// ``` | ||
| 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 | ||
| 20 | impl LabelCommon for Label { | 49 | impl LabelCommon for Label { |
| 50 | /// Sets the size of the label using a [`LabelSize`]. | ||
| 51 | /// | ||
| 52 | /// # Examples | ||
| 53 | /// | ||
| 54 | /// ``` | ||
| 55 | /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); | ||
| 56 | /// ``` | ||
| 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 | self | 59 | 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 | self | 71 | 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 | self | 83 | 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 | self | 95 | self |
| @@ -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. | ||
| 22 | pub trait LabelCommon { | 23 | pub 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; |
| @@ -1,10 +1,10 @@ | |||
| 1 | use crate::prelude::*; | 1 | use crate::prelude::*; |
| 2 | use crate::{h_stack, Icon, IconElement, IconSize, Label}; | 2 | use crate::{h_stack, Icon, IconName, IconSize, Label}; |
| 3 | 3 | ||
| 4 | #[derive(IntoElement)] | 4 | #[derive(IntoElement)] |
| 5 | pub struct ListSubHeader { | 5 | pub 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 | self | 22 | 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 | ) |
| @@ -108,6 +108,7 @@ impl<M: ManagedView> PopoverMenu<M> { · 107 unmodified lines | |||
| 108 | } | 108 | } |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | /// Creates a [`PopoverMenu`] | ||
| 111 | pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> { | 112 | pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> { |
| 112 | PopoverMenu { | 113 | PopoverMenu { |
| 113 | id: id.into(), | 114 | id: id.into(), |
| @@ -39,6 +39,7 @@ impl<M: ManagedView> RightClickMenu<M> { · 38 unmodified lines | |||
| 39 | } | 39 | } |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | /// Creates a [`RightClickMenu`] | ||
| 42 | pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> { | 43 | pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> { |
| 43 | RightClickMenu { | 44 | RightClickMenu { |
| 44 | id: id.into(), | 45 | id: id.into(), |
| @@ -2,17 +2,13 @@ use gpui::{div, Div}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::StyledExt; | 3 | use 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] |
| 9 | pub fn h_stack() -> Div { | 7 | pub 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] |
| 17 | pub fn v_stack() -> Div { | 13 | pub fn v_stack() -> Div { |
| 18 | div().v_flex() | 14 | div().v_flex() |
| @@ -1,7 +1,7 @@ | |||
| 1 | use gpui::Render; | 1 | use gpui::Render; |
| 2 | use story::Story; | 2 | use story::Story; |
| 3 | 3 | ||
| 4 | use crate::{prelude::*, Icon}; | 4 | use crate::{prelude::*, IconName}; |
| 5 | use crate::{Button, ButtonStyle}; | 5 | use crate::{Button, ButtonStyle}; |
| 6 | 6 | ||
| 7 | pub struct ButtonStory; | 7 | pub 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)) |
| @@ -3,17 +3,17 @@ use story::Story; · 2 unmodified lines | |||
| 3 | use strum::IntoEnumIterator; | 3 | use strum::IntoEnumIterator; |
| 4 | 4 | ||
| 5 | use crate::prelude::*; | 5 | use crate::prelude::*; |
| 6 | use crate::{Icon, IconElement}; | 6 | use crate::{Icon, IconName}; |
| 7 | 7 | ||
| 8 | pub struct IconStory; | 8 | pub struct IconStory; |
| 9 | 9 | ||
| 10 | impl Render for IconStory { | 10 | impl 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 | } |
| @@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line | |||
| 2 | use story::{StoryContainer, StoryItem, StorySection}; | 2 | use story::{StoryContainer, StoryItem, StorySection}; |
| 3 | 3 | ||
| 4 | use crate::{prelude::*, Tooltip}; | 4 | use crate::{prelude::*, Tooltip}; |
| 5 | use crate::{Icon, IconButton}; | 5 | use crate::{IconButton, IconName}; |
| 6 | 6 | ||
| 7 | pub struct IconButtonStory; | 7 | pub 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 | ) |
| @@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line | |||
| 2 | use story::Story; | 2 | use story::Story; |
| 3 | 3 | ||
| 4 | use crate::{prelude::*, IconButton}; | 4 | use crate::{prelude::*, IconButton}; |
| 5 | use crate::{Icon, ListHeader}; | 5 | use crate::{IconName, ListHeader}; |
| 6 | 6 | ||
| 7 | pub struct ListHeaderStory; | 7 | pub 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 | } |
| @@ -1,8 +1,8 @@ | |||
| 1 | use gpui::Render; | 1 | use gpui::{Render, SharedUrl}; |
| 2 | use story::Story; | 2 | use story::Story; |
| 3 | 3 | ||
| 4 | use crate::{prelude::*, Avatar}; | 4 | use crate::{prelude::*, Avatar}; |
| 5 | use crate::{Icon, ListItem}; | 5 | use crate::{IconName, ListItem}; |
| 6 | 6 | ||
| 7 | pub struct ListItemStory; | 7 | pub 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 | ) |
| @@ -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), |
| @@ -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 | ), |
| @@ -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. |
| 2 | pub trait Disableable { | 2 | pub 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; |
| @@ -1,6 +1,6 @@ | |||
| 1 | use gpui::DefiniteLength; | 1 | use 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. |
| 4 | pub trait FixedWidth { | 4 | pub 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; |
| @@ -1,3 +1,5 @@ | |||
| 1 | //! The prelude of this crate. When building UI in Zed you almost always want to import this. | ||
| 2 | |||
| 1 | pub use gpui::prelude::*; | 3 | pub use gpui::prelude::*; |
| 2 | pub use gpui::{ | 4 | pub 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 | |||
| 15 | pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; | 17 | pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; |
| 16 | pub use crate::{ButtonCommon, Color, StyledExt}; | 18 | pub use crate::{ButtonCommon, Color, StyledExt}; |
| 17 | pub use crate::{Headline, HeadlineSize}; | 19 | pub use crate::{Headline, HeadlineSize}; |
| 18 | pub use crate::{Icon, IconElement, IconPosition, IconSize}; | 20 | pub use crate::{Icon, IconName, IconPosition, IconSize}; |
| 19 | pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; | 21 | pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; |
| 20 | pub use theme::ActiveTheme; | 22 | pub use theme::ActiveTheme; |
| @@ -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. | ||
| 2 | pub trait Selectable { | 4 | pub 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)] |
| 8 | pub enum Selection { | 11 | pub 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 | ||
| 15 | impl Selection { | 21 | impl 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, |
| @@ -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. |
| 18 | pub trait StyledExt: Styled + Sized { | 18 | pub 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 elements | 82 | /// 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 | } |
| @@ -1,6 +1,7 @@ | |||
| 1 | use gpui::{Hsla, WindowContext}; | 1 | use gpui::{Hsla, WindowContext}; |
| 2 | use theme::ActiveTheme; | 2 | use 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)] |
| 5 | pub enum Color { | 6 | pub enum Color { |
| 6 | #[default] | 7 | #[default] |
| @@ -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)] |
| 89 | pub enum ElementIndex { | 90 | pub enum ElementIndex { |
| 90 | Effect, | 91 | Effect, |
| @@ -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)] |
| 42 | pub enum HeadlineSize { | 43 | pub enum HeadlineSize { |
| 43 | XSmall, | 44 | XSmall, |
| @@ -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 | |||
| 8 | mod clickable; | 6 | mod clickable; |
| 9 | mod components; | 7 | mod components; |
| 10 | mod disableable; | 8 | mod disableable; |
| @@ -1,3 +1,5 @@ | |||
| 1 | //! UI-related utilities (e.g. converting dates to a human-readable form). | ||
| 2 | |||
| 1 | mod format_distance; | 3 | mod format_distance; |
| 2 | 4 | ||
| 3 | pub use format_distance::*; | 5 | pub use format_distance::*; |
| @@ -7,10 +7,10 @@ pub enum DateTimeType { · 6 unmodified lines | |||
| 7 | } | 7 | } |
| 8 | 8 | ||
| 9 | impl DateTimeType { | 9 | impl 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 | /// ## Arguments | 74 | /// ## Arguments |
| 75 | /// | 75 | /// |
| 76 | /// * `date` - A NaiveDateTime object representing the date of interest | 76 | /// * `date` - A [NaiveDateTime`] object representing the date of interest |
| 77 | /// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made | 77 | /// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made |
| 78 | fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { | 78 | fn 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 | /// # Arguments | 238 | /// # 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 detailed | 242 | /// * `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 future | 243 | /// * `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 | /// # Arguments | 275 | /// # 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 detailed | 278 | /// * `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 future | 279 | /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future |
| 280 | /// | 280 | /// |
| @@ -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) = this | 399 | if let Some(ix) = this |
| 401 | .panel_entries | 400 | .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 | None | 778 | None |
| 780 | } | 779 | } |
| 781 | 780 | ||
| @@ -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 | ||
| 106 | impl Render for ModalLayer { | 110 | impl Render for ModalLayer { |
| @@ -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 | ||
| 351 | pub trait NotifyResultExt { | 252 | pub trait NotifyResultExt { |
| @@ -31,8 +31,8 @@ use std::{ · 30 unmodified lines | |||
| 31 | use theme::ThemeSettings; | 31 | use theme::ThemeSettings; |
| 32 | 32 | ||
| 33 | use ui::{ | 33 | use 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 | }; |
| 37 | use ui::{v_stack, ContextMenu}; | 37 | use ui::{v_stack, ContextMenu}; |
| 38 | use util::{maybe, truncate_and_remove_front, ResultExt}; | 38 | use 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(); |
| @@ -12,7 +12,7 @@ use serde::Deserialize; · 11 unmodified lines | |||
| 12 | use std::sync::Arc; | 12 | use std::sync::Arc; |
| 13 | use ui::{prelude::*, Button}; | 13 | use ui::{prelude::*, Button}; |
| 14 | 14 | ||
| 15 | const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) | 15 | const HANDLE_HITBOX_SIZE: f32 = 4.0; |
| 16 | const HORIZONTAL_MIN_SIZE: f32 = 80.; | 16 | const HORIZONTAL_MIN_SIZE: f32 = 80.; |
| 17 | const VERTICAL_MIN_SIZE: f32 = 100.; | 17 | const 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 | workspace | 710 | 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 using | 860 | .map(|magnification| { |
| 846 | // 1 for all non-active panes, and then the magnification for the | 861 | 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 = bounds | 869 | let child_size = bounds |
| 849 | .size | 870 | .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 | } |
| @@ -12,7 +12,7 @@ use gpui::{ · 11 unmodified lines | |||
| 12 | WindowContext, | 12 | WindowContext, |
| 13 | }; | 13 | }; |
| 14 | use std::sync::{Arc, Weak}; | 14 | use std::sync::{Arc, Weak}; |
| 15 | use ui::{h_stack, prelude::*, Icon, IconElement, Label}; | 15 | use ui::{h_stack, prelude::*, Icon, IconName, Label}; |
| 16 | 16 | ||
| 17 | pub enum Event { | 17 | pub 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::Default | 106 | Color::Default |
| @@ -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 | |||
| 212 | impl Toolbar { | 136 | impl 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 | // } | ||
| @@ -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 | div | 3383 | 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>, |
| @@ -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)] |
| 769 | mod tests { | 768 | mod tests { |
| 770 | use super::*; | 769 | use super::*; |