Unified · Split
| @@ -1561,6 +1561,7 @@ dependencies = [ · 1560 unmodified lines | ||
| 1561 | 1561 | "serde_json", |
| 1562 | 1562 | "settings", |
| 1563 | 1563 | "smallvec", |
| 1564 | + "story", | |
| 1564 | 1565 | "theme", |
| 1565 | 1566 | "theme_selector", |
| 1566 | 1567 | "time", |
| @@ -7449,6 +7450,7 @@ dependencies = [ · 5882 unmodified lines | ||
| 7449 | 7450 | "backtrace-on-stack-overflow", |
| 7450 | 7451 | "chrono", |
| 7451 | 7452 | "clap 4.4.4", |
| 7453 | + "collab_ui", | |
| 7452 | 7454 | "dialoguer", |
| 7453 | 7455 | "editor", |
| 7454 | 7456 | "fuzzy", |
| @@ -933,7 +933,7 @@ impl AssistantPanel { · 932 unmodified lines | ||
| 933 | 933 | } |
| 934 | 934 | |
| 935 | 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 | 937 | .on_click(cx.listener(|this, _event, cx| { |
| 938 | 938 | if this.active_editor().is_some() { |
| 939 | 939 | this.set_active_editor_index(None, cx); |
| @@ -957,7 +957,7 @@ impl AssistantPanel { · 17 unmodified lines | ||
| 957 | 957 | } |
| 958 | 958 | |
| 959 | 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 | 961 | .on_click(cx.listener(|this, _event, cx| { |
| 962 | 962 | if let Some(active_editor) = this.active_editor() { |
| 963 | 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 | 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 | 972 | .on_click(cx.listener(|this, _event, cx| { |
| 973 | 973 | if let Some(active_editor) = this.active_editor() { |
| 974 | 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 | 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 | 983 | .on_click(cx.listener(|this, _event, cx| { |
| 984 | 984 | if let Some(workspace) = this.workspace.upgrade() { |
| 985 | 985 | cx.window_context().defer(move |cx| { |
| @@ -994,7 +994,7 @@ impl AssistantPanel { · 8 unmodified lines | ||
| 994 | 994 | } |
| 995 | 995 | |
| 996 | 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 | 998 | .on_click(cx.listener(|this, _event, cx| { |
| 999 | 999 | this.new_conversation(cx); |
| 1000 | 1000 | })) |
| @@ -1004,12 +1004,12 @@ impl AssistantPanel { · 3 unmodified lines | ||
| 1004 | 1004 | |
| 1005 | 1005 | fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { |
| 1006 | 1006 | let zoomed = self.zoomed; |
| 1007 | − IconButton::new("zoom_button", Icon::Maximize) | |
| 1007 | + IconButton::new("zoom_button", IconName::Maximize) | |
| 1008 | 1008 | .on_click(cx.listener(|this, _event, cx| { |
| 1009 | 1009 | this.toggle_zoom(&ToggleZoom, cx); |
| 1010 | 1010 | })) |
| 1011 | 1011 | .selected(zoomed) |
| 1012 | − .selected_icon(Icon::Minimize) | |
| 1012 | + .selected_icon(IconName::Minimize) | |
| 1013 | 1013 | .icon_size(IconSize::Small) |
| 1014 | 1014 | .tooltip(move |cx| { |
| 1015 | 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> { | |
| 1290 | − Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button) | |
| 1289 | + fn icon(&self, cx: &WindowContext) -> Option<IconName> { | |
| 1290 | + Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button) | |
| 1291 | 1291 | } |
| 1292 | 1292 | |
| 1293 | 1293 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -2349,7 +2349,7 @@ impl ConversationEditor { · 1055 unmodified lines | ||
| 2349 | 2349 | div() |
| 2350 | 2350 | .id("error") |
| 2351 | 2351 | .tooltip(move |cx| Tooltip::text(error.clone(), cx)) |
| 2352 | − .child(IconElement::new(Icon::XCircle)), | |
| 2352 | + .child(Icon::new(IconName::XCircle)), | |
| 2353 | 2353 | ) |
| 2354 | 2354 | } else { |
| 2355 | 2355 | None |
| @@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { · 289 unmodified lines | ||
| 2645 | 2645 | .justify_center() |
| 2646 | 2646 | .w(measurements.gutter_width) |
| 2647 | 2647 | .child( |
| 2648 | − IconButton::new("include_conversation", Icon::Ai) | |
| 2648 | + IconButton::new("include_conversation", IconName::Ai) | |
| 2649 | 2649 | .on_click(cx.listener(|this, _, cx| { |
| 2650 | 2650 | this.toggle_include_conversation(&ToggleIncludeConversation, cx) |
| 2651 | 2651 | })) |
| @@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { · 8 unmodified lines | ||
| 2660 | 2660 | ) |
| 2661 | 2661 | .children(if SemanticIndex::enabled(cx) { |
| 2662 | 2662 | Some( |
| 2663 | − IconButton::new("retrieve_context", Icon::MagnifyingGlass) | |
| 2663 | + IconButton::new("retrieve_context", IconName::MagnifyingGlass) | |
| 2664 | 2664 | .on_click(cx.listener(|this, _, cx| { |
| 2665 | 2665 | this.toggle_retrieve_context(&ToggleRetrieveContext, cx) |
| 2666 | 2666 | })) |
| @@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { · 15 unmodified lines | ||
| 2682 | 2682 | div() |
| 2683 | 2683 | .id("error") |
| 2684 | 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 | 2687 | } else { |
| 2688 | 2688 | None |
| @@ -2957,7 +2957,7 @@ impl InlineAssistant { · 268 unmodified lines | ||
| 2957 | 2957 | div() |
| 2958 | 2958 | .id("error") |
| 2959 | 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 | 2961 | .into_any_element() |
| 2962 | 2962 | ), |
| 2963 | 2963 | |
| @@ -2965,7 +2965,7 @@ impl InlineAssistant { · 1 unmodified line | ||
| 2965 | 2965 | div() |
| 2966 | 2966 | .id("error") |
| 2967 | 2967 | .tooltip(|cx| Tooltip::text("Not Indexed", cx)) |
| 2968 | − .child(IconElement::new(Icon::XCircle)) | |
| 2968 | + .child(Icon::new(IconName::XCircle)) | |
| 2969 | 2969 | .into_any_element() |
| 2970 | 2970 | ), |
| 2971 | 2971 | |
| @@ -2996,7 +2996,7 @@ impl InlineAssistant { · 24 unmodified lines | ||
| 2996 | 2996 | div() |
| 2997 | 2997 | .id("update") |
| 2998 | 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 | 3000 | .into_any_element() |
| 3001 | 3001 | ) |
| 3002 | 3002 | } |
| @@ -3005,7 +3005,7 @@ impl InlineAssistant { · 2 unmodified lines | ||
| 3005 | 3005 | div() |
| 3006 | 3006 | .id("check") |
| 3007 | 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 | 3009 | .into_any_element() |
| 3010 | 3010 | ), |
| 3011 | 3011 | } |
| @@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines | ||
| 4 | 4 | }; |
| 5 | 5 | use menu::Cancel; |
| 6 | 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 | 9 | pub struct UpdateNotification { |
| 10 | 10 | version: SemanticVersion, |
| @@ -30,7 +30,7 @@ impl Render for UpdateNotification { · 19 unmodified lines | ||
| 30 | 30 | .child( |
| 31 | 31 | div() |
| 32 | 32 | .id("cancel") |
| 33 | − .child(IconElement::new(Icon::Close)) | |
| 33 | + .child(Icon::new(IconName::Close)) | |
| 34 | 34 | .cursor_pointer() |
| 35 | 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 | 9 | pub mute_on_join: bool, |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | +/// Configuration of voice calls in Zed. | |
| 12 | 13 | #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] |
| 13 | 14 | pub struct CallSettingsContent { |
| 14 | 15 | /// Whether the microphone should be muted when joining a channel or a call. |
| 16 | + /// | |
| 17 | + /// Default: false | |
| 15 | 18 | pub mute_on_join: Option<bool>, |
| 16 | 19 | } |
| 17 | 20 | |
| @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; · 2 unmodified lines | ||
| 3 | 3 | use collections::{hash_map::Entry, HashMap, HashSet}; |
| 4 | 4 | use feature_flags::FeatureFlagAppExt; |
| 5 | 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 | 7 | use postage::{sink::Sink, watch}; |
| 8 | 8 | use rpc::proto::{RequestMessage, UsersResponse}; |
| 9 | 9 | use std::sync::{Arc, Weak}; |
| @@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); · 9 unmodified lines | ||
| 19 | 19 | pub struct User { |
| 20 | 20 | pub id: UserId, |
| 21 | 21 | pub github_login: String, |
| 22 | − pub avatar_uri: SharedString, | |
| 22 | + pub avatar_uri: SharedUrl, | |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | #[derive(Clone, Debug, PartialEq, Eq)] |
| @@ -9,6 +9,8 @@ path = "src/collab_ui.rs" · 8 unmodified lines | ||
| 9 | 9 | doctest = false |
| 10 | 10 | |
| 11 | 11 | [features] |
| 12 | +default = [] | |
| 13 | +stories = ["dep:story"] | |
| 12 | 14 | test-support = [ |
| 13 | 15 | "call/test-support", |
| 14 | 16 | "client/test-support", |
| @@ -44,6 +46,7 @@ project = { path = "../project" } · 29 unmodified lines | ||
| 44 | 46 | recent_projects = { path = "../recent_projects" } |
| 45 | 47 | rpc = { path = "../rpc" } |
| 46 | 48 | settings = { path = "../settings" } |
| 49 | +story = { path = "../story", optional = true } | |
| 47 | 50 | feature_flags = { path = "../feature_flags"} |
| 48 | 51 | theme = { path = "../theme" } |
| 49 | 52 | theme_selector = { path = "../theme_selector" } |
| @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; · 19 unmodified lines | ||
| 20 | 20 | use settings::{Settings, SettingsStore}; |
| 21 | 21 | use std::sync::Arc; |
| 22 | 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 | 24 | use util::{ResultExt, TryFutureExt}; |
| 25 | 25 | use workspace::{ |
| 26 | 26 | dock::{DockPosition, Panel, PanelEvent}, |
| @@ -281,12 +281,12 @@ impl ChatPanel { · 254 unmodified lines | ||
| 281 | 281 | )), |
| 282 | 282 | ) |
| 283 | 283 | .end_child( |
| 284 | − IconButton::new("notes", Icon::File) | |
| 284 | + IconButton::new("notes", IconName::File) | |
| 285 | 285 | .on_click(cx.listener(Self::open_notes)) |
| 286 | 286 | .tooltip(|cx| Tooltip::text("Open notes", cx)), |
| 287 | 287 | ) |
| 288 | 288 | .end_child( |
| 289 | − IconButton::new("call", Icon::AudioOn) | |
| 289 | + IconButton::new("call", IconName::AudioOn) | |
| 290 | 290 | .on_click(cx.listener(Self::join_call)) |
| 291 | 291 | .tooltip(|cx| Tooltip::text("Join call", cx)), |
| 292 | 292 | ), |
| @@ -395,7 +395,7 @@ impl ChatPanel { · 102 unmodified lines | ||
| 395 | 395 | .w_8() |
| 396 | 396 | .visible_on_hover("") |
| 397 | 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 | 399 | cx.listener(move |this, _, cx| { |
| 400 | 400 | this.remove_message(message_id, cx); |
| 401 | 401 | }), |
| @@ -429,7 +429,7 @@ impl ChatPanel { · 27 unmodified lines | ||
| 429 | 429 | Button::new("sign-in", "Sign in") |
| 430 | 430 | .style(ButtonStyle::Filled) |
| 431 | 431 | .icon_color(Color::Muted) |
| 432 | − .icon(Icon::Github) | |
| 432 | + .icon(IconName::Github) | |
| 433 | 433 | .icon_position(IconPosition::Start) |
| 434 | 434 | .full_width() |
| 435 | 435 | .on_click(cx.listener(move |this, _, cx| { |
| @@ -622,12 +622,12 @@ impl Panel for ChatPanel { · 186 unmodified lines | ||
| 622 | 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 | 626 | if !is_channels_feature_enabled(cx) { |
| 627 | 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 | 633 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -31,7 +31,7 @@ use smallvec::SmallVec; · 30 unmodified lines | ||
| 31 | 31 | use std::{mem, sync::Arc}; |
| 32 | 32 | use theme::{ActiveTheme, ThemeSettings}; |
| 33 | 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 | 35 | ListHeader, ListItem, Tooltip, |
| 36 | 36 | }; |
| 37 | 37 | use util::{maybe, ResultExt, TryFutureExt}; |
| @@ -844,7 +844,7 @@ impl CollabPanel { · 806 unmodified lines | ||
| 844 | 844 | .end_slot(if is_pending { |
| 845 | 845 | Label::new("Calling").color(Color::Muted).into_any_element() |
| 846 | 846 | } else if is_current_user { |
| 847 | − IconButton::new("leave-call", Icon::Exit) | |
| 847 | + IconButton::new("leave-call", IconName::Exit) | |
| 848 | 848 | .style(ButtonStyle::Subtle) |
| 849 | 849 | .on_click(move |_, cx| Self::leave_call(cx)) |
| 850 | 850 | .tooltip(|cx| Tooltip::text("Leave Call", cx)) |
| @@ -903,7 +903,7 @@ impl CollabPanel { · 52 unmodified lines | ||
| 903 | 903 | h_stack() |
| 904 | 904 | .gap_1() |
| 905 | 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 | 908 | .child(Label::new(project_name.clone())) |
| 909 | 909 | .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx)) |
| @@ -924,7 +924,7 @@ impl CollabPanel { · 14 unmodified lines | ||
| 924 | 924 | h_stack() |
| 925 | 925 | .gap_1() |
| 926 | 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 | 929 | .child(Label::new("Screen")) |
| 930 | 930 | .when_some(peer_id, |this, _| { |
| @@ -965,7 +965,7 @@ impl CollabPanel { · 34 unmodified lines | ||
| 965 | 965 | h_stack() |
| 966 | 966 | .gap_1() |
| 967 | 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 | 970 | .child(div().h_7().w_full().child(Label::new("notes"))) |
| 971 | 971 | .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx)) |
| @@ -986,7 +986,7 @@ impl CollabPanel { · 14 unmodified lines | ||
| 986 | 986 | h_stack() |
| 987 | 987 | .gap_1() |
| 988 | 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 | 991 | .child(Label::new("chat")) |
| 992 | 992 | .tooltip(move |cx| Tooltip::text("Open Chat", cx)) |
| @@ -1757,7 +1757,7 @@ impl CollabPanel { · 764 unmodified lines | ||
| 1757 | 1757 | .child( |
| 1758 | 1758 | Button::new("sign_in", "Sign in") |
| 1759 | 1759 | .icon_color(Color::Muted) |
| 1760 | − .icon(Icon::Github) | |
| 1760 | + .icon(IconName::Github) | |
| 1761 | 1761 | .icon_position(IconPosition::Start) |
| 1762 | 1762 | .style(ButtonStyle::Filled) |
| 1763 | 1763 | .full_width() |
| @@ -1949,7 +1949,7 @@ impl CollabPanel { · 185 unmodified lines | ||
| 1949 | 1949 | let button = match section { |
| 1950 | 1950 | Section::ActiveCall => channel_link.map(|channel_link| { |
| 1951 | 1951 | let channel_link_copy = channel_link.clone(); |
| 1952 | − IconButton::new("channel-link", Icon::Copy) | |
| 1952 | + IconButton::new("channel-link", IconName::Copy) | |
| 1953 | 1953 | .icon_size(IconSize::Small) |
| 1954 | 1954 | .size(ButtonSize::None) |
| 1955 | 1955 | .visible_on_hover("section-header") |
| @@ -1961,13 +1961,13 @@ impl CollabPanel { · 5 unmodified lines | ||
| 1961 | 1961 | .into_any_element() |
| 1962 | 1962 | }), |
| 1963 | 1963 | Section::Contacts => Some( |
| 1964 | − IconButton::new("add-contact", Icon::Plus) | |
| 1964 | + IconButton::new("add-contact", IconName::Plus) | |
| 1965 | 1965 | .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) |
| 1966 | 1966 | .tooltip(|cx| Tooltip::text("Search for new contact", cx)) |
| 1967 | 1967 | .into_any_element(), |
| 1968 | 1968 | ), |
| 1969 | 1969 | Section::Channels => Some( |
| 1970 | − IconButton::new("add-channel", Icon::Plus) | |
| 1970 | + IconButton::new("add-channel", IconName::Plus) | |
| 1971 | 1971 | .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx))) |
| 1972 | 1972 | .tooltip(|cx| Tooltip::text("Create a channel", cx)) |
| 1973 | 1973 | .into_any_element(), |
| @@ -2038,7 +2038,7 @@ impl CollabPanel { · 64 unmodified lines | ||
| 2038 | 2038 | }) |
| 2039 | 2039 | .when(!calling, |el| { |
| 2040 | 2040 | el.child( |
| 2041 | − IconButton::new("remove_contact", Icon::Close) | |
| 2041 | + IconButton::new("remove_contact", IconName::Close) | |
| 2042 | 2042 | .icon_color(Color::Muted) |
| 2043 | 2043 | .visible_on_hover("") |
| 2044 | 2044 | .tooltip(|cx| Tooltip::text("Remove Contact", cx)) |
| @@ -2099,13 +2099,13 @@ impl CollabPanel { · 54 unmodified lines | ||
| 2099 | 2099 | |
| 2100 | 2100 | let controls = if is_incoming { |
| 2101 | 2101 | vec![ |
| 2102 | − IconButton::new("decline-contact", Icon::Close) | |
| 2102 | + IconButton::new("decline-contact", IconName::Close) | |
| 2103 | 2103 | .on_click(cx.listener(move |this, _, cx| { |
| 2104 | 2104 | this.respond_to_contact_request(user_id, false, cx); |
| 2105 | 2105 | })) |
| 2106 | 2106 | .icon_color(color) |
| 2107 | 2107 | .tooltip(|cx| Tooltip::text("Decline invite", cx)), |
| 2108 | − IconButton::new("accept-contact", Icon::Check) | |
| 2108 | + IconButton::new("accept-contact", IconName::Check) | |
| 2109 | 2109 | .on_click(cx.listener(move |this, _, cx| { |
| 2110 | 2110 | this.respond_to_contact_request(user_id, true, cx); |
| 2111 | 2111 | })) |
| @@ -2114,7 +2114,7 @@ impl CollabPanel { · 2 unmodified lines | ||
| 2114 | 2114 | ] |
| 2115 | 2115 | } else { |
| 2116 | 2116 | let github_login = github_login.clone(); |
| 2117 | − vec![IconButton::new("remove_contact", Icon::Close) | |
| 2117 | + vec![IconButton::new("remove_contact", IconName::Close) | |
| 2118 | 2118 | .on_click(cx.listener(move |this, _, cx| { |
| 2119 | 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 | 2156 | let controls = [ |
| 2157 | − IconButton::new("reject-invite", Icon::Close) | |
| 2157 | + IconButton::new("reject-invite", IconName::Close) | |
| 2158 | 2158 | .on_click(cx.listener(move |this, _, cx| { |
| 2159 | 2159 | this.respond_to_channel_invite(channel_id, false, cx); |
| 2160 | 2160 | })) |
| 2161 | 2161 | .icon_color(color) |
| 2162 | 2162 | .tooltip(|cx| Tooltip::text("Decline invite", cx)), |
| 2163 | − IconButton::new("accept-invite", Icon::Check) | |
| 2163 | + IconButton::new("accept-invite", IconName::Check) | |
| 2164 | 2164 | .on_click(cx.listener(move |this, _, cx| { |
| 2165 | 2165 | this.respond_to_channel_invite(channel_id, true, cx); |
| 2166 | 2166 | })) |
| @@ -2178,7 +2178,7 @@ impl CollabPanel { · 11 unmodified lines | ||
| 2178 | 2178 | .child(h_stack().children(controls)), |
| 2179 | 2179 | ) |
| 2180 | 2180 | .start_slot( |
| 2181 | − IconElement::new(Icon::Hash) | |
| 2181 | + Icon::new(IconName::Hash) | |
| 2182 | 2182 | .size(IconSize::Small) |
| 2183 | 2183 | .color(Color::Muted), |
| 2184 | 2184 | ) |
| @@ -2190,7 +2190,7 @@ impl CollabPanel { · 5 unmodified lines | ||
| 2190 | 2190 | cx: &mut ViewContext<Self>, |
| 2191 | 2191 | ) -> ListItem { |
| 2192 | 2192 | ListItem::new("contact-placeholder") |
| 2193 | − .child(IconElement::new(Icon::Plus)) | |
| 2193 | + .child(Icon::new(IconName::Plus)) | |
| 2194 | 2194 | .child(Label::new("Add a Contact")) |
| 2195 | 2195 | .selected(is_selected) |
| 2196 | 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 | 2276 | let messages_button = |cx: &mut ViewContext<Self>| { |
| 2277 | − IconButton::new("channel_chat", Icon::MessageBubbles) | |
| 2277 | + IconButton::new("channel_chat", IconName::MessageBubbles) | |
| 2278 | 2278 | .icon_size(IconSize::Small) |
| 2279 | 2279 | .icon_color(if has_messages_notification { |
| 2280 | 2280 | Color::Default |
| @@ -2286,7 +2286,7 @@ impl CollabPanel { · 5 unmodified lines | ||
| 2286 | 2286 | }; |
| 2287 | 2287 | |
| 2288 | 2288 | let notes_button = |cx: &mut ViewContext<Self>| { |
| 2289 | − IconButton::new("channel_notes", Icon::File) | |
| 2289 | + IconButton::new("channel_notes", IconName::File) | |
| 2290 | 2290 | .icon_size(IconSize::Small) |
| 2291 | 2291 | .icon_color(if has_notes_notification { |
| 2292 | 2292 | Color::Default |
| @@ -2343,9 +2343,13 @@ impl CollabPanel { · 50 unmodified lines | ||
| 2343 | 2343 | }, |
| 2344 | 2344 | )) |
| 2345 | 2345 | .start_slot( |
| 2346 | − IconElement::new(if is_public { Icon::Public } else { Icon::Hash }) | |
| 2347 | − .size(IconSize::Small) | |
| 2348 | − .color(Color::Muted), | |
| 2346 | + Icon::new(if is_public { | |
| 2347 | + IconName::Public | |
| 2348 | + } else { | |
| 2349 | + IconName::Hash | |
| 2350 | + }) | |
| 2351 | + .size(IconSize::Small) | |
| 2352 | + .color(Color::Muted), | |
| 2349 | 2353 | ) |
| 2350 | 2354 | .child( |
| 2351 | 2355 | h_stack() |
| @@ -2414,7 +2418,7 @@ impl CollabPanel { · 62 unmodified lines | ||
| 2414 | 2418 | .indent_level(depth + 1) |
| 2415 | 2419 | .indent_step_size(px(20.)) |
| 2416 | 2420 | .start_slot( |
| 2417 | − IconElement::new(Icon::Hash) | |
| 2421 | + Icon::new(IconName::Hash) | |
| 2418 | 2422 | .size(IconSize::Small) |
| 2419 | 2423 | .color(Color::Muted), |
| 2420 | 2424 | ); |
| @@ -2528,10 +2532,10 @@ impl Panel for CollabPanel { · 107 unmodified lines | ||
| 2528 | 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 | 2536 | CollaborationPanelSettings::get_global(cx) |
| 2533 | 2537 | .button |
| 2534 | − .then(|| ui::Icon::Collab) | |
| 2538 | + .then(|| ui::IconName::Collab) | |
| 2535 | 2539 | } |
| 2536 | 2540 | |
| 2537 | 2541 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -2669,11 +2673,11 @@ impl Render for DraggedChannelView { · 131 unmodified lines | ||
| 2669 | 2673 | .p_1() |
| 2670 | 2674 | .gap_1() |
| 2671 | 2675 | .child( |
| 2672 | − IconElement::new( | |
| 2676 | + Icon::new( | |
| 2673 | 2677 | if self.channel.visibility == proto::ChannelVisibility::Public { |
| 2674 | − Icon::Public | |
| 2678 | + IconName::Public | |
| 2675 | 2679 | } else { |
| 2676 | − Icon::Hash | |
| 2680 | + IconName::Hash | |
| 2677 | 2681 | }, |
| 2678 | 2682 | ) |
| 2679 | 2683 | .size(IconSize::Small) |
| @@ -168,7 +168,7 @@ impl Render for ChannelModal { · 167 unmodified lines | ||
| 168 | 168 | .w_px() |
| 169 | 169 | .flex_1() |
| 170 | 170 | .gap_1() |
| 171 | − .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) | |
| 171 | + .child(Icon::new(IconName::Hash).size(IconSize::Medium)) | |
| 172 | 172 | .child(Label::new(channel_name)), |
| 173 | 173 | ) |
| 174 | 174 | .child( |
| @@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { · 231 unmodified lines | ||
| 406 | 406 | Some(ChannelRole::Guest) => Some(Label::new("Guest")), |
| 407 | 407 | _ => None, |
| 408 | 408 | }) |
| 409 | − .child(IconButton::new("ellipsis", Icon::Ellipsis)) | |
| 409 | + .child(IconButton::new("ellipsis", IconName::Ellipsis)) | |
| 410 | 410 | .children( |
| 411 | 411 | if let (Some((menu, _)), true) = (&self.context_menu, selected) { |
| 412 | 412 | Some( |
| @@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { · 154 unmodified lines | ||
| 155 | 155 | .selected(selected) |
| 156 | 156 | .start_slot(Avatar::new(user.avatar_uri.clone())) |
| 157 | 157 | .child(Label::new(user.github_login.clone())) |
| 158 | − .end_slot::<IconElement>( | |
| 159 | − icon_path.map(|icon_path| IconElement::from_path(icon_path)), | |
| 160 | − ), | |
| 158 | + .end_slot::<Icon>(icon_path.map(|icon_path| Icon::from_path(icon_path))), | |
| 161 | 159 | ) |
| 162 | 160 | } |
| 163 | 161 | } |
| @@ -15,7 +15,7 @@ use std::sync::Arc; · 14 unmodified lines | ||
| 15 | 15 | use theme::{ActiveTheme, PlayerColors}; |
| 16 | 16 | use ui::{ |
| 17 | 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 | 20 | use util::ResultExt; |
| 21 | 21 | use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; |
| @@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { · 191 unmodified lines | ||
| 213 | 213 | .child( |
| 214 | 214 | div() |
| 215 | 215 | .child( |
| 216 | − IconButton::new("leave-call", ui::Icon::Exit) | |
| 216 | + IconButton::new("leave-call", ui::IconName::Exit) | |
| 217 | 217 | .style(ButtonStyle::Subtle) |
| 218 | 218 | .tooltip(|cx| Tooltip::text("Leave call", cx)) |
| 219 | 219 | .icon_size(IconSize::Small) |
| @@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { · 10 unmodified lines | ||
| 230 | 230 | IconButton::new( |
| 231 | 231 | "mute-microphone", |
| 232 | 232 | if is_muted { |
| 233 | − ui::Icon::MicMute | |
| 233 | + ui::IconName::MicMute | |
| 234 | 234 | } else { |
| 235 | − ui::Icon::Mic | |
| 235 | + ui::IconName::Mic | |
| 236 | 236 | }, |
| 237 | 237 | ) |
| 238 | 238 | .tooltip(move |cx| { |
| @@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { · 17 unmodified lines | ||
| 256 | 256 | IconButton::new( |
| 257 | 257 | "mute-sound", |
| 258 | 258 | if is_deafened { |
| 259 | − ui::Icon::AudioOff | |
| 259 | + ui::IconName::AudioOff | |
| 260 | 260 | } else { |
| 261 | − ui::Icon::AudioOn | |
| 261 | + ui::IconName::AudioOn | |
| 262 | 262 | }, |
| 263 | 263 | ) |
| 264 | 264 | .style(ButtonStyle::Subtle) |
| @@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { · 16 unmodified lines | ||
| 281 | 281 | ) |
| 282 | 282 | .when(!read_only, |this| { |
| 283 | 283 | this.child( |
| 284 | − IconButton::new("screen-share", ui::Icon::Screen) | |
| 284 | + IconButton::new("screen-share", ui::IconName::Screen) | |
| 285 | 285 | .style(ButtonStyle::Subtle) |
| 286 | 286 | .icon_size(IconSize::Small) |
| 287 | 287 | .selected(is_screen_sharing) |
| @@ -573,7 +573,7 @@ impl CollabTitlebarItem { · 285 unmodified lines | ||
| 573 | 573 | | client::Status::ReconnectionError { .. } => Some( |
| 574 | 574 | div() |
| 575 | 575 | .id("disconnected") |
| 576 | − .child(IconElement::new(Icon::Disconnected).size(IconSize::Small)) | |
| 576 | + .child(Icon::new(IconName::Disconnected).size(IconSize::Small)) | |
| 577 | 577 | .tooltip(|cx| Tooltip::text("Disconnected", cx)) |
| 578 | 578 | .into_any_element(), |
| 579 | 579 | ), |
| @@ -643,7 +643,7 @@ impl CollabTitlebarItem { · 63 unmodified lines | ||
| 643 | 643 | h_stack() |
| 644 | 644 | .gap_0p5() |
| 645 | 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 | 648 | .style(ButtonStyle::Subtle) |
| 649 | 649 | .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), |
| @@ -665,7 +665,7 @@ impl CollabTitlebarItem { · 15 unmodified lines | ||
| 665 | 665 | .child( |
| 666 | 666 | h_stack() |
| 667 | 667 | .gap_0p5() |
| 668 | − .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), | |
| 668 | + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), | |
| 669 | 669 | ) |
| 670 | 670 | .style(ButtonStyle::Subtle) |
| 671 | 671 | .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), |
| @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; · 18 unmodified lines | ||
| 19 | 19 | use settings::{Settings, SettingsStore}; |
| 20 | 20 | use std::{sync::Arc, time::Duration}; |
| 21 | 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 | 23 | use util::{ResultExt, TryFutureExt}; |
| 24 | 24 | use workspace::{ |
| 25 | 25 | dock::{DockPosition, Panel, PanelEvent}, |
| @@ -553,7 +553,7 @@ impl Render for NotificationPanel { · 527 unmodified lines | ||
| 553 | 553 | .border_b_1() |
| 554 | 554 | .border_color(cx.theme().colors().border) |
| 555 | 555 | .child(Label::new("Notifications")) |
| 556 | − .child(IconElement::new(Icon::Envelope)), | |
| 556 | + .child(Icon::new(IconName::Envelope)), | |
| 557 | 557 | ) |
| 558 | 558 | .map(|this| { |
| 559 | 559 | if self.client.user_id().is_none() { |
| @@ -564,7 +564,7 @@ impl Render for NotificationPanel { · 4 unmodified lines | ||
| 564 | 564 | .child( |
| 565 | 565 | Button::new("sign_in_prompt_button", "Sign in") |
| 566 | 566 | .icon_color(Color::Muted) |
| 567 | − .icon(Icon::Github) | |
| 567 | + .icon(IconName::Github) | |
| 568 | 568 | .icon_position(IconPosition::Start) |
| 569 | 569 | .style(ButtonStyle::Filled) |
| 570 | 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 | 659 | (NotificationPanelSettings::get_global(cx).button |
| 660 | 660 | && self.notification_store.read(cx).notification_count() > 0) |
| 661 | − .then(|| Icon::Bell) | |
| 661 | + .then(|| IconName::Bell) | |
| 662 | 662 | } |
| 663 | 663 | |
| 664 | 664 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -716,7 +716,7 @@ impl Render for NotificationToast { · 51 unmodified lines | ||
| 716 | 716 | .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) |
| 717 | 717 | .child(Label::new(self.text.clone())) |
| 718 | 718 | .child( |
| 719 | − IconButton::new("close", Icon::Close) | |
| 719 | + IconButton::new("close", IconName::Close) | |
| 720 | 720 | .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), |
| 721 | 721 | ) |
| 722 | 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 | 8 | use gpui::AppContext; |
| 2 | 9 | use std::sync::Arc; |
| 3 | 10 | use workspace::AppState; |
| 4 | 11 | |
| 5 | −pub mod incoming_call_notification; | |
| 6 | −pub mod project_shared_notification; | |
| 12 | +#[cfg(feature = "stories")] | |
| 13 | +pub use stories::*; | |
| 7 | 14 | |
| 8 | 15 | pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { |
| 9 | 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 | 1 | use crate::notification_window_options; |
| 2 | +use crate::notifications::collab_notification::CollabNotification; | |
| 2 | 3 | use call::{ActiveCall, IncomingCall}; |
| 3 | 4 | use futures::StreamExt; |
| 4 | −use gpui::{ | |
| 5 | − img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext, | |
| 6 | − VisualContext as _, WindowHandle, | |
| 7 | −}; | |
| 5 | +use gpui::{prelude::*, AppContext, WindowHandle}; | |
| 8 | 6 | use settings::Settings; |
| 9 | 7 | use std::sync::{Arc, Weak}; |
| 10 | 8 | use theme::ThemeSettings; |
| 11 | −use ui::prelude::*; | |
| 12 | −use ui::{h_stack, v_stack, Button, Label}; | |
| 9 | +use ui::{prelude::*, Button, Label}; | |
| 13 | 10 | use util::ResultExt; |
| 14 | 11 | use workspace::AppState; |
| 15 | 12 | |
| @@ -31,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 15 unmodified lines | ||
| 31 | 28 | if let Some(incoming_call) = incoming_call { |
| 32 | 29 | let unique_screens = cx.update(|cx| cx.displays()).unwrap(); |
| 33 | 30 | let window_size = gpui::Size { |
| 34 | − width: px(380.), | |
| 35 | − height: px(64.), | |
| 31 | + width: px(400.), | |
| 32 | + height: px(72.), | |
| 36 | 33 | }; |
| 37 | 34 | |
| 38 | 35 | for screen in unique_screens { |
| @@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { · 90 unmodified lines | ||
| 129 | 126 | |
| 130 | 127 | cx.set_rem_size(ui_font_size); |
| 131 | 128 | |
| 132 | − h_stack() | |
| 133 | − .font(ui_font) | |
| 134 | − .text_ui() | |
| 135 | − .justify_between() | |
| 136 | − .size_full() | |
| 137 | − .overflow_hidden() | |
| 138 | − .elevation_3(cx) | |
| 139 | − .p_2() | |
| 140 | − .gap_2() | |
| 141 | − .child( | |
| 142 | − img(self.state.call.calling_user.avatar_uri.clone()) | |
| 143 | − .w_12() | |
| 144 | − .h_12() | |
| 145 | − .rounded_full(), | |
| 129 | + div().size_full().font(ui_font).child( | |
| 130 | + CollabNotification::new( | |
| 131 | + self.state.call.calling_user.avatar_uri.clone(), | |
| 132 | + Button::new("accept", "Accept").on_click({ | |
| 133 | + let state = self.state.clone(); | |
| 134 | + move |_, cx| state.respond(true, cx) | |
| 135 | + }), | |
| 136 | + Button::new("decline", "Decline").on_click({ | |
| 137 | + let state = self.state.clone(); | |
| 138 | + move |_, cx| state.respond(false, cx) | |
| 139 | + }), | |
| 146 | 140 | ) |
| 147 | 141 | .child(v_stack().overflow_hidden().child(Label::new(format!( |
| 148 | 142 | "{} is sharing a project in Zed", |
| 149 | 143 | self.state.call.calling_user.github_login |
| 150 | − )))) | |
| 151 | − .child( | |
| 152 | − v_stack() | |
| 153 | − .child(Button::new("accept", "Accept").render(cx).on_click({ | |
| 154 | − let state = self.state.clone(); | |
| 155 | − move |_, cx| state.respond(true, cx) | |
| 156 | − })) | |
| 157 | − .child(Button::new("decline", "Decline").render(cx).on_click({ | |
| 158 | − let state = self.state.clone(); | |
| 159 | − move |_, cx| state.respond(false, cx) | |
| 160 | − })), | |
| 161 | − ) | |
| 144 | + )))), | |
| 145 | + ) | |
| 162 | 146 | } |
| 163 | 147 | } |
| @@ -1,12 +1,13 @@ | ||
| 1 | 1 | use crate::notification_window_options; |
| 2 | +use crate::notifications::collab_notification::CollabNotification; | |
| 2 | 3 | use call::{room, ActiveCall}; |
| 3 | 4 | use client::User; |
| 4 | 5 | use collections::HashMap; |
| 5 | −use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext}; | |
| 6 | +use gpui::{AppContext, Size}; | |
| 6 | 7 | use settings::Settings; |
| 7 | 8 | use std::sync::{Arc, Weak}; |
| 8 | 9 | use theme::ThemeSettings; |
| 9 | −use ui::{h_stack, prelude::*, v_stack, Button, Label}; | |
| 10 | +use ui::{prelude::*, Button, Label}; | |
| 10 | 11 | use workspace::AppState; |
| 11 | 12 | |
| 12 | 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 | 132 | cx.set_rem_size(ui_font_size); |
| 132 | 133 | |
| 133 | − h_stack() | |
| 134 | − .font(ui_font) | |
| 135 | − .text_ui() | |
| 136 | − .justify_between() | |
| 137 | − .size_full() | |
| 138 | − .overflow_hidden() | |
| 139 | − .elevation_3(cx) | |
| 140 | − .p_2() | |
| 141 | − .gap_2() | |
| 142 | − .child( | |
| 143 | − img(self.owner.avatar_uri.clone()) | |
| 144 | − .w_12() | |
| 145 | − .h_12() | |
| 146 | − .rounded_full(), | |
| 147 | − ) | |
| 148 | − .child( | |
| 149 | − v_stack() | |
| 150 | − .overflow_hidden() | |
| 151 | − .child(Label::new(self.owner.github_login.clone())) | |
| 152 | − .child(Label::new(format!( | |
| 153 | − "is sharing a project in Zed{}", | |
| 154 | − if self.worktree_root_names.is_empty() { | |
| 155 | − "" | |
| 156 | − } else { | |
| 157 | − ":" | |
| 158 | − } | |
| 159 | − ))) | |
| 160 | − .children(if self.worktree_root_names.is_empty() { | |
| 161 | − None | |
| 162 | − } else { | |
| 163 | − Some(Label::new(self.worktree_root_names.join(", "))) | |
| 164 | − }), | |
| 165 | − ) | |
| 166 | − .child( | |
| 167 | − v_stack() | |
| 168 | − .child(Button::new("open", "Open").on_click(cx.listener( | |
| 169 | − move |this, _event, cx| { | |
| 170 | − this.join(cx); | |
| 171 | − }, | |
| 172 | − ))) | |
| 173 | − .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( | |
| 174 | − move |this, _event, cx| { | |
| 175 | − this.dismiss(cx); | |
| 176 | − }, | |
| 177 | − ))), | |
| 134 | + div().size_full().font(ui_font).child( | |
| 135 | + CollabNotification::new( | |
| 136 | + self.owner.avatar_uri.clone(), | |
| 137 | + Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| { | |
| 138 | + this.join(cx); | |
| 139 | + })), | |
| 140 | + Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| { | |
| 141 | + this.dismiss(cx); | |
| 142 | + })), | |
| 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 | 17 | use workspace::{ |
| 18 | 18 | create_and_open_local_file, |
| 19 | 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 | 23 | StatusItemView, Toast, Workspace, |
| 22 | 24 | }; |
| 23 | 25 | use zed_actions::OpenBrowser; |
| @@ -51,15 +53,15 @@ impl Render for CopilotButton { · 27 unmodified lines | ||
| 51 | 53 | .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); |
| 52 | 54 | |
| 53 | 55 | let icon = match status { |
| 54 | − Status::Error(_) => Icon::CopilotError, | |
| 56 | + Status::Error(_) => IconName::CopilotError, | |
| 55 | 57 | Status::Authorized => { |
| 56 | 58 | if enabled { |
| 57 | − Icon::Copilot | |
| 59 | + IconName::Copilot | |
| 58 | 60 | } else { |
| 59 | − Icon::CopilotDisabled | |
| 61 | + IconName::CopilotDisabled | |
| 60 | 62 | } |
| 61 | 63 | } |
| 62 | − _ => Icon::CopilotInit, | |
| 64 | + _ => IconName::CopilotInit, | |
| 63 | 65 | }; |
| 64 | 66 | |
| 65 | 67 | if let Status::Error(e) = status { |
| @@ -4,7 +4,7 @@ use gpui::{ · 3 unmodified lines | ||
| 4 | 4 | FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, |
| 5 | 5 | Subscription, ViewContext, |
| 6 | 6 | }; |
| 7 | −use ui::{prelude::*, Button, Icon, Label}; | |
| 7 | +use ui::{prelude::*, Button, IconName, Label}; | |
| 8 | 8 | use workspace::ModalView; |
| 9 | 9 | |
| 10 | 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 | 175 | .w_32() |
| 176 | 176 | .h_16() |
| 177 | 177 | .flex_none() |
| 178 | − .path(Icon::ZedXCopilot.path()) | |
| 178 | + .path(IconName::ZedXCopilot.path()) | |
| 179 | 179 | .text_color(cx.theme().colors().icon), |
| 180 | 180 | ) |
| 181 | 181 | .child(prompt) |
| @@ -36,7 +36,7 @@ use std::{ · 35 unmodified lines | ||
| 36 | 36 | }; |
| 37 | 37 | use theme::ActiveTheme; |
| 38 | 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 | 40 | use util::TryFutureExt; |
| 41 | 41 | use workspace::{ |
| 42 | 42 | item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, |
| @@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { · 617 unmodified lines | ||
| 660 | 660 | then.child( |
| 661 | 661 | h_stack() |
| 662 | 662 | .gap_1() |
| 663 | − .child(IconElement::new(Icon::XCircle).color(Color::Error)) | |
| 663 | + .child(Icon::new(IconName::XCircle).color(Color::Error)) | |
| 664 | 664 | .child(Label::new(self.summary.error_count.to_string()).color( |
| 665 | 665 | if selected { |
| 666 | 666 | Color::Default |
| @@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { · 7 unmodified lines | ||
| 674 | 674 | then.child( |
| 675 | 675 | h_stack() |
| 676 | 676 | .gap_1() |
| 677 | − .child( | |
| 678 | − IconElement::new(Icon::ExclamationTriangle).color(Color::Warning), | |
| 679 | − ) | |
| 677 | + .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) | |
| 680 | 678 | .child(Label::new(self.summary.warning_count.to_string()).color( |
| 681 | 679 | if selected { |
| 682 | 680 | Color::Default |
| @@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { · 133 unmodified lines | ||
| 816 | 814 | .flex_none() |
| 817 | 815 | .map(|icon| { |
| 818 | 816 | if diagnostic.severity == DiagnosticSeverity::ERROR { |
| 819 | − icon.path(Icon::XCircle.path()) | |
| 817 | + icon.path(IconName::XCircle.path()) | |
| 820 | 818 | .text_color(Color::Error.color(cx)) |
| 821 | 819 | } else { |
| 822 | − icon.path(Icon::ExclamationTriangle.path()) | |
| 820 | + icon.path(IconName::ExclamationTriangle.path()) | |
| 823 | 821 | .text_color(Color::Warning.color(cx)) |
| 824 | 822 | } |
| 825 | 823 | }), |
| @@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines | ||
| 6 | 6 | }; |
| 7 | 7 | use language::Diagnostic; |
| 8 | 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 | 10 | use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; |
| 11 | 11 | |
| 12 | 12 | use crate::{Deploy, ProjectDiagnosticsEditor}; |
| @@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { · 12 unmodified lines | ||
| 25 | 25 | let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { |
| 26 | 26 | (0, 0) => h_stack().map(|this| { |
| 27 | 27 | this.child( |
| 28 | − IconElement::new(Icon::Check) | |
| 28 | + Icon::new(IconName::Check) | |
| 29 | 29 | .size(IconSize::Small) |
| 30 | 30 | .color(Color::Default), |
| 31 | 31 | ) |
| @@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line | ||
| 33 | 33 | (0, warning_count) => h_stack() |
| 34 | 34 | .gap_1() |
| 35 | 35 | .child( |
| 36 | − IconElement::new(Icon::ExclamationTriangle) | |
| 36 | + Icon::new(IconName::ExclamationTriangle) | |
| 37 | 37 | .size(IconSize::Small) |
| 38 | 38 | .color(Color::Warning), |
| 39 | 39 | ) |
| @@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { · 1 unmodified line | ||
| 41 | 41 | (error_count, 0) => h_stack() |
| 42 | 42 | .gap_1() |
| 43 | 43 | .child( |
| 44 | − IconElement::new(Icon::XCircle) | |
| 44 | + Icon::new(IconName::XCircle) | |
| 45 | 45 | .size(IconSize::Small) |
| 46 | 46 | .color(Color::Error), |
| 47 | 47 | ) |
| @@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { · 1 unmodified line | ||
| 49 | 49 | (error_count, warning_count) => h_stack() |
| 50 | 50 | .gap_1() |
| 51 | 51 | .child( |
| 52 | − IconElement::new(Icon::XCircle) | |
| 52 | + Icon::new(IconName::XCircle) | |
| 53 | 53 | .size(IconSize::Small) |
| 54 | 54 | .color(Color::Error), |
| 55 | 55 | ) |
| 56 | 56 | .child(Label::new(error_count.to_string()).size(LabelSize::Small)) |
| 57 | 57 | .child( |
| 58 | − IconElement::new(Icon::ExclamationTriangle) | |
| 58 | + Icon::new(IconName::ExclamationTriangle) | |
| 59 | 59 | .size(IconSize::Small) |
| 60 | 60 | .color(Color::Warning), |
| 61 | 61 | ) |
| @@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { · 4 unmodified lines | ||
| 66 | 66 | Some( |
| 67 | 67 | h_stack() |
| 68 | 68 | .gap_2() |
| 69 | − .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) | |
| 69 | + .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) | |
| 70 | 70 | .child( |
| 71 | 71 | Label::new("Checking…") |
| 72 | 72 | .size(LabelSize::Small) |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | use crate::ProjectDiagnosticsEditor; |
| 2 | 2 | use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; |
| 3 | 3 | use ui::prelude::*; |
| 4 | −use ui::{Icon, IconButton, Tooltip}; | |
| 4 | +use ui::{IconButton, IconName, Tooltip}; | |
| 5 | 5 | use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; |
| 6 | 6 | |
| 7 | 7 | pub struct ToolbarControls { |
| @@ -24,7 +24,7 @@ impl Render for ToolbarControls { · 16 unmodified lines | ||
| 24 | 24 | }; |
| 25 | 25 | |
| 26 | 26 | div().child( |
| 27 | − IconButton::new("toggle-warnings", Icon::ExclamationTriangle) | |
| 27 | + IconButton::new("toggle-warnings", IconName::ExclamationTriangle) | |
| 28 | 28 | .tooltip(move |cx| Tooltip::text(tooltip, cx)) |
| 29 | 29 | .on_click(cx.listener(|this, _, cx| { |
| 30 | 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 | 1015 | .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) |
| 1016 | 1016 | .unwrap_or(10); |
| 1017 | 1017 | |
| 1018 | − let _test_platform = &cx.test_platform; | |
| 1019 | 1018 | let mut tab_size = rng.gen_range(1..=4); |
| 1020 | 1019 | let buffer_start_excerpt_header_height = rng.gen_range(1..=5); |
| 1021 | 1020 | let excerpt_header_height = rng.gen_range(1..=5); |
| @@ -99,8 +99,8 @@ use sum_tree::TreeMap; · 98 unmodified lines | ||
| 99 | 99 | use text::{OffsetUtf16, Rope}; |
| 100 | 100 | use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; |
| 101 | 101 | use ui::{ |
| 102 | − h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover, | |
| 103 | − Tooltip, | |
| 102 | + h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, | |
| 103 | + Popover, Tooltip, | |
| 104 | 104 | }; |
| 105 | 105 | use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; |
| 106 | 106 | use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; |
| @@ -507,7 +507,7 @@ pub enum SoftWrap { · 400 unmodified lines | ||
| 507 | 507 | Column(u32), |
| 508 | 508 | } |
| 509 | 509 | |
| 510 | −#[derive(Clone, Default)] | |
| 510 | +#[derive(Clone)] | |
| 511 | 511 | pub struct EditorStyle { |
| 512 | 512 | pub background: Hsla, |
| 513 | 513 | pub local_player: PlayerColor, |
| @@ -519,6 +519,24 @@ pub struct EditorStyle { · 5 unmodified lines | ||
| 519 | 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 | 540 | type CompletionId = usize; |
| 523 | 541 | |
| 524 | 542 | // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; |
| @@ -4223,7 +4241,7 @@ impl Editor { · 3698 unmodified lines | ||
| 4223 | 4241 | ) -> Option<IconButton> { |
| 4224 | 4242 | if self.available_code_actions.is_some() { |
| 4225 | 4243 | Some( |
| 4226 | − IconButton::new("code_actions_indicator", ui::Icon::Bolt) | |
| 4244 | + IconButton::new("code_actions_indicator", ui::IconName::Bolt) | |
| 4227 | 4245 | .icon_size(IconSize::Small) |
| 4228 | 4246 | .icon_color(Color::Muted) |
| 4229 | 4247 | .selected(is_active) |
| @@ -4257,7 +4275,7 @@ impl Editor { · 27 unmodified lines | ||
| 4257 | 4275 | fold_data |
| 4258 | 4276 | .map(|(fold_status, buffer_row, active)| { |
| 4259 | 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 | 4279 | .on_click(cx.listener(move |editor, _e, cx| match fold_status { |
| 4262 | 4280 | FoldStatus::Folded => { |
| 4263 | 4281 | editor.unfold_at(&UnfoldAt { buffer_row }, cx); |
| @@ -4269,7 +4287,7 @@ impl Editor { · 5 unmodified lines | ||
| 4269 | 4287 | .icon_color(ui::Color::Muted) |
| 4270 | 4288 | .icon_size(ui::IconSize::Small) |
| 4271 | 4289 | .selected(fold_status == FoldStatus::Folded) |
| 4272 | − .selected_icon(ui::Icon::ChevronRight) | |
| 4290 | + .selected_icon(ui::IconName::ChevronRight) | |
| 4273 | 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 | 9759 | .child( |
| 9742 | − IconButton::new(("copy-block", cx.block_id), Icon::Copy) | |
| 9760 | + IconButton::new(("copy-block", cx.block_id), IconName::Copy) | |
| 9743 | 9761 | .icon_color(Color::Muted) |
| 9744 | 9762 | .size(ButtonSize::Compact) |
| 9745 | 9763 | .style(ButtonStyle::Transparent) |
| @@ -28,7 +28,7 @@ use gpui::{ · 27 unmodified lines | ||
| 28 | 28 | AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, |
| 29 | 29 | CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, |
| 30 | 30 | InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, |
| 31 | − MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine, | |
| 31 | + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, | |
| 32 | 32 | SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, |
| 33 | 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 | 584 | fn paint_background( |
| 620 | 585 | &self, |
| 621 | 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 | 2419 | &mut self, |
| 2455 | − bounds: Bounds<Pixels>, | |
| 2456 | − gutter_bounds: Bounds<Pixels>, | |
| 2457 | − text_bounds: Bounds<Pixels>, | |
| 2420 | + interactive_bounds: &InteractiveBounds, | |
| 2458 | 2421 | layout: &LayoutState, |
| 2459 | 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 | 2424 | cx.on_mouse_event({ |
| 2467 | 2425 | let position_map = layout.position_map.clone(); |
| 2468 | 2426 | let editor = self.editor.clone(); |
| 2469 | 2427 | let interactive_bounds = interactive_bounds.clone(); |
| 2428 | + let mut delta = ScrollDelta::default(); | |
| 2470 | 2429 | |
| 2471 | 2430 | move |event: &ScrollWheelEvent, phase, cx| { |
| 2472 | 2431 | if phase == DispatchPhase::Bubble |
| 2473 | 2432 | && interactive_bounds.visibly_contains(&event.position, cx) |
| 2474 | 2433 | { |
| 2434 | + delta = delta.coalesce(event.delta); | |
| 2475 | 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 | 2491 | cx.on_mouse_event({ |
| 2483 | 2492 | let position_map = layout.position_map.clone(); |
| @@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; · 15 unmodified lines | ||
| 16 | 16 | use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; |
| 17 | 17 | use settings::Settings; |
| 18 | 18 | use std::{ops::Range, sync::Arc, time::Duration}; |
| 19 | −use ui::{StyledExt, Tooltip}; | |
| 19 | +use ui::{prelude::*, Tooltip}; | |
| 20 | 20 | use util::TryFutureExt; |
| 21 | 21 | use workspace::Workspace; |
| 22 | 22 | |
| @@ -514,6 +514,8 @@ impl DiagnosticPopover { · 491 unmodified lines | ||
| 514 | 514 | None => self.local_diagnostic.diagnostic.message.clone(), |
| 515 | 515 | }; |
| 516 | 516 | |
| 517 | + let status_colors = cx.theme().status(); | |
| 518 | + | |
| 517 | 519 | struct DiagnosticColors { |
| 518 | 520 | pub background: Hsla, |
| 519 | 521 | pub border: Hsla, |
| @@ -521,24 +523,24 @@ impl DiagnosticPopover { · 1 unmodified line | ||
| 521 | 523 | |
| 522 | 524 | let diagnostic_colors = match self.local_diagnostic.diagnostic.severity { |
| 523 | 525 | DiagnosticSeverity::ERROR => DiagnosticColors { |
| 524 | − background: style.status.error_background, | |
| 525 | − border: style.status.error_border, | |
| 526 | + background: status_colors.error_background, | |
| 527 | + border: status_colors.error_border, | |
| 526 | 528 | }, |
| 527 | 529 | DiagnosticSeverity::WARNING => DiagnosticColors { |
| 528 | − background: style.status.warning_background, | |
| 529 | − border: style.status.warning_border, | |
| 530 | + background: status_colors.warning_background, | |
| 531 | + border: status_colors.warning_border, | |
| 530 | 532 | }, |
| 531 | 533 | DiagnosticSeverity::INFORMATION => DiagnosticColors { |
| 532 | − background: style.status.info_background, | |
| 533 | − border: style.status.info_border, | |
| 534 | + background: status_colors.info_background, | |
| 535 | + border: status_colors.info_border, | |
| 534 | 536 | }, |
| 535 | 537 | DiagnosticSeverity::HINT => DiagnosticColors { |
| 536 | − background: style.status.hint_background, | |
| 537 | − border: style.status.hint_border, | |
| 538 | + background: status_colors.hint_background, | |
| 539 | + border: status_colors.hint_border, | |
| 538 | 540 | }, |
| 539 | 541 | _ => DiagnosticColors { |
| 540 | − background: style.status.ignored_background, | |
| 541 | − border: style.status.ignored_border, | |
| 542 | + background: status_colors.ignored_background, | |
| 543 | + border: status_colors.ignored_border, | |
| 542 | 544 | }, |
| 543 | 545 | }; |
| 544 | 546 | |
| @@ -384,10 +384,12 @@ impl Editor { · 383 unmodified lines | ||
| 384 | 384 | ) { |
| 385 | 385 | hide_hover(self, cx); |
| 386 | 386 | let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); |
| 387 | − let top_row = scroll_anchor | |
| 388 | − .anchor | |
| 389 | − .to_point(&self.buffer().read(cx).snapshot(cx)) | |
| 390 | − .row; | |
| 387 | + let snapshot = &self.buffer().read(cx).snapshot(cx); | |
| 388 | + if !scroll_anchor.anchor.is_valid(snapshot) { | |
| 389 | + log::warn!("Invalid scroll anchor: {:?}", scroll_anchor); | |
| 390 | + return; | |
| 391 | + } | |
| 392 | + let top_row = scroll_anchor.anchor.to_point(snapshot).row; | |
| 391 | 393 | self.scroll_manager |
| 392 | 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 | 60 | #[allow(dead_code)] |
| 61 | 61 | #[cfg(any(test, feature = "test-support"))] |
| 62 | 62 | pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor { |
| 63 | − // todo!() | |
| 64 | − Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx) | |
| 63 | + Editor::new(EditorMode::Full, buffer, None, cx) | |
| 65 | 64 | } |
| 66 | 65 | |
| 67 | 66 | pub(crate) fn build_editor_with_project( |
| @@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( · 1 unmodified line | ||
| 69 | 68 | buffer: Model<MultiBuffer>, |
| 70 | 69 | cx: &mut ViewContext<Editor>, |
| 71 | 70 | ) -> Editor { |
| 72 | − // todo!() | |
| 73 | − Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx) | |
| 71 | + Editor::new(EditorMode::Full, buffer, Some(project), cx) | |
| 74 | 72 | } |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | use gpui::{Render, ViewContext, WeakView}; |
| 2 | −use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; | |
| 2 | +use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip}; | |
| 3 | 3 | use workspace::{item::ItemHandle, StatusItemView, Workspace}; |
| 4 | 4 | |
| 5 | 5 | use crate::{feedback_modal::FeedbackModal, GiveFeedback}; |
| @@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { · 21 unmodified lines | ||
| 27 | 27 | }) |
| 28 | 28 | }) |
| 29 | 29 | .is_some(); |
| 30 | − IconButton::new("give-feedback", Icon::Envelope) | |
| 30 | + IconButton::new("give-feedback", IconName::Envelope) | |
| 31 | 31 | .style(ui::ButtonStyle::Subtle) |
| 32 | 32 | .icon_size(IconSize::Small) |
| 33 | 33 | .selected(is_open) |
| @@ -488,7 +488,7 @@ impl Render for FeedbackModal { · 487 unmodified lines | ||
| 488 | 488 | .child( |
| 489 | 489 | Button::new("community_repository", "Community Repository") |
| 490 | 490 | .style(ButtonStyle::Transparent) |
| 491 | − .icon(Icon::ExternalLink) | |
| 491 | + .icon(IconName::ExternalLink) | |
| 492 | 492 | .icon_position(IconPosition::End) |
| 493 | 493 | .icon_size(IconSize::Small) |
| 494 | 494 | .on_click(open_community_repo), |
| @@ -50,7 +50,7 @@ impl Render for Menu { · 49 unmodified lines | ||
| 50 | 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 | 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 | 114 | pub(crate) fn load_actions(&mut self) { |
| 115 | 115 | for builder in __GPUI_ACTIONS { |
| 116 | 116 | let action = builder(); |
| 117 | − //todo(remove) | |
| 118 | − let name: SharedString = action.name.into(); | |
| 119 | − self.builders_by_name.insert(name.clone(), action.build); | |
| 120 | − self.names_by_type_id.insert(action.type_id, name.clone()); | |
| 121 | − self.all_names.push(name); | |
| 117 | + self.insert_action(action); | |
| 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 | 137 | /// Construct an action based on its name and optional JSON parameters sourced from the keymap. |
| 126 | 138 | pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> { |
| 127 | 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 | 218 | fn debug_name() -> &'static str |
| 208 | 219 | where |
| 209 | 220 | Self: ::std::marker::Sized |
| @@ -45,11 +45,13 @@ use util::{ · 44 unmodified lines | ||
| 45 | 45 | |
| 46 | 46 | /// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows. |
| 47 | 47 | /// Strongly consider removing after stabilization. |
| 48 | +#[doc(hidden)] | |
| 48 | 49 | pub struct AppCell { |
| 49 | 50 | app: RefCell<AppContext>, |
| 50 | 51 | } |
| 51 | 52 | |
| 52 | 53 | impl AppCell { |
| 54 | + #[doc(hidden)] | |
| 53 | 55 | #[track_caller] |
| 54 | 56 | pub fn borrow(&self) -> AppRef { |
| 55 | 57 | if option_env!("TRACK_THREAD_BORROWS").is_some() { |
| @@ -59,6 +61,7 @@ impl AppCell { · 3 unmodified lines | ||
| 59 | 61 | AppRef(self.app.borrow()) |
| 60 | 62 | } |
| 61 | 63 | |
| 64 | + #[doc(hidden)] | |
| 62 | 65 | #[track_caller] |
| 63 | 66 | pub fn borrow_mut(&self) -> AppRefMut { |
| 64 | 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 | 76 | #[derive(Deref, DerefMut)] |
| 73 | 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 | 89 | #[derive(Deref, DerefMut)] |
| 85 | 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 | 103 | pub struct App(Rc<AppCell>); |
| 97 | 104 | |
| 98 | 105 | /// Represents an application before it is fully launched. Once your app is |
| @@ -136,6 +143,8 @@ impl App { · 37 unmodified lines | ||
| 136 | 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 | 148 | pub fn on_reopen<F>(&self, mut callback: F) -> &Self |
| 140 | 149 | where |
| 141 | 150 | F: 'static + FnMut(&mut AppContext), |
| @@ -149,18 +158,22 @@ impl App { · 7 unmodified lines | ||
| 149 | 158 | self |
| 150 | 159 | } |
| 151 | 160 | |
| 161 | + /// Returns metadata associated with the application | |
| 152 | 162 | pub fn metadata(&self) -> AppMetadata { |
| 153 | 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 | 167 | pub fn background_executor(&self) -> BackgroundExecutor { |
| 157 | 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 | 172 | pub fn foreground_executor(&self) -> ForegroundExecutor { |
| 161 | 173 | self.0.borrow().foreground_executor.clone() |
| 162 | 174 | } |
| 163 | 175 | |
| 176 | + /// Returns a reference to the [`TextSystem`] associated with this app. | |
| 164 | 177 | pub fn text_system(&self) -> Arc<TextSystem> { |
| 165 | 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 | 187 | type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>; |
| 175 | 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 | 190 | pub struct AppContext { |
| 184 | 191 | pub(crate) this: Weak<AppCell>, |
| 185 | 192 | pub(crate) platform: Rc<dyn Platform>, |
| @@ -292,7 +299,7 @@ impl AppContext { · 106 unmodified lines | ||
| 292 | 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 | 303 | /// will be given 100ms to complete before exiting. |
| 297 | 304 | pub fn shutdown(&mut self) { |
| 298 | 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 | 325 | pub fn quit(&mut self) { |
| 318 | 326 | self.platform.quit(); |
| 319 | 327 | } |
| 320 | 328 | |
| 329 | + /// Get metadata about the app and platform. | |
| 321 | 330 | pub fn app_metadata(&self) -> AppMetadata { |
| 322 | 331 | self.app_metadata.clone() |
| 323 | 332 | } |
| @@ -340,6 +349,7 @@ impl AppContext { · 16 unmodified lines | ||
| 340 | 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 | 353 | pub fn observe<W, E>( |
| 344 | 354 | &mut self, |
| 345 | 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 | 369 | &mut self, |
| 360 | 370 | entity: &E, |
| 361 | 371 | mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, |
| @@ -380,15 +390,17 @@ impl AppContext { · 18 unmodified lines | ||
| 380 | 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 | 396 | &mut self, |
| 385 | 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 | 399 | ) -> Subscription |
| 388 | 400 | where |
| 389 | − T: 'static + EventEmitter<Evt>, | |
| 401 | + T: 'static + EventEmitter<Event>, | |
| 390 | 402 | E: Entity<T>, |
| 391 | − Evt: 'static, | |
| 403 | + Event: 'static, | |
| 392 | 404 | { |
| 393 | 405 | self.subscribe_internal(entity, move |entity, event, cx| { |
| 394 | 406 | on_event(entity, event, cx); |
| @@ -426,6 +438,9 @@ impl AppContext { · 31 unmodified lines | ||
| 426 | 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 | 444 | pub fn windows(&self) -> Vec<AnyWindowHandle> { |
| 430 | 445 | self.windows |
| 431 | 446 | .values() |
| @@ -565,7 +580,7 @@ impl AppContext { · 133 unmodified lines | ||
| 565 | 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 | 584 | /// such as notifying observers, emitting events, etc. Effects can themselves |
| 570 | 585 | /// cause effects, so we continue looping until all effects are processed. |
| 571 | 586 | fn flush_effects(&mut self) { |
| @@ -82,6 +82,7 @@ impl Context for AsyncAppContext { · 81 unmodified lines | ||
| 82 | 82 | } |
| 83 | 83 | |
| 84 | 84 | impl AsyncAppContext { |
| 85 | + /// Schedules all windows in the application to be redrawn. | |
| 85 | 86 | pub fn refresh(&mut self) -> Result<()> { |
| 86 | 87 | let app = self |
| 87 | 88 | .app |
| @@ -92,14 +93,17 @@ impl AsyncAppContext { · 4 unmodified lines | ||
| 92 | 93 | Ok(()) |
| 93 | 94 | } |
| 94 | 95 | |
| 96 | + /// Get an executor which can be used to spawn futures in the background. | |
| 95 | 97 | pub fn background_executor(&self) -> &BackgroundExecutor { |
| 96 | 98 | &self.background_executor |
| 97 | 99 | } |
| 98 | 100 | |
| 101 | + /// Get an executor which can be used to spawn futures in the foreground. | |
| 99 | 102 | pub fn foreground_executor(&self) -> &ForegroundExecutor { |
| 100 | 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 | 107 | pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> { |
| 104 | 108 | let app = self |
| 105 | 109 | .app |
| @@ -109,6 +113,7 @@ impl AsyncAppContext { · 3 unmodified lines | ||
| 109 | 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 | 117 | pub fn open_window<V>( |
| 113 | 118 | &self, |
| 114 | 119 | options: crate::WindowOptions, |
| @@ -125,6 +130,7 @@ impl AsyncAppContext { · 10 unmodified lines | ||
| 125 | 130 | Ok(lock.open_window(options, build_root_view)) |
| 126 | 131 | } |
| 127 | 132 | |
| 133 | + /// Schedule a future to be polled in the background. | |
| 128 | 134 | pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R> |
| 129 | 135 | where |
| 130 | 136 | Fut: Future<Output = R> + 'static, |
| @@ -19,7 +19,10 @@ use std::{ · 18 unmodified lines | ||
| 19 | 19 | #[cfg(any(test, feature = "test-support"))] |
| 20 | 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 | 27 | impl EntityId { |
| 25 | 28 | pub fn as_u64(self) -> u64 { |
| @@ -1,3 +1,5 @@ | ||
| 1 | +#![deny(missing_docs)] | |
| 2 | + | |
| 1 | 3 | use crate::{ |
| 2 | 4 | div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, |
| 3 | 5 | BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, |
| @@ -9,13 +11,19 @@ use anyhow::{anyhow, bail}; · 5 unmodified lines | ||
| 9 | 11 | use futures::{Stream, StreamExt}; |
| 10 | 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 | 16 | #[derive(Clone)] |
| 13 | 17 | pub struct TestAppContext { |
| 18 | + #[doc(hidden)] | |
| 14 | 19 | pub app: Rc<AppCell>, |
| 20 | + #[doc(hidden)] | |
| 15 | 21 | pub background_executor: BackgroundExecutor, |
| 22 | + #[doc(hidden)] | |
| 16 | 23 | pub foreground_executor: ForegroundExecutor, |
| 24 | + #[doc(hidden)] | |
| 17 | 25 | pub dispatcher: TestDispatcher, |
| 18 | − pub test_platform: Rc<TestPlatform>, | |
| 26 | + test_platform: Rc<TestPlatform>, | |
| 19 | 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 | 86 | impl TestAppContext { |
| 87 | + /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. | |
| 79 | 88 | pub fn new(dispatcher: TestDispatcher) -> Self { |
| 80 | 89 | let arc_dispatcher = Arc::new(dispatcher.clone()); |
| 81 | 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 | 108 | pub fn new_app(&self) -> TestAppContext { |
| 99 | 109 | Self::new(self.dispatcher.clone()) |
| 100 | 110 | } |
| 101 | 111 | |
| 112 | + /// Simulates quitting the app. | |
| 102 | 113 | pub fn quit(&self) { |
| 103 | 114 | self.app.borrow_mut().shutdown(); |
| 104 | 115 | } |
| 105 | 116 | |
| 117 | + /// Schedules all windows to be redrawn on the next effect cycle. | |
| 106 | 118 | pub fn refresh(&mut self) -> Result<()> { |
| 107 | 119 | let mut app = self.app.borrow_mut(); |
| 108 | 120 | app.refresh(); |
| 109 | 121 | Ok(()) |
| 110 | 122 | } |
| 111 | 123 | |
| 124 | + /// Returns an executor (for running tasks in the background) | |
| 112 | 125 | pub fn executor(&self) -> BackgroundExecutor { |
| 113 | 126 | self.background_executor.clone() |
| 114 | 127 | } |
| 115 | 128 | |
| 129 | + /// Returns an executor (for running tasks on the main thread) | |
| 116 | 130 | pub fn foreground_executor(&self) -> &ForegroundExecutor { |
| 117 | 131 | &self.foreground_executor |
| 118 | 132 | } |
| 119 | 133 | |
| 134 | + /// Gives you an `&mut AppContext` for the duration of the closure | |
| 120 | 135 | pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { |
| 121 | 136 | let mut cx = self.app.borrow_mut(); |
| 122 | 137 | cx.update(f) |
| 123 | 138 | } |
| 124 | 139 | |
| 140 | + /// Gives you an `&AppContext` for the duration of the closure | |
| 125 | 141 | pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R { |
| 126 | 142 | let cx = self.app.borrow(); |
| 127 | 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 | 148 | pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V> |
| 131 | 149 | where |
| 132 | 150 | F: FnOnce(&mut ViewContext<V>) -> V, |
| @@ -136,12 +154,16 @@ impl TestAppContext { · 3 unmodified lines | ||
| 136 | 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 | 158 | pub fn add_empty_window(&mut self) -> AnyWindowHandle { |
| 140 | 159 | let mut cx = self.app.borrow_mut(); |
| 141 | 160 | cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) |
| 142 | 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 | 167 | pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext) |
| 146 | 168 | where |
| 147 | 169 | F: FnOnce(&mut ViewContext<V>) -> V, |
| @@ -156,18 +178,23 @@ impl TestAppContext { · 8 unmodified lines | ||
| 156 | 178 | (view, Box::leak(cx)) |
| 157 | 179 | } |
| 158 | 180 | |
| 181 | + /// returns the TextSystem | |
| 159 | 182 | pub fn text_system(&self) -> &Arc<TextSystem> { |
| 160 | 183 | &self.text_system |
| 161 | 184 | } |
| 162 | 185 | |
| 186 | + /// Simulates writing to the platform clipboard | |
| 163 | 187 | pub fn write_to_clipboard(&self, item: ClipboardItem) { |
| 164 | 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 | 193 | pub fn read_from_clipboard(&self) -> Option<ClipboardItem> { |
| 168 | 194 | self.test_platform.read_from_clipboard() |
| 169 | 195 | } |
| 170 | 196 | |
| 197 | + /// Simulates choosing a File in the platform's "Open" dialog. | |
| 171 | 198 | pub fn simulate_new_path_selection( |
| 172 | 199 | &self, |
| 173 | 200 | select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>, |
| @@ -175,22 +202,27 @@ impl TestAppContext { · 1 unmodified line | ||
| 175 | 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 | 206 | pub fn simulate_prompt_answer(&self, button_ix: usize) { |
| 179 | 207 | self.test_platform.simulate_prompt_answer(button_ix); |
| 180 | 208 | } |
| 181 | 209 | |
| 210 | + /// Returns true if there's an alert dialog open. | |
| 182 | 211 | pub fn has_pending_prompt(&self) -> bool { |
| 183 | 212 | self.test_platform.has_pending_prompt() |
| 184 | 213 | } |
| 185 | 214 | |
| 215 | + /// Simulates the user resizing the window to the new size. | |
| 186 | 216 | pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) { |
| 187 | 217 | self.test_window(window_handle).simulate_resize(size); |
| 188 | 218 | } |
| 189 | 219 | |
| 220 | + /// Returns all windows open in the test. | |
| 190 | 221 | pub fn windows(&self) -> Vec<AnyWindowHandle> { |
| 191 | 222 | self.app.borrow().windows().clone() |
| 192 | 223 | } |
| 193 | 224 | |
| 225 | + /// Run the given task on the main thread. | |
| 194 | 226 | pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R> |
| 195 | 227 | where |
| 196 | 228 | Fut: Future<Output = R> + 'static, |
| @@ -199,16 +231,20 @@ impl TestAppContext { · 2 unmodified lines | ||
| 199 | 231 | self.foreground_executor.spawn(f(self.to_async())) |
| 200 | 232 | } |
| 201 | 233 | |
| 234 | + /// true if the given global is defined | |
| 202 | 235 | pub fn has_global<G: 'static>(&self) -> bool { |
| 203 | 236 | let app = self.app.borrow(); |
| 204 | 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 | 242 | pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { |
| 208 | 243 | let app = self.app.borrow(); |
| 209 | 244 | read(app.global(), &app) |
| 210 | 245 | } |
| 211 | 246 | |
| 247 | + /// runs the given closure with a reference to the global (if set) | |
| 212 | 248 | pub fn try_read_global<G: 'static, R>( |
| 213 | 249 | &self, |
| 214 | 250 | read: impl FnOnce(&G, &AppContext) -> R, |
| @@ -217,11 +253,13 @@ impl TestAppContext { · 2 unmodified lines | ||
| 217 | 253 | Some(read(lock.try_global()?, &lock)) |
| 218 | 254 | } |
| 219 | 255 | |
| 256 | + /// sets the global in this context. | |
| 220 | 257 | pub fn set_global<G: 'static>(&mut self, global: G) { |
| 221 | 258 | let mut lock = self.app.borrow_mut(); |
| 222 | 259 | lock.set_global(global); |
| 223 | 260 | } |
| 224 | 261 | |
| 262 | + /// updates the global in this context. (panics if `has_global` would return false) | |
| 225 | 263 | pub fn update_global<G: 'static, R>( |
| 226 | 264 | &mut self, |
| 227 | 265 | update: impl FnOnce(&mut G, &mut AppContext) -> R, |
| @@ -230,6 +268,8 @@ impl TestAppContext { · 2 unmodified lines | ||
| 230 | 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 | 273 | pub fn to_async(&self) -> AsyncAppContext { |
| 234 | 274 | AsyncAppContext { |
| 235 | 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 | 282 | pub fn run_until_parked(&mut self) { |
| 242 | 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 | 287 | pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A) |
| 246 | 288 | where |
| 247 | 289 | A: Action, |
| @@ -255,7 +297,8 @@ impl TestAppContext { · 7 unmodified lines | ||
| 255 | 297 | |
| 256 | 298 | /// simulate_keystrokes takes a space-separated list of keys to type. |
| 257 | 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 | 302 | pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { |
| 260 | 303 | for keystroke in keystrokes |
| 261 | 304 | .split(" ") |
| @@ -270,7 +313,8 @@ impl TestAppContext { · 8 unmodified lines | ||
| 270 | 313 | |
| 271 | 314 | /// simulate_input takes a string of text to type. |
| 272 | 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 | 318 | pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { |
| 275 | 319 | for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { |
| 276 | 320 | self.dispatch_keystroke(window, keystroke.into(), false); |
| @@ -279,6 +323,7 @@ impl TestAppContext { · 2 unmodified lines | ||
| 279 | 323 | self.background_executor.run_until_parked() |
| 280 | 324 | } |
| 281 | 325 | |
| 326 | + /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`) | |
| 282 | 327 | pub fn dispatch_keystroke( |
| 283 | 328 | &mut self, |
| 284 | 329 | window: AnyWindowHandle, |
| @@ -289,6 +334,7 @@ impl TestAppContext { · 4 unmodified lines | ||
| 289 | 334 | .simulate_keystroke(keystroke, is_held) |
| 290 | 335 | } |
| 291 | 336 | |
| 337 | + /// Returns the `TestWindow` backing the given handle. | |
| 292 | 338 | pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { |
| 293 | 339 | self.app |
| 294 | 340 | .borrow_mut() |
| @@ -303,6 +349,7 @@ impl TestAppContext { · 8 unmodified lines | ||
| 303 | 349 | .clone() |
| 304 | 350 | } |
| 305 | 351 | |
| 352 | + /// Returns a stream of notifications whenever the View or Model is updated. | |
| 306 | 353 | pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> { |
| 307 | 354 | let (tx, rx) = futures::channel::mpsc::unbounded(); |
| 308 | 355 | self.update(|cx| { |
| @@ -319,6 +366,7 @@ impl TestAppContext { · 10 unmodified lines | ||
| 319 | 366 | rx |
| 320 | 367 | } |
| 321 | 368 | |
| 369 | + /// Retuens a stream of events emitted by the given Model. | |
| 322 | 370 | pub fn events<Evt, T: 'static + EventEmitter<Evt>>( |
| 323 | 371 | &mut self, |
| 324 | 372 | entity: &Model<T>, |
| @@ -337,6 +385,8 @@ impl TestAppContext { · 12 unmodified lines | ||
| 337 | 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 | 390 | pub async fn condition<T: 'static>( |
| 341 | 391 | &mut self, |
| 342 | 392 | model: &Model<T>, |
| @@ -366,6 +416,7 @@ impl TestAppContext { · 23 unmodified lines | ||
| 366 | 416 | } |
| 367 | 417 | |
| 368 | 418 | impl<T: Send> Model<T> { |
| 419 | + /// Block until the next event is emitted by the model, then return it. | |
| 369 | 420 | pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt |
| 370 | 421 | where |
| 371 | 422 | Evt: Send + Clone + 'static, |
| @@ -395,6 +446,7 @@ impl<T: Send> Model<T> { · 23 unmodified lines | ||
| 395 | 446 | } |
| 396 | 447 | |
| 397 | 448 | impl<V: 'static> View<V> { |
| 449 | + /// Returns a future that resolves when the view is next updated. | |
| 398 | 450 | pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> { |
| 399 | 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 | 475 | impl<V> View<V> { |
| 476 | + /// Returns a future that resolves when the condition becomes true. | |
| 424 | 477 | pub fn condition<Evt>( |
| 425 | 478 | &self, |
| 426 | 479 | cx: &TestAppContext, |
| @@ -471,12 +524,11 @@ impl<V> View<V> { · 44 unmodified lines | ||
| 471 | 524 | } |
| 472 | 525 | } |
| 473 | 526 | |
| 474 | − // todo!(start_waiting) | |
| 475 | − // cx.borrow().foreground_executor().start_waiting(); | |
| 527 | + cx.borrow().background_executor().start_waiting(); | |
| 476 | 528 | rx.recv() |
| 477 | 529 | .await |
| 478 | 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 | 534 | .await |
| @@ -488,6 +540,8 @@ impl<V> View<V> { · 5 unmodified lines | ||
| 488 | 540 | |
| 489 | 541 | use derive_more::{Deref, DerefMut}; |
| 490 | 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 | 545 | pub struct VisualTestContext { |
| 492 | 546 | #[deref] |
| 493 | 547 | #[deref_mut] |
| @@ -496,10 +550,14 @@ pub struct VisualTestContext { · 2 unmodified lines | ||
| 496 | 550 | } |
| 497 | 551 | |
| 498 | 552 | impl<'a> VisualTestContext { |
| 553 | + /// Provides the `WindowContext` for the duration of the closure. | |
| 499 | 554 | pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { |
| 500 | 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 | 561 | pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self { |
| 504 | 562 | Self { |
| 505 | 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 | 569 | pub fn run_until_parked(&self) { |
| 511 | 570 | self.cx.background_executor.run_until_parked(); |
| 512 | 571 | } |
| 513 | 572 | |
| 573 | + /// Dispatch the action to the currently focused node. | |
| 514 | 574 | pub fn dispatch_action<A>(&mut self, action: A) |
| 515 | 575 | where |
| 516 | 576 | A: Action, |
| @@ -518,24 +578,32 @@ impl<'a> VisualTestContext { · 1 unmodified line | ||
| 518 | 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 | 582 | pub fn window_title(&mut self) -> Option<String> { |
| 522 | 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 | 588 | pub fn simulate_keystrokes(&mut self, keystrokes: &str) { |
| 526 | 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 | 594 | pub fn simulate_input(&mut self, input: &str) { |
| 530 | 595 | self.cx.simulate_input(self.window, input) |
| 531 | 596 | } |
| 532 | 597 | |
| 598 | + /// Simulates the user blurring the window. | |
| 533 | 599 | pub fn deactivate_window(&mut self) { |
| 534 | 600 | if Some(self.window) == self.test_platform.active_window() { |
| 535 | 601 | self.test_platform.set_active_window(None) |
| 536 | 602 | } |
| 537 | 603 | self.background_executor.run_until_parked(); |
| 538 | 604 | } |
| 605 | + | |
| 606 | + /// Simulates the user closing the window. | |
| 539 | 607 | /// Returns true if the window was closed. |
| 540 | 608 | pub fn simulate_close(&mut self) -> bool { |
| 541 | 609 | let handler = self |
| @@ -672,6 +740,7 @@ impl VisualContext for VisualTestContext { · 130 unmodified lines | ||
| 672 | 740 | } |
| 673 | 741 | |
| 674 | 742 | impl AnyWindowHandle { |
| 743 | + /// Creates the given view in this window. | |
| 675 | 744 | pub fn build_view<V: Render + 'static>( |
| 676 | 745 | &self, |
| 677 | 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 | 754 | pub struct EmptyView {} |
| 685 | 755 | |
| 686 | 756 | impl Render for EmptyView { |
| @@ -31,14 +31,14 @@ pub trait IntoElement: Sized { · 30 unmodified lines | ||
| 31 | 31 | /// The specific type of element into which the implementing type is converted. |
| 32 | 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 | 35 | /// If present, the resulting element's state will be carried across frames. |
| 36 | 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 | 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 | 42 | fn into_any_element(self) -> AnyElement { |
| 43 | 43 | self.into_element().into_any() |
| 44 | 44 | } |
| @@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { · 70 unmodified lines | ||
| 115 | 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 | 119 | /// It is used to allow views to be expressed in terms of abstract data. |
| 120 | 120 | pub trait RenderOnce: 'static { |
| 121 | 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 | 228 | impl<E: Element> DrawableElement<E> { |
| 229 | 229 | fn new(element: E) -> Self { |
| 230 | 230 | DrawableElement { |
| @@ -2,7 +2,7 @@ use std::sync::Arc; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | use crate::{ |
| 4 | 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 | 6 | StyleRefinement, Styled, WindowContext, |
| 7 | 7 | }; |
| 8 | 8 | use futures::FutureExt; |
| @@ -12,13 +12,13 @@ use util::ResultExt; · 3 unmodified lines | ||
| 12 | 12 | #[derive(Clone, Debug)] |
| 13 | 13 | pub enum ImageSource { |
| 14 | 14 | /// Image content will be loaded from provided URI at render time. |
| 15 | − Uri(SharedString), | |
| 15 | + Uri(SharedUrl), | |
| 16 | 16 | Data(Arc<ImageData>), |
| 17 | 17 | Surface(CVImageBuffer), |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | −impl From<SharedString> for ImageSource { | |
| 21 | − fn from(value: SharedString) -> Self { | |
| 20 | +impl From<SharedUrl> for ImageSource { | |
| 21 | + fn from(value: SharedUrl) -> Self { | |
| 22 | 22 | Self::Uri(value) |
| 23 | 23 | } |
| 24 | 24 | } |
| @@ -14,8 +14,8 @@ pub struct Overlay { · 13 unmodified lines | ||
| 14 | 14 | children: SmallVec<[AnyElement; 2]>, |
| 15 | 15 | anchor_corner: AnchorCorner, |
| 16 | 16 | fit_mode: OverlayFitMode, |
| 17 | − // todo!(); | |
| 18 | 17 | anchor_position: Option<Point<Pixels>>, |
| 18 | + // todo!(); | |
| 19 | 19 | // position_mode: OverlayPositionMode, |
| 20 | 20 | } |
| 21 | 21 | |
| @@ -32,6 +32,12 @@ pub struct ForegroundExecutor { · 31 unmodified lines | ||
| 32 | 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 | 41 | #[must_use] |
| 36 | 42 | #[derive(Debug)] |
| 37 | 43 | pub enum Task<T> { |
| @@ -40,10 +46,12 @@ pub enum Task<T> { · 2 unmodified lines | ||
| 40 | 46 | } |
| 41 | 47 | |
| 42 | 48 | impl<T> Task<T> { |
| 49 | + /// Create a new task that will resolve with the value | |
| 43 | 50 | pub fn ready(val: T) -> Self { |
| 44 | 51 | Task::Ready(Some(val)) |
| 45 | 52 | } |
| 46 | 53 | |
| 54 | + /// Detaching a task runs it to completion in the background | |
| 47 | 55 | pub fn detach(self) { |
| 48 | 56 | match self { |
| 49 | 57 | Task::Ready(_) => {} |
| @@ -57,6 +65,8 @@ where · 7 unmodified lines | ||
| 57 | 65 | T: 'static, |
| 58 | 66 | E: 'static + Debug, |
| 59 | 67 | { |
| 68 | + /// Run the task to completion in the background and log any | |
| 69 | + /// errors that occur. | |
| 60 | 70 | #[track_caller] |
| 61 | 71 | pub fn detach_and_log_err(self, cx: &mut AppContext) { |
| 62 | 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 | 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 | 114 | impl BackgroundExecutor { |
| 101 | 115 | pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self { |
| 102 | 116 | Self { dispatcher } |
| @@ -135,6 +149,7 @@ impl BackgroundExecutor { · 32 unmodified lines | ||
| 135 | 149 | Task::Spawned(task) |
| 136 | 150 | } |
| 137 | 151 | |
| 152 | + /// Used by the test harness to run an async test in a syncronous fashion. | |
| 138 | 153 | #[cfg(any(test, feature = "test-support"))] |
| 139 | 154 | #[track_caller] |
| 140 | 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 | 165 | pub fn block<R>(&self, future: impl Future<Output = R>) -> R { |
| 149 | 166 | if let Ok(value) = self.block_internal(true, future, usize::MAX) { |
| 150 | 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 | 228 | pub fn block_with_timeout<R>( |
| 210 | 229 | &self, |
| 211 | 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 | 262 | pub async fn scoped<'scope, F>(&self, scheduler: F) |
| 242 | 263 | where |
| 243 | 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 | 280 | pub fn timer(&self, duration: Duration) -> Task<()> { |
| 257 | 281 | let (runnable, task) = async_task::spawn(async move {}, { |
| 258 | 282 | let dispatcher = self.dispatcher.clone(); |
| @@ -262,65 +286,81 @@ impl BackgroundExecutor { · 3 unmodified lines | ||
| 262 | 286 | Task::Spawned(task) |
| 263 | 287 | } |
| 264 | 288 | |
| 289 | + /// in tests, start_waiting lets you indicate which task is waiting (for debugging only) | |
| 265 | 290 | #[cfg(any(test, feature = "test-support"))] |
| 266 | 291 | pub fn start_waiting(&self) { |
| 267 | 292 | self.dispatcher.as_test().unwrap().start_waiting(); |
| 268 | 293 | } |
| 269 | 294 | |
| 295 | + /// in tests, removes the debugging data added by start_waiting | |
| 270 | 296 | #[cfg(any(test, feature = "test-support"))] |
| 271 | 297 | pub fn finish_waiting(&self) { |
| 272 | 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 | 302 | #[cfg(any(test, feature = "test-support"))] |
| 276 | 303 | pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { |
| 277 | 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 | 308 | #[cfg(any(test, feature = "test-support"))] |
| 281 | 309 | pub fn deprioritize(&self, task_label: TaskLabel) { |
| 282 | 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 | 314 | #[cfg(any(test, feature = "test-support"))] |
| 286 | 315 | pub fn advance_clock(&self, duration: Duration) { |
| 287 | 316 | self.dispatcher.as_test().unwrap().advance_clock(duration) |
| 288 | 317 | } |
| 289 | 318 | |
| 319 | + /// in tests, run one task. | |
| 290 | 320 | #[cfg(any(test, feature = "test-support"))] |
| 291 | 321 | pub fn tick(&self) -> bool { |
| 292 | 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 | 327 | #[cfg(any(test, feature = "test-support"))] |
| 296 | 328 | pub fn run_until_parked(&self) { |
| 297 | 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 | 335 | #[cfg(any(test, feature = "test-support"))] |
| 301 | 336 | pub fn allow_parking(&self) { |
| 302 | 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 | 341 | #[cfg(any(test, feature = "test-support"))] |
| 306 | 342 | pub fn rng(&self) -> StdRng { |
| 307 | 343 | self.dispatcher.as_test().unwrap().rng() |
| 308 | 344 | } |
| 309 | 345 | |
| 346 | + /// How many CPUs are available to the dispatcher | |
| 310 | 347 | pub fn num_cpus(&self) -> usize { |
| 311 | 348 | num_cpus::get() |
| 312 | 349 | } |
| 313 | 350 | |
| 351 | + /// Whether we're on the main thread. | |
| 314 | 352 | pub fn is_main_thread(&self) -> bool { |
| 315 | 353 | self.dispatcher.is_main_thread() |
| 316 | 354 | } |
| 317 | 355 | |
| 318 | 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 | 358 | pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) { |
| 320 | 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 | 364 | impl ForegroundExecutor { |
| 325 | 365 | pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self { |
| 326 | 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 | |
| 333 | − /// a future which will be run to completion on any available thread. | |
| 372 | + /// Enqueues the given Task to run on the main thread at some point in the future. | |
| 334 | 373 | pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R> |
| 335 | 374 | where |
| 336 | 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 | 393 | pub struct Scope<'a> { |
| 354 | 394 | executor: BackgroundExecutor, |
| 355 | 395 | futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>, |
| @@ -18,6 +18,7 @@ mod platform; · 17 unmodified lines | ||
| 18 | 18 | pub mod prelude; |
| 19 | 19 | mod scene; |
| 20 | 20 | mod shared_string; |
| 21 | +mod shared_url; | |
| 21 | 22 | mod style; |
| 22 | 23 | mod styled; |
| 23 | 24 | mod subscription; |
| @@ -67,6 +68,7 @@ pub use refineable::*; · 43 unmodified lines | ||
| 67 | 68 | pub use scene::*; |
| 68 | 69 | use seal::Sealed; |
| 69 | 70 | pub use shared_string::*; |
| 71 | +pub use shared_url::*; | |
| 70 | 72 | pub use smol::Timer; |
| 71 | 73 | pub use style::*; |
| 72 | 74 | pub use styled::*; |
| @@ -1,4 +1,4 @@ | ||
| 1 | −use crate::{ImageData, ImageId, SharedString}; | |
| 1 | +use crate::{ImageData, ImageId, SharedUrl}; | |
| 2 | 2 | use collections::HashMap; |
| 3 | 3 | use futures::{ |
| 4 | 4 | future::{BoxFuture, Shared}, |
| @@ -44,7 +44,7 @@ impl From<ImageError> for Error { · 39 unmodified lines | ||
| 44 | 44 | |
| 45 | 45 | pub struct ImageCache { |
| 46 | 46 | client: Arc<dyn HttpClient>, |
| 47 | − images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>, | |
| 47 | + images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>, | |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | 50 | type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>; |
| @@ -59,7 +59,7 @@ impl ImageCache { · 8 unmodified lines | ||
| 59 | 59 | |
| 60 | 60 | pub fn get( |
| 61 | 61 | &self, |
| 62 | − uri: impl Into<SharedString>, | |
| 62 | + uri: impl Into<SharedUrl>, | |
| 63 | 63 | ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> { |
| 64 | 64 | let uri = uri.into(); |
| 65 | 65 | let mut images = self.images.lock(); |
| @@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized { · 33 unmodified lines | ||
| 34 | 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 | 38 | /// with an instance during your element's paint. |
| 39 | 39 | pub struct ElementInputHandler<V> { |
| 40 | 40 | view: View<V>, |
| @@ -178,6 +178,20 @@ impl ScrollDelta { · 177 unmodified lines | ||
| 178 | 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 | 197 | #[derive(Clone, Debug, Default)] |
| @@ -192,8 +192,8 @@ impl DispatchTree { · 191 unmodified lines | ||
| 192 | 192 | keymap |
| 193 | 193 | .bindings_for_action(action) |
| 194 | 194 | .filter(|binding| { |
| 195 | − for i in 1..context_stack.len() { | |
| 196 | − let context = &context_stack[0..i]; | |
| 195 | + for i in 0..context_stack.len() { | |
| 196 | + let context = &context_stack[0..=i]; | |
| 197 | 197 | if keymap.binding_enabled(binding, context) { |
| 198 | 198 | return true; |
| 199 | 199 | } |
| @@ -283,3 +283,76 @@ impl DispatchTree { · 83 unmodified lines | ||
| 283 | 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 | 14 | unsafe impl Send for MacDisplay {} |
| 15 | 15 | |
| 16 | 16 | impl MacDisplay { |
| 17 | − /// Get the screen with the given [DisplayId]. | |
| 17 | + /// Get the screen with the given [`DisplayId`]. | |
| 18 | 18 | pub fn find_by_id(id: DisplayId) -> Option<Self> { |
| 19 | 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 | 23 | pub fn find_by_uuid(uuid: Uuid) -> Option<Self> { |
| 24 | 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 | 34 | fn as_any(&self) -> &dyn std::any::Any { |
| 35 | − todo!() | |
| 35 | + unimplemented!() | |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | 38 | fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> { |
| @@ -15,6 +15,7 @@ use std::{ · 14 unmodified lines | ||
| 15 | 15 | time::Duration, |
| 16 | 16 | }; |
| 17 | 17 | |
| 18 | +/// TestPlatform implements the Platform trait for use in tests. | |
| 18 | 19 | pub struct TestPlatform { |
| 19 | 20 | background_executor: BackgroundExecutor, |
| 20 | 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 | 107 | impl Platform for TestPlatform { |
| 108 | 108 | fn background_executor(&self) -> BackgroundExecutor { |
| 109 | 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 | 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 | |
| 43 | − /// to activate the `[Subscription]`. | |
| 42 | + /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter | |
| 43 | + /// to activate the [`Subscription`]. | |
| 44 | 44 | #[must_use] |
| 45 | 45 | pub fn insert( |
| 46 | 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 | 28 | use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; |
| 2 | 29 | use futures::StreamExt as _; |
| 3 | 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 | 99 | pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> { |
| 72 | 100 | let (tx, rx) = smol::channel::unbounded(); |
| 73 | 101 | let _subscription = cx.update(|cx| { |
| @@ -1,3 +1,5 @@ | ||
| 1 | +#![deny(missing_docs)] | |
| 2 | + | |
| 1 | 3 | use crate::{ |
| 2 | 4 | px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, |
| 3 | 5 | AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, |
| @@ -85,10 +87,12 @@ pub enum DispatchPhase { · 81 unmodified lines | ||
| 85 | 87 | } |
| 86 | 88 | |
| 87 | 89 | impl DispatchPhase { |
| 90 | + /// Returns true if this represents the "bubble" phase. | |
| 88 | 91 | pub fn bubble(self) -> bool { |
| 89 | 92 | self == DispatchPhase::Bubble |
| 90 | 93 | } |
| 91 | 94 | |
| 95 | + /// Returns true if this represents the "capture" phase. | |
| 92 | 96 | pub fn capture(self) -> bool { |
| 93 | 97 | self == DispatchPhase::Capture |
| 94 | 98 | } |
| @@ -103,7 +107,10 @@ struct FocusEvent { · 8 unmodified lines | ||
| 103 | 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 | 115 | thread_local! { |
| 109 | 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 | 238 | /// FocusableView allows users of your view to easily |
| 232 | 239 | /// focus it (using cx.focus_view(view)) |
| 233 | 240 | pub trait FocusableView: 'static + Render { |
| 241 | + /// Returns the focus handle associated with this view. | |
| 234 | 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 | 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 | 252 | pub struct DismissEvent; |
| 244 | 253 | |
| 245 | 254 | // Holds the state for a specific window. |
| 255 | +#[doc(hidden)] | |
| 246 | 256 | pub struct Window { |
| 247 | 257 | pub(crate) handle: AnyWindowHandle, |
| 248 | 258 | pub(crate) removed: bool, |
| @@ -434,6 +444,7 @@ impl Window { · 185 unmodified lines | ||
| 434 | 444 | #[derive(Clone, Debug, Default, PartialEq, Eq)] |
| 435 | 445 | #[repr(C)] |
| 436 | 446 | pub struct ContentMask<P: Clone + Default + Debug> { |
| 447 | + /// The bounds | |
| 437 | 448 | pub bounds: Bounds<P>, |
| 438 | 449 | } |
| 439 | 450 | |
| @@ -453,8 +464,8 @@ impl ContentMask<Pixels> { · 13 unmodified lines | ||
| 453 | 464 | } |
| 454 | 465 | |
| 455 | 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 | |
| 457 | −/// an `AppContext` and call any `AppContext` methods. | |
| 467 | +/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes | |
| 468 | +/// an [`AppContext`] and call any [`AppContext`] methods. | |
| 458 | 469 | pub struct WindowContext<'a> { |
| 459 | 470 | pub(crate) app: &'a mut AppContext, |
| 460 | 471 | pub(crate) window: &'a mut Window, |
| @@ -482,20 +493,20 @@ impl<'a> WindowContext<'a> { · 21 unmodified lines | ||
| 482 | 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 | 497 | /// for elements rendered within this window. |
| 487 | 498 | pub fn focus_handle(&mut self) -> FocusHandle { |
| 488 | 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 | 503 | pub fn focused(&self) -> Option<FocusHandle> { |
| 493 | 504 | self.window |
| 494 | 505 | .focus |
| 495 | 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 | 510 | pub fn focus(&mut self, handle: &FocusHandle) { |
| 500 | 511 | if !self.window.focus_enabled || self.window.focus == Some(handle.id) { |
| 501 | 512 | return; |
| @@ -525,11 +536,13 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines | ||
| 525 | 536 | self.notify(); |
| 526 | 537 | } |
| 527 | 538 | |
| 539 | + /// Blur the window and don't allow anything in it to be focused again. | |
| 528 | 540 | pub fn disable_focus(&mut self) { |
| 529 | 541 | self.blur(); |
| 530 | 542 | self.window.focus_enabled = false; |
| 531 | 543 | } |
| 532 | 544 | |
| 545 | + /// Dispatch the given action on the currently focused element. | |
| 533 | 546 | pub fn dispatch_action(&mut self, action: Box<dyn Action>) { |
| 534 | 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 | 610 | pub fn subscribe<Emitter, E, Evt>( |
| 595 | 611 | &mut self, |
| 596 | 612 | entity: &E, |
| @@ -754,6 +770,9 @@ impl<'a> WindowContext<'a> { · 157 unmodified lines | ||
| 754 | 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 | 776 | pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) { |
| 758 | 777 | let mut layout_engine = self.window.layout_engine.take().unwrap(); |
| 759 | 778 | layout_engine.compute_layout(layout_id, available_space, self); |
| @@ -788,30 +807,37 @@ impl<'a> WindowContext<'a> { · 28 unmodified lines | ||
| 788 | 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 | 811 | pub fn window_bounds(&self) -> WindowBounds { |
| 792 | 812 | self.window.bounds |
| 793 | 813 | } |
| 794 | 814 | |
| 815 | + /// Returns the size of the drawable area within the window. | |
| 795 | 816 | pub fn viewport_size(&self) -> Size<Pixels> { |
| 796 | 817 | self.window.viewport_size |
| 797 | 818 | } |
| 798 | 819 | |
| 820 | + /// Returns whether this window is focused by the operating system (receiving key events). | |
| 799 | 821 | pub fn is_window_active(&self) -> bool { |
| 800 | 822 | self.window.active |
| 801 | 823 | } |
| 802 | 824 | |
| 825 | + /// Toggle zoom on the window. | |
| 803 | 826 | pub fn zoom_window(&self) { |
| 804 | 827 | self.window.platform_window.zoom(); |
| 805 | 828 | } |
| 806 | 829 | |
| 830 | + /// Update the window's title at the platform level. | |
| 807 | 831 | pub fn set_window_title(&mut self, title: &str) { |
| 808 | 832 | self.window.platform_window.set_title(title); |
| 809 | 833 | } |
| 810 | 834 | |
| 835 | + /// Mark the window as dirty at the platform level. | |
| 811 | 836 | pub fn set_window_edited(&mut self, edited: bool) { |
| 812 | 837 | self.window.platform_window.set_edited(edited); |
| 813 | 838 | } |
| 814 | 839 | |
| 840 | + /// Determine the display on which the window is visible. | |
| 815 | 841 | pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> { |
| 816 | 842 | self.platform |
| 817 | 843 | .displays() |
| @@ -819,6 +845,7 @@ impl<'a> WindowContext<'a> { · 1 unmodified line | ||
| 819 | 845 | .find(|display| display.id() == self.window.display_id) |
| 820 | 846 | } |
| 821 | 847 | |
| 848 | + /// Show the platform character palette. | |
| 822 | 849 | pub fn show_character_palette(&self) { |
| 823 | 850 | self.window.platform_window.show_character_palette(); |
| 824 | 851 | } |
| @@ -936,6 +963,7 @@ impl<'a> WindowContext<'a> { · 111 unmodified lines | ||
| 936 | 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 | 967 | pub fn is_action_available(&self, action: &dyn Action) -> bool { |
| 940 | 968 | let target = self |
| 941 | 969 | .focused() |
| @@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> { · 20 unmodified lines | ||
| 962 | 990 | self.window.modifiers |
| 963 | 991 | } |
| 964 | 992 | |
| 993 | + /// Update the cursor style at the platform level. | |
| 965 | 994 | pub fn set_cursor_style(&mut self, style: CursorStyle) { |
| 966 | 995 | self.window.requested_cursor_style = Some(style) |
| 967 | 996 | } |
| @@ -991,7 +1020,7 @@ impl<'a> WindowContext<'a> { · 23 unmodified lines | ||
| 991 | 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 | 1024 | &self, |
| 996 | 1025 | point: &Point<Pixels>, |
| 997 | 1026 | level: &StackingOrder, |
| @@ -1649,6 +1678,7 @@ impl<'a> WindowContext<'a> { · 651 unmodified lines | ||
| 1649 | 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 | 1682 | pub fn has_pending_keystrokes(&self) -> bool { |
| 1653 | 1683 | self.window |
| 1654 | 1684 | .rendered_frame |
| @@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> { · 60 unmodified lines | ||
| 1715 | 1745 | subscription |
| 1716 | 1746 | } |
| 1717 | 1747 | |
| 1748 | + /// Focus the current window and bring it to the foreground at the platform level. | |
| 1718 | 1749 | pub fn activate_window(&self) { |
| 1719 | 1750 | self.window.platform_window.activate(); |
| 1720 | 1751 | } |
| 1721 | 1752 | |
| 1753 | + /// Minimize the current window at the platform level. | |
| 1722 | 1754 | pub fn minimize_window(&self) { |
| 1723 | 1755 | self.window.platform_window.minimize(); |
| 1724 | 1756 | } |
| 1725 | 1757 | |
| 1758 | + /// Toggle full screen status on the current window at the platform level. | |
| 1726 | 1759 | pub fn toggle_full_screen(&self) { |
| 1727 | 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 | 1766 | pub fn prompt( |
| 1731 | 1767 | &self, |
| 1732 | 1768 | level: PromptLevel, |
| 1733 | − msg: &str, | |
| 1769 | + message: &str, | |
| 1734 | 1770 | answers: &[&str], |
| 1735 | 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 | 1776 | pub fn available_actions(&self) -> Vec<Box<dyn Action>> { |
| 1740 | 1777 | let node_id = self |
| 1741 | 1778 | .window |
| @@ -1754,6 +1791,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines | ||
| 1754 | 1791 | .available_actions(node_id) |
| 1755 | 1792 | } |
| 1756 | 1793 | |
| 1794 | + /// Returns key bindings that invoke the given action on the currently focused element. | |
| 1757 | 1795 | pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> { |
| 1758 | 1796 | self.window |
| 1759 | 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 | 1806 | pub fn bindings_for_action_in( |
| 1768 | 1807 | &self, |
| 1769 | 1808 | action: &dyn Action, |
| @@ -1782,6 +1821,7 @@ impl<'a> WindowContext<'a> { · 12 unmodified lines | ||
| 1782 | 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 | 1825 | pub fn listener_for<V: Render, E>( |
| 1786 | 1826 | &self, |
| 1787 | 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 | 1837 | pub fn handler_for<V: Render>( |
| 1797 | 1838 | &self, |
| 1798 | 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 | 1850 | pub fn with_key_dispatch<R>( |
| 1809 | 1851 | &mut self, |
| 1810 | 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 | 1890 | pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { |
| 1847 | 1891 | let mut this = self.to_async(); |
| 1848 | 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 | 2065 | pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { |
| 2066 | + #[doc(hidden)] | |
| 2021 | 2067 | fn app_mut(&mut self) -> &mut AppContext { |
| 2022 | 2068 | self.borrow_mut() |
| 2023 | 2069 | } |
| 2024 | 2070 | |
| 2071 | + #[doc(hidden)] | |
| 2025 | 2072 | fn app(&self) -> &AppContext { |
| 2026 | 2073 | self.borrow() |
| 2027 | 2074 | } |
| 2028 | 2075 | |
| 2076 | + #[doc(hidden)] | |
| 2029 | 2077 | fn window(&self) -> &Window { |
| 2030 | 2078 | self.borrow() |
| 2031 | 2079 | } |
| 2032 | 2080 | |
| 2081 | + #[doc(hidden)] | |
| 2033 | 2082 | fn window_mut(&mut self) -> &mut Window { |
| 2034 | 2083 | self.borrow_mut() |
| 2035 | 2084 | } |
| @@ -2279,6 +2328,10 @@ impl BorrowMut<Window> for WindowContext<'_> { · 243 unmodified lines | ||
| 2279 | 2328 | |
| 2280 | 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 | 2335 | pub struct ViewContext<'a, V> { |
| 2283 | 2336 | window_cx: WindowContext<'a>, |
| 2284 | 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 | 2373 | pub fn entity_id(&self) -> EntityId { |
| 2320 | 2374 | self.view.entity_id() |
| 2321 | 2375 | } |
| 2322 | 2376 | |
| 2377 | + /// Get the view pointer underlying this context. | |
| 2323 | 2378 | pub fn view(&self) -> &View<V> { |
| 2324 | 2379 | self.view |
| 2325 | 2380 | } |
| 2326 | 2381 | |
| 2382 | + /// Get the model underlying this view. | |
| 2327 | 2383 | pub fn model(&self) -> &Model<V> { |
| 2328 | 2384 | &self.view.model |
| 2329 | 2385 | } |
| @@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 3 unmodified lines | ||
| 2333 | 2389 | &mut self.window_cx |
| 2334 | 2390 | } |
| 2335 | 2391 | |
| 2392 | + /// Set a given callback to be run on the next frame. | |
| 2336 | 2393 | pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static) |
| 2337 | 2394 | where |
| 2338 | 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 | 2411 | pub fn observe<V2, E>( |
| 2354 | 2412 | &mut self, |
| 2355 | 2413 | entity: &E, |
| @@ -2383,6 +2441,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 27 unmodified lines | ||
| 2383 | 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 | 2447 | pub fn subscribe<V2, E, Evt>( |
| 2387 | 2448 | &mut self, |
| 2388 | 2449 | entity: &E, |
| @@ -2440,6 +2501,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 51 unmodified lines | ||
| 2440 | 2501 | subscription |
| 2441 | 2502 | } |
| 2442 | 2503 | |
| 2504 | + /// Register a callback to be invoked when the given Model or View is released. | |
| 2443 | 2505 | pub fn observe_release<V2, E>( |
| 2444 | 2506 | &mut self, |
| 2445 | 2507 | entity: &E, |
| @@ -2466,6 +2528,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 20 unmodified lines | ||
| 2466 | 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 | 2533 | pub fn notify(&mut self) { |
| 2470 | 2534 | if !self.window.drawing { |
| 2471 | 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 | 2543 | pub fn observe_window_bounds( |
| 2479 | 2544 | &mut self, |
| 2480 | 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 | 2553 | subscription |
| 2489 | 2554 | } |
| 2490 | 2555 | |
| 2556 | + /// Register a callback to be invoked when the window is activated or deactivated. | |
| 2491 | 2557 | pub fn observe_window_activation( |
| 2492 | 2558 | &mut self, |
| 2493 | 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 | 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 | 2693 | pub fn spawn<Fut, R>( |
| 2624 | 2694 | &mut self, |
| 2625 | 2695 | f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut, |
| @@ -2632,6 +2702,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 6 unmodified lines | ||
| 2632 | 2702 | self.window_cx.spawn(|cx| f(view, cx)) |
| 2633 | 2703 | } |
| 2634 | 2704 | |
| 2705 | + /// Update the global state of the given type. | |
| 2635 | 2706 | pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R |
| 2636 | 2707 | where |
| 2637 | 2708 | G: 'static, |
| @@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 4 unmodified lines | ||
| 2642 | 2713 | result |
| 2643 | 2714 | } |
| 2644 | 2715 | |
| 2716 | + /// Register a callback to be invoked when the given global state changes. | |
| 2645 | 2717 | pub fn observe_global<G: 'static>( |
| 2646 | 2718 | &mut self, |
| 2647 | 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 | 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 | 2738 | pub fn on_mouse_event<Event: 'static>( |
| 2664 | 2739 | &mut self, |
| 2665 | 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 | 2751 | pub fn on_key_event<Event: 'static>( |
| 2676 | 2752 | &mut self, |
| 2677 | 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 | 2764 | pub fn on_action( |
| 2688 | 2765 | &mut self, |
| 2689 | 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 | 2779 | pub fn emit<Evt>(&mut self, event: Evt) |
| 2702 | 2780 | where |
| 2703 | 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 | 2793 | pub fn focus_self(&mut self) |
| 2715 | 2794 | where |
| 2716 | 2795 | V: FocusableView, |
| @@ -2718,6 +2797,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 1 unmodified line | ||
| 2718 | 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 | 2805 | pub fn listener<E>( |
| 2722 | 2806 | &self, |
| 2723 | 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 | 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 | 2919 | impl WindowId { |
| 2920 | + /// Converts this window ID to a `u64`. | |
| 2833 | 2921 | pub fn as_u64(&self) -> u64 { |
| 2834 | 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 | 2928 | #[derive(Deref, DerefMut)] |
| 2839 | 2929 | pub struct WindowHandle<V> { |
| 2840 | 2930 | #[deref] |
| @@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> { · 3 unmodified lines | ||
| 2844 | 2934 | } |
| 2845 | 2935 | |
| 2846 | 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 | 2939 | pub fn new(id: WindowId) -> Self { |
| 2848 | 2940 | WindowHandle { |
| 2849 | 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 | 2952 | pub fn root<C>(&self, cx: &mut C) -> Result<View<V>> |
| 2858 | 2953 | where |
| 2859 | 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 | 2966 | pub fn update<C, R>( |
| 2869 | 2967 | &self, |
| 2870 | 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 | 2985 | pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { |
| 2885 | 2986 | let x = cx |
| 2886 | 2987 | .windows |
| @@ -2897,6 +2998,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 10 unmodified lines | ||
| 2897 | 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 | 3004 | pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R> |
| 2901 | 3005 | where |
| 2902 | 3006 | C: Context, |
| @@ -2904,6 +3008,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line | ||
| 2904 | 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 | 3014 | pub fn root_view<C>(&self, cx: &C) -> Result<View<V>> |
| 2908 | 3015 | where |
| 2909 | 3016 | C: Context, |
| @@ -2911,6 +3018,9 @@ impl<V: 'static + Render> WindowHandle<V> { · 1 unmodified line | ||
| 2911 | 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 | 3024 | pub fn is_active(&self, cx: &AppContext) -> Option<bool> { |
| 2915 | 3025 | cx.windows |
| 2916 | 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 | 3060 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] |
| 2950 | 3061 | pub struct AnyWindowHandle { |
| 2951 | 3062 | pub(crate) id: WindowId, |
| @@ -2953,10 +3064,13 @@ pub struct AnyWindowHandle { · 1 unmodified line | ||
| 2953 | 3064 | } |
| 2954 | 3065 | |
| 2955 | 3066 | impl AnyWindowHandle { |
| 3067 | + /// Get the ID of this window. | |
| 2956 | 3068 | pub fn window_id(&self) -> WindowId { |
| 2957 | 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 | 3074 | pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> { |
| 2961 | 3075 | if TypeId::of::<T>() == self.state_type { |
| 2962 | 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 | 3088 | pub fn update<C, R>( |
| 2972 | 3089 | self, |
| 2973 | 3090 | cx: &mut C, |
| @@ -2979,6 +3096,9 @@ impl AnyWindowHandle { · 5 unmodified lines | ||
| 2979 | 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 | 3102 | pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R> |
| 2983 | 3103 | where |
| 2984 | 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 | 3126 | #[derive(Clone, Debug, Eq, PartialEq, Hash)] |
| 3003 | 3127 | pub enum ElementId { |
| 3128 | + /// The ID of a View element | |
| 3004 | 3129 | View(EntityId), |
| 3130 | + /// An integer ID. | |
| 3005 | 3131 | Integer(usize), |
| 3132 | + /// A string based ID. | |
| 3006 | 3133 | Name(SharedString), |
| 3134 | + /// An ID that's equated with a focus handle. | |
| 3007 | 3135 | FocusHandle(FocusId), |
| 3136 | + /// A combination of a name and an integer. | |
| 3008 | 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 | 3208 | #[derive(Clone)] |
| 3079 | 3209 | pub struct PaintQuad { |
| 3080 | 3210 | bounds: Bounds<Pixels>, |
| @@ -18,33 +18,33 @@ fn test_action_macros() { · 17 unmodified lines | ||
| 18 | 18 | |
| 19 | 19 | impl gpui::Action for RegisterableAction { |
| 20 | 20 | fn boxed_clone(&self) -> Box<dyn gpui::Action> { |
| 21 | − todo!() | |
| 21 | + unimplemented!() | |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | fn as_any(&self) -> &dyn std::any::Any { |
| 25 | − todo!() | |
| 25 | + unimplemented!() | |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | 28 | fn partial_eq(&self, _action: &dyn gpui::Action) -> bool { |
| 29 | − todo!() | |
| 29 | + unimplemented!() | |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | fn name(&self) -> &str { |
| 33 | − todo!() | |
| 33 | + unimplemented!() | |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | 36 | fn debug_name() -> &'static str |
| 37 | 37 | where |
| 38 | 38 | Self: Sized, |
| 39 | 39 | { |
| 40 | − todo!() | |
| 40 | + unimplemented!() | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>> |
| 44 | 44 | where |
| 45 | 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 | 7 | use proc_macro::TokenStream; |
| 8 | 8 | |
| 9 | 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 | 13 | pub fn register_action(ident: TokenStream) -> TokenStream { |
| 11 | 14 | register_action::register_action_macro(ident) |
| 12 | 15 | } |
| 13 | 16 | |
| 14 | 17 | #[proc_macro_derive(IntoElement)] |
| 18 | +// #[derive(IntoElement)] is used to create a Component out of anything that implements | |
| 19 | +// the `RenderOnce` trait. | |
| 15 | 20 | pub fn derive_into_element(input: TokenStream) -> TokenStream { |
| 16 | 21 | derive_into_element::derive_into_element(input) |
| 17 | 22 | } |
| 18 | 23 | |
| 19 | 24 | #[proc_macro_derive(Render)] |
| 25 | +#[doc(hidden)] | |
| 20 | 26 | pub fn derive_render(input: TokenStream) -> TokenStream { |
| 21 | 27 | derive_render::derive_render(input) |
| 22 | 28 | } |
| 23 | 29 | |
| 30 | +// Used by gpui to generate the style helpers. | |
| 24 | 31 | #[proc_macro] |
| 32 | +#[doc(hidden)] | |
| 25 | 33 | pub fn style_helpers(input: TokenStream) -> TokenStream { |
| 26 | 34 | style_helpers::style_helpers(input) |
| 27 | 35 | } |
| 28 | 36 | |
| 29 | 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 | 60 | pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { |
| 31 | 61 | test::test(args, function) |
| 32 | 62 | } |
| @@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { · 257 unmodified lines | ||
| 258 | 258 | let (buffer, syntax_map) = test_edit_sequence( |
| 259 | 259 | "Rust", |
| 260 | 260 | &[ |
| 261 | − "fn a() { dbg }", | |
| 262 | − "fn a() { dbg«!» }", | |
| 263 | − "fn a() { dbg!«()» }", | |
| 264 | − "fn a() { dbg!(«b») }", | |
| 265 | − "fn a() { dbg!(b«.») }", | |
| 266 | − "fn a() { dbg!(b.«c») }", | |
| 267 | − "fn a() { dbg!(b.c«()») }", | |
| 268 | − "fn a() { dbg!(b.c(«vec»)) }", | |
| 269 | − "fn a() { dbg!(b.c(vec«!»)) }", | |
| 270 | − "fn a() { dbg!(b.c(vec!«[]»)) }", | |
| 271 | − "fn a() { dbg!(b.c(vec![«d»])) }", | |
| 272 | − "fn a() { dbg!(b.c(vec![d«.»])) }", | |
| 273 | − "fn a() { dbg!(b.c(vec![d.«e»])) }", | |
| 261 | + "fn a() { test_macro }", | |
| 262 | + "fn a() { test_macro«!» }", | |
| 263 | + "fn a() { test_macro!«()» }", | |
| 264 | + "fn a() { test_macro!(«b») }", | |
| 265 | + "fn a() { test_macro!(b«.») }", | |
| 266 | + "fn a() { test_macro!(b.«c») }", | |
| 267 | + "fn a() { test_macro!(b.c«()») }", | |
| 268 | + "fn a() { test_macro!(b.c(«vec»)) }", | |
| 269 | + "fn a() { test_macro!(b.c(vec«!»)) }", | |
| 270 | + "fn a() { test_macro!(b.c(vec!«[]»)) }", | |
| 271 | + "fn a() { test_macro!(b.c(vec![«d»])) }", | |
| 272 | + "fn a() { test_macro!(b.c(vec![d«.»])) }", | |
| 273 | + "fn a() { test_macro!(b.c(vec![d.«e»])) }", | |
| 274 | 274 | ], |
| 275 | 275 | ); |
| 276 | 276 | |
| @@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { · 1 unmodified line | ||
| 278 | 278 | &syntax_map, |
| 279 | 279 | &buffer, |
| 280 | 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 | 30 | sync::Arc, |
| 31 | 31 | }; |
| 32 | 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 | 34 | use unicase::UniCase; |
| 35 | 35 | use util::{maybe, ResultExt, TryFutureExt}; |
| 36 | 36 | use workspace::{ |
| @@ -1403,7 +1403,7 @@ impl ProjectPanel { · 1366 unmodified lines | ||
| 1403 | 1403 | .indent_step_size(px(settings.indent_size)) |
| 1404 | 1404 | .selected(is_selected) |
| 1405 | 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 | 1407 | } else { |
| 1408 | 1408 | div().size(IconSize::default().rems()).invisible() |
| 1409 | 1409 | }) |
| @@ -1433,6 +1433,9 @@ impl ProjectPanel { · 23 unmodified lines | ||
| 1433 | 1433 | })) |
| 1434 | 1434 | .on_secondary_mouse_down(cx.listener( |
| 1435 | 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 | 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 | 1590 | .indent_level(self.details.depth) |
| 1588 | 1591 | .indent_step_size(px(settings.indent_size)) |
| 1589 | 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 | 1594 | } else { |
| 1592 | 1595 | div() |
| 1593 | 1596 | }) |
| @@ -1637,8 +1640,8 @@ impl Panel for ProjectPanel { · 43 unmodified lines | ||
| 1637 | 1640 | cx.notify(); |
| 1638 | 1641 | } |
| 1639 | 1642 | |
| 1640 | − fn icon(&self, _: &WindowContext) -> Option<ui::Icon> { | |
| 1641 | − Some(ui::Icon::FileTree) | |
| 1643 | + fn icon(&self, _: &WindowContext) -> Option<ui::IconName> { | |
| 1644 | + Some(ui::IconName::FileTree) | |
| 1642 | 1645 | } |
| 1643 | 1646 | |
| 1644 | 1647 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines | ||
| 6 | 6 | Subscription, View, ViewContext, WeakView, |
| 7 | 7 | }; |
| 8 | 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 | 10 | use workspace::{ |
| 11 | 11 | item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, |
| 12 | 12 | }; |
| @@ -43,7 +43,7 @@ impl Render for QuickActionBar { · 30 unmodified lines | ||
| 43 | 43 | |
| 44 | 44 | let inlay_hints_button = Some(QuickActionBarButton::new( |
| 45 | 45 | "toggle inlay hints", |
| 46 | − Icon::InlayHint, | |
| 46 | + IconName::InlayHint, | |
| 47 | 47 | editor.read(cx).inlay_hints_enabled(), |
| 48 | 48 | Box::new(editor::ToggleInlayHints), |
| 49 | 49 | "Toggle Inlay Hints", |
| @@ -60,7 +60,7 @@ impl Render for QuickActionBar { · 10 unmodified lines | ||
| 60 | 60 | |
| 61 | 61 | let search_button = Some(QuickActionBarButton::new( |
| 62 | 62 | "toggle buffer search", |
| 63 | − Icon::MagnifyingGlass, | |
| 63 | + IconName::MagnifyingGlass, | |
| 64 | 64 | !self.buffer_search_bar.read(cx).is_dismissed(), |
| 65 | 65 | Box::new(buffer_search::Deploy { focus: false }), |
| 66 | 66 | "Buffer Search", |
| @@ -77,7 +77,7 @@ impl Render for QuickActionBar { · 10 unmodified lines | ||
| 77 | 77 | |
| 78 | 78 | let assistant_button = QuickActionBarButton::new( |
| 79 | 79 | "toggle inline assistant", |
| 80 | − Icon::MagicWand, | |
| 80 | + IconName::MagicWand, | |
| 81 | 81 | false, |
| 82 | 82 | Box::new(InlineAssist), |
| 83 | 83 | "Inline Assist", |
| @@ -107,7 +107,7 @@ impl EventEmitter<ToolbarItemEvent> for QuickActionBar {} · 23 unmodified lines | ||
| 107 | 107 | #[derive(IntoElement)] |
| 108 | 108 | struct QuickActionBarButton { |
| 109 | 109 | id: ElementId, |
| 110 | − icon: Icon, | |
| 110 | + icon: IconName, | |
| 111 | 111 | toggled: bool, |
| 112 | 112 | action: Box<dyn Action>, |
| 113 | 113 | tooltip: SharedString, |
| @@ -117,7 +117,7 @@ struct QuickActionBarButton { · 3 unmodified lines | ||
| 117 | 117 | impl QuickActionBarButton { |
| 118 | 118 | fn new( |
| 119 | 119 | id: impl Into<ElementId>, |
| 120 | − icon: Icon, | |
| 120 | + icon: IconName, | |
| 121 | 121 | toggled: bool, |
| 122 | 122 | action: Box<dyn Action>, |
| 123 | 123 | tooltip: impl Into<SharedString>, |
| @@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; · 24 unmodified lines | ||
| 25 | 25 | #[cfg(not(test))] |
| 26 | 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 | 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 | |
| 31 | −/// hash being equivalent to hashing all the text contained in the [Rope] at once. | |
| 30 | +/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting | |
| 31 | +/// hash being equivalent to hashing all the text contained in the [`Rope`] at once. | |
| 32 | 32 | pub type RopeFingerprint = HashMatrix; |
| 33 | 33 | |
| 34 | 34 | #[derive(Clone, Default)] |
| @@ -76,30 +76,35 @@ impl Notification { · 75 unmodified lines | ||
| 76 | 76 | } |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | −#[test] | |
| 80 | −fn test_notification() { | |
| 81 | − // Notifications can be serialized and deserialized. | |
| 82 | − for notification in [ | |
| 83 | − Notification::ContactRequest { sender_id: 1 }, | |
| 84 | − Notification::ContactRequestAccepted { responder_id: 2 }, | |
| 85 | − Notification::ChannelInvitation { | |
| 86 | − channel_id: 100, | |
| 87 | − channel_name: "the-channel".into(), | |
| 88 | − inviter_id: 50, | |
| 89 | − }, | |
| 90 | − Notification::ChannelMessageMention { | |
| 91 | − sender_id: 200, | |
| 92 | − channel_id: 30, | |
| 93 | − message_id: 1, | |
| 94 | − }, | |
| 95 | − ] { | |
| 96 | − let message = notification.to_proto(); | |
| 97 | − let deserialized = Notification::from_proto(&message).unwrap(); | |
| 98 | − assert_eq!(deserialized, notification); | |
| 99 | − } | |
| 79 | +#[cfg(test)] | |
| 80 | +mod tests { | |
| 81 | + use crate::Notification; | |
| 82 | + | |
| 83 | + #[test] | |
| 84 | + fn test_notification() { | |
| 85 | + // Notifications can be serialized and deserialized. | |
| 86 | + for notification in [ | |
| 87 | + Notification::ContactRequest { sender_id: 1 }, | |
| 88 | + Notification::ContactRequestAccepted { responder_id: 2 }, | |
| 89 | + Notification::ChannelInvitation { | |
| 90 | + channel_id: 100, | |
| 91 | + channel_name: "the-channel".into(), | |
| 92 | + inviter_id: 50, | |
| 93 | + }, | |
| 94 | + Notification::ChannelMessageMention { | |
| 95 | + sender_id: 200, | |
| 96 | + channel_id: 30, | |
| 97 | + message_id: 1, | |
| 98 | + }, | |
| 99 | + ] { | |
| 100 | + let message = notification.to_proto(); | |
| 101 | + let deserialized = Notification::from_proto(&message).unwrap(); | |
| 102 | + assert_eq!(deserialized, notification); | |
| 103 | + } | |
| 100 | 104 | |
| 101 | − // When notifications are serialized, the `kind` and `actor_id` fields are | |
| 102 | − // stored separately, and do not appear redundantly in the JSON. | |
| 103 | − let notification = Notification::ContactRequest { sender_id: 1 }; | |
| 104 | − assert_eq!(notification.to_proto().content, "{}"); | |
| 105 | + // When notifications are serialized, the `kind` and `actor_id` fields are | |
| 106 | + // stored separately, and do not appear redundantly in the JSON. | |
| 107 | + let notification = Notification::ContactRequest { sender_id: 1 }; | |
| 108 | + assert_eq!(notification.to_proto().content, "{}"); | |
| 109 | + } | |
| 105 | 110 | } |
| @@ -21,7 +21,7 @@ use settings::Settings; · 20 unmodified lines | ||
| 21 | 21 | use std::{any::Any, sync::Arc}; |
| 22 | 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 | 25 | use util::ResultExt; |
| 26 | 26 | use workspace::{ |
| 27 | 27 | item::ItemHandle, |
| @@ -43,7 +43,7 @@ pub enum Event { · 15 unmodified lines | ||
| 43 | 43 | } |
| 44 | 44 | |
| 45 | 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 | 47 | .detach(); |
| 48 | 48 | } |
| 49 | 49 | |
| @@ -225,7 +225,7 @@ impl Render for BufferSearchBar { · 175 unmodified lines | ||
| 225 | 225 | .border_color(editor_border) |
| 226 | 226 | .min_w(rems(384. / 16.)) |
| 227 | 227 | .rounded_lg() |
| 228 | − .child(IconElement::new(Icon::MagnifyingGlass)) | |
| 228 | + .child(Icon::new(IconName::MagnifyingGlass)) | |
| 229 | 229 | .child(self.render_text_input(&self.query_editor, cx)) |
| 230 | 230 | .children(supported_options.case.then(|| { |
| 231 | 231 | self.render_search_option_button( |
| @@ -287,7 +287,7 @@ impl Render for BufferSearchBar { · 55 unmodified lines | ||
| 287 | 287 | this.child( |
| 288 | 288 | IconButton::new( |
| 289 | 289 | "buffer-search-bar-toggle-replace-button", |
| 290 | − Icon::Replace, | |
| 290 | + IconName::Replace, | |
| 291 | 291 | ) |
| 292 | 292 | .style(ButtonStyle::Subtle) |
| 293 | 293 | .when(self.replace_enabled, |button| { |
| @@ -323,7 +323,7 @@ impl Render for BufferSearchBar { · 29 unmodified lines | ||
| 323 | 323 | ) |
| 324 | 324 | .when(should_show_replace_input, |this| { |
| 325 | 325 | this.child( |
| 326 | − IconButton::new("search-replace-next", ui::Icon::ReplaceNext) | |
| 326 | + IconButton::new("search-replace-next", ui::IconName::ReplaceNext) | |
| 327 | 327 | .tooltip(move |cx| { |
| 328 | 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 | 334 | .child( |
| 335 | − IconButton::new("search-replace-all", ui::Icon::ReplaceAll) | |
| 335 | + IconButton::new("search-replace-all", ui::IconName::ReplaceAll) | |
| 336 | 336 | .tooltip(move |cx| { |
| 337 | 337 | Tooltip::for_action("Replace all", &ReplaceAll, cx) |
| 338 | 338 | }) |
| @@ -350,7 +350,7 @@ impl Render for BufferSearchBar { · 11 unmodified lines | ||
| 350 | 350 | .gap_0p5() |
| 351 | 351 | .flex_none() |
| 352 | 352 | .child( |
| 353 | − IconButton::new("select-all", ui::Icon::SelectAll) | |
| 353 | + IconButton::new("select-all", ui::IconName::SelectAll) | |
| 354 | 354 | .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone())) |
| 355 | 355 | .tooltip(|cx| { |
| 356 | 356 | Tooltip::for_action("Select all matches", &SelectAllMatches, cx) |
| @@ -358,13 +358,13 @@ impl Render for BufferSearchBar { · 1 unmodified line | ||
| 358 | 358 | ) |
| 359 | 359 | .children(match_count) |
| 360 | 360 | .child(render_nav_button( |
| 361 | − ui::Icon::ChevronLeft, | |
| 361 | + ui::IconName::ChevronLeft, | |
| 362 | 362 | self.active_match_index.is_some(), |
| 363 | 363 | "Select previous match", |
| 364 | 364 | &SelectPrevMatch, |
| 365 | 365 | )) |
| 366 | 366 | .child(render_nav_button( |
| 367 | − ui::Icon::ChevronRight, | |
| 367 | + ui::IconName::ChevronRight, | |
| 368 | 368 | self.active_match_index.is_some(), |
| 369 | 369 | "Select next match", |
| 370 | 370 | &SelectNextMatch, |
| @@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { · 108 unmodified lines | ||
| 479 | 479 | callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>), |
| 480 | 480 | ) { |
| 481 | 481 | self.register_action(move |workspace, action: &A, cx| { |
| 482 | + if workspace.has_active_modal(cx) { | |
| 483 | + cx.propagate(); | |
| 484 | + return; | |
| 485 | + } | |
| 486 | + | |
| 482 | 487 | let pane = workspace.active_pane(); |
| 483 | 488 | pane.update(cx, move |this, cx| { |
| 484 | 489 | this.toolbar().update(cx, move |this, cx| { |
| @@ -539,11 +544,11 @@ impl BufferSearchBar { · 54 unmodified lines | ||
| 539 | 544 | this.select_all_matches(action, cx); |
| 540 | 545 | }); |
| 541 | 546 | registrar.register_handler(|this, _: &editor::Cancel, cx| { |
| 542 | − if !this.dismissed { | |
| 547 | + if this.dismissed { | |
| 548 | + cx.propagate(); | |
| 549 | + } else { | |
| 543 | 550 | this.dismiss(&Dismiss, cx); |
| 544 | − return; | |
| 545 | 551 | } |
| 546 | − cx.propagate(); | |
| 547 | 552 | }); |
| 548 | 553 | registrar.register_handler(|this, deploy, cx| { |
| 549 | 554 | this.deploy(deploy, cx); |
| @@ -38,7 +38,7 @@ use std::{ · 37 unmodified lines | ||
| 38 | 38 | use theme::ThemeSettings; |
| 39 | 39 | |
| 40 | 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 | 42 | Selectable, ToggleButton, Tooltip, |
| 43 | 43 | }; |
| 44 | 44 | use util::{paths::PathMatcher, ResultExt as _}; |
| @@ -424,7 +424,8 @@ impl Item for ProjectSearchView { · 379 unmodified lines | ||
| 424 | 424 | .current() |
| 425 | 425 | .as_ref() |
| 426 | 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 | 429 | query_text.into() |
| 429 | 430 | }); |
| 430 | 431 | let tab_name = last_query |
| @@ -432,7 +433,7 @@ impl Item for ProjectSearchView { · 1 unmodified line | ||
| 432 | 433 | .unwrap_or_else(|| "Project search".into()); |
| 433 | 434 | h_stack() |
| 434 | 435 | .gap_2() |
| 435 | − .child(IconElement::new(Icon::MagnifyingGlass).color(if selected { | |
| 436 | + .child(Icon::new(IconName::MagnifyingGlass).color(if selected { | |
| 436 | 437 | Color::Default |
| 437 | 438 | } else { |
| 438 | 439 | Color::Muted |
| @@ -1616,12 +1617,12 @@ impl Render for ProjectSearchBar { · 1177 unmodified lines | ||
| 1616 | 1617 | .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) |
| 1617 | 1618 | .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) |
| 1618 | 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 | 1621 | .child(self.render_text_input(&search.query_editor, cx)) |
| 1621 | 1622 | .child( |
| 1622 | 1623 | h_stack() |
| 1623 | 1624 | .child( |
| 1624 | − IconButton::new("project-search-filter-button", Icon::Filter) | |
| 1625 | + IconButton::new("project-search-filter-button", IconName::Filter) | |
| 1625 | 1626 | .tooltip(|cx| { |
| 1626 | 1627 | Tooltip::for_action("Toggle filters", &ToggleFilters, cx) |
| 1627 | 1628 | }) |
| @@ -1639,7 +1640,7 @@ impl Render for ProjectSearchBar { · 11 unmodified lines | ||
| 1639 | 1640 | this.child( |
| 1640 | 1641 | IconButton::new( |
| 1641 | 1642 | "project-search-case-sensitive", |
| 1642 | − Icon::CaseSensitive, | |
| 1643 | + IconName::CaseSensitive, | |
| 1643 | 1644 | ) |
| 1644 | 1645 | .tooltip(|cx| { |
| 1645 | 1646 | Tooltip::for_action( |
| @@ -1659,7 +1660,7 @@ impl Render for ProjectSearchBar { · 13 unmodified lines | ||
| 1659 | 1660 | )), |
| 1660 | 1661 | ) |
| 1661 | 1662 | .child( |
| 1662 | − IconButton::new("project-search-whole-word", Icon::WholeWord) | |
| 1663 | + IconButton::new("project-search-whole-word", IconName::WholeWord) | |
| 1663 | 1664 | .tooltip(|cx| { |
| 1664 | 1665 | Tooltip::for_action( |
| 1665 | 1666 | "Toggle whole word", |
| @@ -1738,7 +1739,7 @@ impl Render for ProjectSearchBar { · 72 unmodified lines | ||
| 1738 | 1739 | }), |
| 1739 | 1740 | ) |
| 1740 | 1741 | .child( |
| 1741 | − IconButton::new("project-search-toggle-replace", Icon::Replace) | |
| 1742 | + IconButton::new("project-search-toggle-replace", IconName::Replace) | |
| 1742 | 1743 | .on_click(cx.listener(|this, _, cx| { |
| 1743 | 1744 | this.toggle_replace(&ToggleReplace, cx); |
| 1744 | 1745 | })) |
| @@ -1755,7 +1756,7 @@ impl Render for ProjectSearchBar { · 10 unmodified lines | ||
| 1755 | 1756 | .border_1() |
| 1756 | 1757 | .border_color(cx.theme().colors().border) |
| 1757 | 1758 | .rounded_lg() |
| 1758 | − .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) | |
| 1759 | + .child(Icon::new(IconName::Replace).size(ui::IconSize::Small)) | |
| 1759 | 1760 | .child(self.render_text_input(&search.replacement_editor, cx)) |
| 1760 | 1761 | } else { |
| 1761 | 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 | 1765 | let actions_column = h_stack() |
| 1765 | 1766 | .when(search.replace_enabled, |this| { |
| 1766 | 1767 | this.child( |
| 1767 | − IconButton::new("project-search-replace-next", Icon::ReplaceNext) | |
| 1768 | + IconButton::new("project-search-replace-next", IconName::ReplaceNext) | |
| 1768 | 1769 | .on_click(cx.listener(|this, _, cx| { |
| 1769 | 1770 | if let Some(search) = this.active_project_search.as_ref() { |
| 1770 | 1771 | search.update(cx, |this, cx| { |
| @@ -1775,7 +1776,7 @@ impl Render for ProjectSearchBar { · 4 unmodified lines | ||
| 1775 | 1776 | .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), |
| 1776 | 1777 | ) |
| 1777 | 1778 | .child( |
| 1778 | − IconButton::new("project-search-replace-all", Icon::ReplaceAll) | |
| 1779 | + IconButton::new("project-search-replace-all", IconName::ReplaceAll) | |
| 1779 | 1780 | .on_click(cx.listener(|this, _, cx| { |
| 1780 | 1781 | if let Some(search) = this.active_project_search.as_ref() { |
| 1781 | 1782 | search.update(cx, |this, cx| { |
| @@ -1796,7 +1797,7 @@ impl Render for ProjectSearchBar { · 14 unmodified lines | ||
| 1796 | 1797 | this |
| 1797 | 1798 | }) |
| 1798 | 1799 | .child( |
| 1799 | − IconButton::new("project-search-prev-match", Icon::ChevronLeft) | |
| 1800 | + IconButton::new("project-search-prev-match", IconName::ChevronLeft) | |
| 1800 | 1801 | .disabled(search.active_match_index.is_none()) |
| 1801 | 1802 | .on_click(cx.listener(|this, _, cx| { |
| 1802 | 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 | 1813 | .child( |
| 1813 | − IconButton::new("project-search-next-match", Icon::ChevronRight) | |
| 1814 | + IconButton::new("project-search-next-match", IconName::ChevronRight) | |
| 1814 | 1815 | .disabled(search.active_match_index.is_none()) |
| 1815 | 1816 | .on_click(cx.listener(|this, _, cx| { |
| 1816 | 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 | 64 | match *self { |
| 65 | − SearchOptions::WHOLE_WORD => ui::Icon::WholeWord, | |
| 66 | − SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive, | |
| 67 | − SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit, | |
| 65 | + SearchOptions::WHOLE_WORD => ui::IconName::WholeWord, | |
| 66 | + SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive, | |
| 67 | + SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit, | |
| 68 | 68 | _ => panic!("{:?} is not a named SearchOption", self), |
| 69 | 69 | } |
| 70 | 70 | } |
| @@ -3,7 +3,7 @@ use ui::IconButton; · 2 unmodified lines | ||
| 3 | 3 | use ui::{prelude::*, Tooltip}; |
| 4 | 4 | |
| 5 | 5 | pub(super) fn render_nav_button( |
| 6 | − icon: ui::Icon, | |
| 6 | + icon: ui::IconName, | |
| 7 | 7 | active: bool, |
| 8 | 8 | tooltip: &'static str, |
| 9 | 9 | action: &'static dyn Action, |
| @@ -14,6 +14,7 @@ anyhow.workspace = true · 13 unmodified lines | ||
| 14 | 14 | backtrace-on-stack-overflow = "0.3.0" |
| 15 | 15 | chrono = "0.4" |
| 16 | 16 | clap = { version = "4.4", features = ["derive", "string"] } |
| 17 | +collab_ui = { path = "../collab_ui", features = ["stories"] } | |
| 17 | 18 | strum = { version = "0.25.0", features = ["derive"] } |
| 18 | 19 | dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } |
| 19 | 20 | editor = { path = "../editor" } |
| @@ -16,6 +16,7 @@ pub enum ComponentStory { · 15 unmodified lines | ||
| 16 | 16 | Avatar, |
| 17 | 17 | Button, |
| 18 | 18 | Checkbox, |
| 19 | + CollabNotification, | |
| 19 | 20 | ContextMenu, |
| 20 | 21 | Cursor, |
| 21 | 22 | Disclosure, |
| @@ -45,6 +46,9 @@ impl ComponentStory { · 23 unmodified lines | ||
| 45 | 46 | Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(), |
| 46 | 47 | Self::Button => cx.new_view(|_| ui::ButtonStory).into(), |
| 47 | 48 | Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(), |
| 49 | + Self::CollabNotification => cx | |
| 50 | + .new_view(|_| collab_ui::notifications::CollabNotificationStory) | |
| 51 | + .into(), | |
| 48 | 52 | Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(), |
| 49 | 53 | Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(), |
| 50 | 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 | 466 | let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { |
| 455 | 467 | div() |
| 456 | 468 | .size_full() |
| @@ -19,7 +19,7 @@ use workspace::{ · 18 unmodified lines | ||
| 19 | 19 | dock::{DockPosition, Panel, PanelEvent}, |
| 20 | 20 | item::Item, |
| 21 | 21 | pane, |
| 22 | − ui::Icon, | |
| 22 | + ui::IconName, | |
| 23 | 23 | DraggedTab, Pane, Workspace, |
| 24 | 24 | }; |
| 25 | 25 | |
| @@ -71,7 +71,7 @@ impl TerminalPanel { · 45 unmodified lines | ||
| 71 | 71 | h_stack() |
| 72 | 72 | .gap_2() |
| 73 | 73 | .child( |
| 74 | − IconButton::new("plus", Icon::Plus) | |
| 74 | + IconButton::new("plus", IconName::Plus) | |
| 75 | 75 | .icon_size(IconSize::Small) |
| 76 | 76 | .on_click(move |_, cx| { |
| 77 | 77 | terminal_panel |
| @@ -82,10 +82,10 @@ impl TerminalPanel { · 4 unmodified lines | ||
| 82 | 82 | ) |
| 83 | 83 | .child({ |
| 84 | 84 | let zoomed = pane.is_zoomed(); |
| 85 | − IconButton::new("toggle_zoom", Icon::Maximize) | |
| 85 | + IconButton::new("toggle_zoom", IconName::Maximize) | |
| 86 | 86 | .icon_size(IconSize::Small) |
| 87 | 87 | .selected(zoomed) |
| 88 | − .selected_icon(Icon::Minimize) | |
| 88 | + .selected_icon(IconName::Minimize) | |
| 89 | 89 | .on_click(cx.listener(|pane, _, cx| { |
| 90 | 90 | pane.toggle_zoom(&workspace::ToggleZoom, cx); |
| 91 | 91 | })) |
| @@ -477,8 +477,8 @@ impl Panel for TerminalPanel { · 385 unmodified lines | ||
| 477 | 477 | "TerminalPanel" |
| 478 | 478 | } |
| 479 | 479 | |
| 480 | − fn icon(&self, _cx: &WindowContext) -> Option<Icon> { | |
| 481 | − Some(Icon::Terminal) | |
| 480 | + fn icon(&self, _cx: &WindowContext) -> Option<IconName> { | |
| 481 | + Some(IconName::Terminal) | |
| 482 | 482 | } |
| 483 | 483 | |
| 484 | 484 | fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { |
| @@ -20,7 +20,7 @@ use terminal::{ · 19 unmodified lines | ||
| 20 | 20 | Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, |
| 21 | 21 | }; |
| 22 | 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 | 24 | use util::{paths::PathLikeWithPosition, ResultExt}; |
| 25 | 25 | use workspace::{ |
| 26 | 26 | item::{BreadcrumbText, Item, ItemEvent}, |
| @@ -690,7 +690,7 @@ impl Item for TerminalView { · 663 unmodified lines | ||
| 690 | 690 | let title = self.terminal().read(cx).title(true); |
| 691 | 691 | h_stack() |
| 692 | 692 | .gap_2() |
| 693 | − .child(IconElement::new(Icon::Terminal)) | |
| 693 | + .child(Icon::new(IconName::Terminal)) | |
| 694 | 694 | .child(Label::new(title).color(if selected { |
| 695 | 695 | Color::Default |
| 696 | 696 | } else { |
| @@ -90,7 +90,7 @@ impl Anchor { · 89 unmodified lines | ||
| 90 | 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 | 94 | pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { |
| 95 | 95 | if *self == Anchor::MIN || *self == Anchor::MAX { |
| 96 | 96 | true |
| @@ -2,7 +2,9 @@ use gpui::{AppContext, Hsla, SharedString}; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 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 | 8 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] |
| 7 | 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 | 46 | pub struct ColorScale(Vec<Hsla>); |
| 41 | 47 | |
| 42 | 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 | 239 | pub struct ColorScaleSet { |
| 233 | 240 | name: SharedString, |
| 234 | 241 | light: ColorScale, |
| @@ -27,7 +27,7 @@ pub struct ThemeSettings { · 26 unmodified lines | ||
| 27 | 27 | } |
| 28 | 28 | |
| 29 | 29 | #[derive(Default)] |
| 30 | −pub struct AdjustedBufferFontSize(Pixels); | |
| 30 | +pub(crate) struct AdjustedBufferFontSize(Pixels); | |
| 31 | 31 | |
| 32 | 32 | #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] |
| 33 | 33 | pub struct ThemeSettingsContent { |
| @@ -2,11 +2,12 @@ use gpui::Hsla; · 1 unmodified line | ||
| 2 | 2 | use refineable::Refineable; |
| 3 | 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 | 7 | #[derive(Refineable, Clone, Debug)] |
| 8 | 8 | #[refineable(Debug, serde::Deserialize)] |
| 9 | 9 | pub struct ThemeColors { |
| 10 | + /// Border color. Used for most borders, is usually a high contrast color. | |
| 10 | 11 | pub border: Hsla, |
| 11 | 12 | /// Border color. Used for deemphasized borders, like a visual divider between two sections |
| 12 | 13 | pub border_variant: Hsla, |
| @@ -219,6 +220,8 @@ pub struct ThemeStyles { · 206 unmodified lines | ||
| 219 | 220 | |
| 220 | 221 | #[refineable] |
| 221 | 222 | pub colors: ThemeColors, |
| 223 | + | |
| 224 | + #[refineable] | |
| 222 | 225 | pub status: StatusColors, |
| 223 | 226 | pub player: PlayerColors, |
| 224 | 227 | pub syntax: Arc<SyntaxTheme>, |
| @@ -78,15 +78,6 @@ pub struct StatusColors { · 77 unmodified lines | ||
| 78 | 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 | 81 | pub struct DiagnosticColors { |
| 91 | 82 | pub error: Hsla, |
| 92 | 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 | 9 | mod default_colors; |
| 2 | 10 | mod default_theme; |
| 3 | 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 | 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 | 4 | pub trait Clickable { |
| 5 | 5 | /// Sets the click handler that will fire whenever the element is clicked. |
| 6 | 6 | fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self; |
| @@ -1,13 +1,26 @@ | ||
| 1 | 1 | use crate::prelude::*; |
| 2 | 2 | use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; |
| 3 | 3 | |
| 4 | +/// The shape of an [`Avatar`]. | |
| 4 | 5 | #[derive(Debug, Default, PartialEq, Clone)] |
| 5 | −pub enum Shape { | |
| 6 | +pub enum AvatarShape { | |
| 7 | + /// The avatar is shown in a circle. | |
| 6 | 8 | #[default] |
| 7 | 9 | Circle, |
| 10 | + /// The avatar is shown in a rectangle with rounded corners. | |
| 8 | 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 | 24 | #[derive(IntoElement)] |
| 12 | 25 | pub struct Avatar { |
| 13 | 26 | image: Img, |
| @@ -18,7 +31,7 @@ pub struct Avatar { · 4 unmodified lines | ||
| 18 | 31 | impl RenderOnce for Avatar { |
| 19 | 32 | fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { |
| 20 | 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 | 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 | 93 | self.image = match shape { |
| 71 | − Shape::Circle => self.image.rounded_full(), | |
| 72 | − Shape::RoundedRectangle => self.image.rounded_md(), | |
| 94 | + AvatarShape::Circle => self.image.rounded_full(), | |
| 95 | + AvatarShape::RoundedRectangle => self.image.rounded_md(), | |
| 73 | 96 | }; |
| 74 | 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 | 107 | pub fn grayscale(mut self, grayscale: bool) -> Self { |
| 78 | 108 | self.image = self.image.grayscale(grayscale); |
| 79 | 109 | self |
| @@ -2,11 +2,69 @@ use gpui::{AnyView, DefiniteLength}; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | use crate::{prelude::*, IconPosition, KeyBinding}; |
| 4 | 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 | 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 | 68 | #[derive(IntoElement)] |
| 11 | 69 | pub struct Button { |
| 12 | 70 | base: ButtonLike, |
| @@ -14,15 +72,21 @@ pub struct Button { · 1 unmodified line | ||
| 14 | 72 | label_color: Option<Color>, |
| 15 | 73 | label_size: Option<LabelSize>, |
| 16 | 74 | selected_label: Option<SharedString>, |
| 17 | − icon: Option<Icon>, | |
| 75 | + icon: Option<IconName>, | |
| 18 | 76 | icon_position: Option<IconPosition>, |
| 19 | 77 | icon_size: Option<IconSize>, |
| 20 | 78 | icon_color: Option<Color>, |
| 21 | − selected_icon: Option<Icon>, | |
| 79 | + selected_icon: Option<IconName>, | |
| 22 | 80 | key_binding: Option<KeyBinding>, |
| 23 | 81 | } |
| 24 | 82 | |
| 25 | 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 | 90 | pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self { |
| 27 | 91 | Self { |
| 28 | 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 | 107 | pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self { |
| 43 | 108 | self.label_color = label_color.into(); |
| 44 | 109 | self |
| 45 | 110 | } |
| 46 | 111 | |
| 112 | + /// Defines the size of the button's label. | |
| 47 | 113 | pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self { |
| 48 | 114 | self.label_size = label_size.into(); |
| 49 | 115 | self |
| 50 | 116 | } |
| 51 | 117 | |
| 118 | + /// Sets the label used when the button is in a selected state. | |
| 52 | 119 | pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self { |
| 53 | 120 | self.selected_label = label.into().map(Into::into); |
| 54 | 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 | 126 | self.icon = icon.into(); |
| 59 | 127 | self |
| 60 | 128 | } |
| 61 | 129 | |
| 130 | + /// Sets the position of the icon relative to the label. | |
| 62 | 131 | pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self { |
| 63 | 132 | self.icon_position = icon_position.into(); |
| 64 | 133 | self |
| 65 | 134 | } |
| 66 | 135 | |
| 136 | + /// Specifies the size of the button's icon. | |
| 67 | 137 | pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self { |
| 68 | 138 | self.icon_size = icon_size.into(); |
| 69 | 139 | self |
| 70 | 140 | } |
| 71 | 141 | |
| 142 | + /// Sets the color of the button's icon. | |
| 72 | 143 | pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self { |
| 73 | 144 | self.icon_color = icon_color.into(); |
| 74 | 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 | 150 | self.selected_icon = icon.into(); |
| 79 | 151 | self |
| 80 | 152 | } |
| 81 | 153 | |
| 154 | + /// Binds a key combination to the button for keyboard shortcuts. | |
| 82 | 155 | pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self { |
| 83 | 156 | self.key_binding = key_binding.into(); |
| 84 | 157 | self |
| @@ -86,6 +159,22 @@ impl Button { · 1 unmodified line | ||
| 86 | 159 | } |
| 87 | 160 | |
| 88 | 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 | 178 | fn selected(mut self, selected: bool) -> Self { |
| 90 | 179 | self.base = self.base.selected(selected); |
| 91 | 180 | self |
| @@ -93,6 +182,19 @@ impl Selectable for Button { · 1 unmodified line | ||
| 93 | 182 | } |
| 94 | 183 | |
| 95 | 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 | 198 | fn selected_style(mut self, style: ButtonStyle) -> Self { |
| 97 | 199 | self.base = self.base.selected_style(style); |
| 98 | 200 | self |
| @@ -100,6 +202,22 @@ impl SelectableButton for Button { · 1 unmodified line | ||
| 100 | 202 | } |
| 101 | 203 | |
| 102 | 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 | 221 | fn disabled(mut self, disabled: bool) -> Self { |
| 104 | 222 | self.base = self.base.disabled(disabled); |
| 105 | 223 | self |
| @@ -107,6 +225,7 @@ impl Disableable for Button { · 1 unmodified line | ||
| 107 | 225 | } |
| 108 | 226 | |
| 109 | 227 | impl Clickable for Button { |
| 228 | + /// Sets the click event handler for the button. | |
| 110 | 229 | fn on_click( |
| 111 | 230 | mut self, |
| 112 | 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 | 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 | 255 | fn width(mut self, width: DefiniteLength) -> Self { |
| 121 | 256 | self.base = self.base.width(width); |
| 122 | 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 | 273 | fn full_width(mut self) -> Self { |
| 126 | 274 | self.base = self.base.full_width(); |
| 127 | 275 | self |
| @@ -129,20 +277,42 @@ impl FixedWidth for Button { · 1 unmodified line | ||
| 129 | 277 | } |
| 130 | 278 | |
| 131 | 279 | impl ButtonCommon for Button { |
| 280 | + /// Sets the button's id. | |
| 132 | 281 | fn id(&self) -> &ElementId { |
| 133 | 282 | self.base.id() |
| 134 | 283 | } |
| 135 | 284 | |
| 285 | + /// Sets the visual style of the button using a [`ButtonStyle`]. | |
| 136 | 286 | fn style(mut self, style: ButtonStyle) -> Self { |
| 137 | 287 | self.base = self.base.style(style); |
| 138 | 288 | self |
| 139 | 289 | } |
| 140 | 290 | |
| 291 | + /// Sets the button's size using a [`ButtonSize`]. | |
| 141 | 292 | fn size(mut self, size: ButtonSize) -> Self { |
| 142 | 293 | self.base = self.base.size(size); |
| 143 | 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 | 316 | fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { |
| 147 | 317 | self.base = self.base.tooltip(tooltip); |
| 148 | 318 | self |
| @@ -1,4 +1,4 @@ | ||
| 1 | −use crate::{prelude::*, Icon, IconElement, IconSize}; | |
| 1 | +use crate::{prelude::*, Icon, IconName, IconSize}; | |
| 2 | 2 | |
| 3 | 3 | /// An icon that appears within a button. |
| 4 | 4 | /// |
| @@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; · 1 unmodified line | ||
| 6 | 6 | /// or as a standalone icon, like in [`IconButton`](crate::IconButton). |
| 7 | 7 | #[derive(IntoElement)] |
| 8 | 8 | pub(super) struct ButtonIcon { |
| 9 | − icon: Icon, | |
| 9 | + icon: IconName, | |
| 10 | 10 | size: IconSize, |
| 11 | 11 | color: Color, |
| 12 | 12 | disabled: bool, |
| 13 | 13 | selected: bool, |
| 14 | − selected_icon: Option<Icon>, | |
| 14 | + selected_icon: Option<IconName>, | |
| 15 | 15 | selected_style: Option<ButtonStyle>, |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | 18 | impl ButtonIcon { |
| 19 | − pub fn new(icon: Icon) -> Self { | |
| 19 | + pub fn new(icon: IconName) -> Self { | |
| 20 | 20 | Self { |
| 21 | 21 | icon, |
| 22 | 22 | size: IconSize::default(), |
| @@ -44,7 +44,7 @@ impl ButtonIcon { · 21 unmodified lines | ||
| 44 | 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 | 48 | self.selected_icon = icon.into(); |
| 49 | 49 | self |
| 50 | 50 | } |
| @@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { · 37 unmodified lines | ||
| 88 | 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 | 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 | 8 | pub trait SelectableButton: Selectable { |
| 8 | 9 | fn selected_style(self, style: ButtonStyle) -> Self; |
| 9 | 10 | } |
| 10 | 11 | |
| 12 | +/// A common set of traits all buttons must implement. | |
| 11 | 13 | pub trait ButtonCommon: Clickable + Disableable { |
| 12 | 14 | /// A unique element ID to identify the button. |
| 13 | 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 | 99 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] |
| 97 | 100 | pub enum ButtonStyle { |
| 98 | 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 | |
| 264 | −/// that are consistently sized with buttons. | |
| 266 | +/// The height of a button. | |
| 267 | +/// | |
| 268 | +/// Can also be used to size non-button elements to align with [`Button`]s. | |
| 265 | 269 | #[derive(Default, PartialEq, Clone, Copy)] |
| 266 | 270 | pub enum ButtonSize { |
| 267 | 271 | Large, |
| @@ -1,21 +1,21 @@ | ||
| 1 | 1 | use gpui::{AnyView, DefiniteLength}; |
| 2 | 2 | |
| 3 | 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 | 6 | use super::button_icon::ButtonIcon; |
| 7 | 7 | |
| 8 | 8 | #[derive(IntoElement)] |
| 9 | 9 | pub struct IconButton { |
| 10 | 10 | base: ButtonLike, |
| 11 | − icon: Icon, | |
| 11 | + icon: IconName, | |
| 12 | 12 | icon_size: IconSize, |
| 13 | 13 | icon_color: Color, |
| 14 | − selected_icon: Option<Icon>, | |
| 14 | + selected_icon: Option<IconName>, | |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 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 | 19 | Self { |
| 20 | 20 | base: ButtonLike::new(id), |
| 21 | 21 | icon, |
| @@ -35,7 +35,7 @@ impl IconButton { · 13 unmodified lines | ||
| 35 | 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 | 39 | self.selected_icon = icon.into(); |
| 40 | 40 | self |
| 41 | 41 | } |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; |
| 2 | 2 | |
| 3 | 3 | use crate::prelude::*; |
| 4 | −use crate::{Color, Icon, IconElement, Selection}; | |
| 4 | +use crate::{Color, Icon, IconName, Selection}; | |
| 5 | 5 | |
| 6 | 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 | 47 | let group_id = format!("checkbox_group_{:?}", self.id); |
| 48 | 48 | |
| 49 | 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 | 51 | if self.disabled { |
| 52 | 52 | Color::Disabled |
| 53 | 53 | } else { |
| @@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { · 1 unmodified line | ||
| 55 | 55 | }, |
| 56 | 56 | )), |
| 57 | 57 | Selection::Indeterminate => Some( |
| 58 | − IconElement::new(Icon::Dash) | |
| 58 | + Icon::new(IconName::Dash) | |
| 59 | 59 | .size(IconSize::Small) |
| 60 | 60 | .color(if self.disabled { |
| 61 | 61 | Color::Disabled |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | use crate::{ |
| 2 | − h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem, | |
| 3 | − ListSeparator, ListSubHeader, | |
| 2 | + h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, | |
| 3 | + ListSubHeader, | |
| 4 | 4 | }; |
| 5 | 5 | use gpui::{ |
| 6 | 6 | px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, |
| @@ -14,7 +14,7 @@ enum ContextMenuItem { · 7 unmodified lines | ||
| 14 | 14 | Header(SharedString), |
| 15 | 15 | Entry { |
| 16 | 16 | label: SharedString, |
| 17 | − icon: Option<Icon>, | |
| 17 | + icon: Option<IconName>, | |
| 18 | 18 | handler: Rc<dyn Fn(&mut WindowContext)>, |
| 19 | 19 | action: Option<Box<dyn Action>>, |
| 20 | 20 | }, |
| @@ -117,7 +117,7 @@ impl ContextMenu { · 96 unmodified lines | ||
| 117 | 117 | label: label.into(), |
| 118 | 118 | action: Some(action.boxed_clone()), |
| 119 | 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 | 122 | self |
| 123 | 123 | } |
| @@ -280,7 +280,7 @@ impl Render for ContextMenu { · 156 unmodified lines | ||
| 280 | 280 | h_stack() |
| 281 | 281 | .gap_1() |
| 282 | 282 | .child(Label::new(label.clone())) |
| 283 | − .child(IconElement::new(*icon)) | |
| 283 | + .child(Icon::new(*icon)) | |
| 284 | 284 | .into_any_element() |
| 285 | 285 | } else { |
| 286 | 286 | Label::new(label.clone()).into_any_element() |
| @@ -1,6 +1,6 @@ | ||
| 1 | 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 | 5 | #[derive(IntoElement)] |
| 6 | 6 | pub struct Disclosure { |
| @@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { · 25 unmodified lines | ||
| 32 | 32 | IconButton::new( |
| 33 | 33 | self.id, |
| 34 | 34 | match self.is_open { |
| 35 | − true => Icon::ChevronDown, | |
| 36 | − false => Icon::ChevronRight, | |
| 35 | + true => IconName::ChevronDown, | |
| 36 | + false => IconName::ChevronRight, | |
| 37 | 37 | }, |
| 38 | 38 | ) |
| 39 | 39 | .icon_color(Color::Muted) |
| @@ -7,6 +7,7 @@ enum DividerDirection { · 6 unmodified lines | ||
| 7 | 7 | Vertical, |
| 8 | 8 | } |
| 9 | 9 | |
| 10 | +/// The color of a [`Divider`]. | |
| 10 | 11 | #[derive(Default)] |
| 11 | 12 | pub enum DividerColor { |
| 12 | 13 | Border, |
| @@ -22,7 +22,7 @@ impl IconSize { · 21 unmodified lines | ||
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | #[derive(Debug, PartialEq, Copy, Clone, EnumIter)] |
| 25 | −pub enum Icon { | |
| 25 | +pub enum IconName { | |
| 26 | 26 | Ai, |
| 27 | 27 | ArrowDown, |
| 28 | 28 | ArrowLeft, |
| @@ -111,118 +111,108 @@ pub enum Icon { · 82 unmodified lines | ||
| 111 | 111 | ZedXCopilot, |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | −impl Icon { | |
| 114 | +impl IconName { | |
| 115 | 115 | pub fn path(self) -> &'static str { |
| 116 | 116 | match self { |
| 117 | − Icon::Ai => "icons/ai.svg", | |
| 118 | − Icon::ArrowDown => "icons/arrow_down.svg", | |
| 119 | − Icon::ArrowLeft => "icons/arrow_left.svg", | |
| 120 | − Icon::ArrowRight => "icons/arrow_right.svg", | |
| 121 | − Icon::ArrowUp => "icons/arrow_up.svg", | |
| 122 | − Icon::ArrowUpRight => "icons/arrow_up_right.svg", | |
| 123 | − Icon::ArrowCircle => "icons/arrow_circle.svg", | |
| 124 | − Icon::AtSign => "icons/at_sign.svg", | |
| 125 | − Icon::AudioOff => "icons/speaker_off.svg", | |
| 126 | − Icon::AudioOn => "icons/speaker_loud.svg", | |
| 127 | − Icon::Backspace => "icons/backspace.svg", | |
| 128 | − Icon::Bell => "icons/bell.svg", | |
| 129 | − Icon::BellOff => "icons/bell_off.svg", | |
| 130 | − Icon::BellRing => "icons/bell_ring.svg", | |
| 131 | − Icon::Bolt => "icons/bolt.svg", | |
| 132 | − Icon::CaseSensitive => "icons/case_insensitive.svg", | |
| 133 | − Icon::Check => "icons/check.svg", | |
| 134 | − Icon::ChevronDown => "icons/chevron_down.svg", | |
| 135 | − Icon::ChevronLeft => "icons/chevron_left.svg", | |
| 136 | − Icon::ChevronRight => "icons/chevron_right.svg", | |
| 137 | − Icon::ChevronUp => "icons/chevron_up.svg", | |
| 138 | − Icon::Close => "icons/x.svg", | |
| 139 | − Icon::Collab => "icons/user_group_16.svg", | |
| 140 | − Icon::Command => "icons/command.svg", | |
| 141 | − Icon::Control => "icons/control.svg", | |
| 142 | − Icon::Copilot => "icons/copilot.svg", | |
| 143 | − Icon::CopilotDisabled => "icons/copilot_disabled.svg", | |
| 144 | − Icon::CopilotError => "icons/copilot_error.svg", | |
| 145 | − Icon::CopilotInit => "icons/copilot_init.svg", | |
| 146 | − Icon::Copy => "icons/copy.svg", | |
| 147 | − Icon::Dash => "icons/dash.svg", | |
| 148 | − Icon::Delete => "icons/delete.svg", | |
| 149 | − Icon::Disconnected => "icons/disconnected.svg", | |
| 150 | − Icon::Ellipsis => "icons/ellipsis.svg", | |
| 151 | − Icon::Envelope => "icons/feedback.svg", | |
| 152 | − Icon::Escape => "icons/escape.svg", | |
| 153 | − Icon::ExclamationTriangle => "icons/warning.svg", | |
| 154 | − Icon::Exit => "icons/exit.svg", | |
| 155 | − Icon::ExternalLink => "icons/external_link.svg", | |
| 156 | − Icon::File => "icons/file.svg", | |
| 157 | − Icon::FileDoc => "icons/file_icons/book.svg", | |
| 158 | − Icon::FileGeneric => "icons/file_icons/file.svg", | |
| 159 | − Icon::FileGit => "icons/file_icons/git.svg", | |
| 160 | − Icon::FileLock => "icons/file_icons/lock.svg", | |
| 161 | − Icon::FileRust => "icons/file_icons/rust.svg", | |
| 162 | − Icon::FileToml => "icons/file_icons/toml.svg", | |
| 163 | − Icon::FileTree => "icons/project.svg", | |
| 164 | − Icon::Filter => "icons/filter.svg", | |
| 165 | − Icon::Folder => "icons/file_icons/folder.svg", | |
| 166 | − Icon::FolderOpen => "icons/file_icons/folder_open.svg", | |
| 167 | − Icon::FolderX => "icons/stop_sharing.svg", | |
| 168 | − Icon::Github => "icons/github.svg", | |
| 169 | − Icon::Hash => "icons/hash.svg", | |
| 170 | − Icon::InlayHint => "icons/inlay_hint.svg", | |
| 171 | − Icon::Link => "icons/link.svg", | |
| 172 | − Icon::MagicWand => "icons/magic_wand.svg", | |
| 173 | − Icon::MagnifyingGlass => "icons/magnifying_glass.svg", | |
| 174 | − Icon::MailOpen => "icons/mail_open.svg", | |
| 175 | − Icon::Maximize => "icons/maximize.svg", | |
| 176 | − Icon::Menu => "icons/menu.svg", | |
| 177 | − Icon::MessageBubbles => "icons/conversations.svg", | |
| 178 | − Icon::Mic => "icons/mic.svg", | |
| 179 | − Icon::MicMute => "icons/mic_mute.svg", | |
| 180 | − Icon::Minimize => "icons/minimize.svg", | |
| 181 | − Icon::Option => "icons/option.svg", | |
| 182 | − Icon::PageDown => "icons/page_down.svg", | |
| 183 | − Icon::PageUp => "icons/page_up.svg", | |
| 184 | − Icon::Plus => "icons/plus.svg", | |
| 185 | − Icon::Public => "icons/public.svg", | |
| 186 | − Icon::Quote => "icons/quote.svg", | |
| 187 | − Icon::Replace => "icons/replace.svg", | |
| 188 | − Icon::ReplaceAll => "icons/replace_all.svg", | |
| 189 | − Icon::ReplaceNext => "icons/replace_next.svg", | |
| 190 | − Icon::Return => "icons/return.svg", | |
| 191 | − Icon::Screen => "icons/desktop.svg", | |
| 192 | − Icon::SelectAll => "icons/select_all.svg", | |
| 193 | − Icon::Shift => "icons/shift.svg", | |
| 194 | − Icon::Snip => "icons/snip.svg", | |
| 195 | − Icon::Space => "icons/space.svg", | |
| 196 | − Icon::Split => "icons/split.svg", | |
| 197 | − Icon::Tab => "icons/tab.svg", | |
| 198 | − Icon::Terminal => "icons/terminal.svg", | |
| 199 | − Icon::Update => "icons/update.svg", | |
| 200 | − Icon::WholeWord => "icons/word_search.svg", | |
| 201 | − Icon::XCircle => "icons/error.svg", | |
| 202 | − Icon::ZedXCopilot => "icons/zed_x_copilot.svg", | |
| 117 | + IconName::Ai => "icons/ai.svg", | |
| 118 | + IconName::ArrowDown => "icons/arrow_down.svg", | |
| 119 | + IconName::ArrowLeft => "icons/arrow_left.svg", | |
| 120 | + IconName::ArrowRight => "icons/arrow_right.svg", | |
| 121 | + IconName::ArrowUp => "icons/arrow_up.svg", | |
| 122 | + IconName::ArrowUpRight => "icons/arrow_up_right.svg", | |
| 123 | + IconName::ArrowCircle => "icons/arrow_circle.svg", | |
| 124 | + IconName::AtSign => "icons/at_sign.svg", | |
| 125 | + IconName::AudioOff => "icons/speaker_off.svg", | |
| 126 | + IconName::AudioOn => "icons/speaker_loud.svg", | |
| 127 | + IconName::Backspace => "icons/backspace.svg", | |
| 128 | + IconName::Bell => "icons/bell.svg", | |
| 129 | + IconName::BellOff => "icons/bell_off.svg", | |
| 130 | + IconName::BellRing => "icons/bell_ring.svg", | |
| 131 | + IconName::Bolt => "icons/bolt.svg", | |
| 132 | + IconName::CaseSensitive => "icons/case_insensitive.svg", | |
| 133 | + IconName::Check => "icons/check.svg", | |
| 134 | + IconName::ChevronDown => "icons/chevron_down.svg", | |
| 135 | + IconName::ChevronLeft => "icons/chevron_left.svg", | |
| 136 | + IconName::ChevronRight => "icons/chevron_right.svg", | |
| 137 | + IconName::ChevronUp => "icons/chevron_up.svg", | |
| 138 | + IconName::Close => "icons/x.svg", | |
| 139 | + IconName::Collab => "icons/user_group_16.svg", | |
| 140 | + IconName::Command => "icons/command.svg", | |
| 141 | + IconName::Control => "icons/control.svg", | |
| 142 | + IconName::Copilot => "icons/copilot.svg", | |
| 143 | + IconName::CopilotDisabled => "icons/copilot_disabled.svg", | |
| 144 | + IconName::CopilotError => "icons/copilot_error.svg", | |
| 145 | + IconName::CopilotInit => "icons/copilot_init.svg", | |
| 146 | + IconName::Copy => "icons/copy.svg", | |
| 147 | + IconName::Dash => "icons/dash.svg", | |
| 148 | + IconName::Delete => "icons/delete.svg", | |
| 149 | + IconName::Disconnected => "icons/disconnected.svg", | |
| 150 | + IconName::Ellipsis => "icons/ellipsis.svg", | |
| 151 | + IconName::Envelope => "icons/feedback.svg", | |
| 152 | + IconName::Escape => "icons/escape.svg", | |
| 153 | + IconName::ExclamationTriangle => "icons/warning.svg", | |
| 154 | + IconName::Exit => "icons/exit.svg", | |
| 155 | + IconName::ExternalLink => "icons/external_link.svg", | |
| 156 | + IconName::File => "icons/file.svg", | |
| 157 | + IconName::FileDoc => "icons/file_icons/book.svg", | |
| 158 | + IconName::FileGeneric => "icons/file_icons/file.svg", | |
| 159 | + IconName::FileGit => "icons/file_icons/git.svg", | |
| 160 | + IconName::FileLock => "icons/file_icons/lock.svg", | |
| 161 | + IconName::FileRust => "icons/file_icons/rust.svg", | |
| 162 | + IconName::FileToml => "icons/file_icons/toml.svg", | |
| 163 | + IconName::FileTree => "icons/project.svg", | |
| 164 | + IconName::Filter => "icons/filter.svg", | |
| 165 | + IconName::Folder => "icons/file_icons/folder.svg", | |
| 166 | + IconName::FolderOpen => "icons/file_icons/folder_open.svg", | |
| 167 | + IconName::FolderX => "icons/stop_sharing.svg", | |
| 168 | + IconName::Github => "icons/github.svg", | |
| 169 | + IconName::Hash => "icons/hash.svg", | |
| 170 | + IconName::InlayHint => "icons/inlay_hint.svg", | |
| 171 | + IconName::Link => "icons/link.svg", | |
| 172 | + IconName::MagicWand => "icons/magic_wand.svg", | |
| 173 | + IconName::MagnifyingGlass => "icons/magnifying_glass.svg", | |
| 174 | + IconName::MailOpen => "icons/mail_open.svg", | |
| 175 | + IconName::Maximize => "icons/maximize.svg", | |
| 176 | + IconName::Menu => "icons/menu.svg", | |
| 177 | + IconName::MessageBubbles => "icons/conversations.svg", | |
| 178 | + IconName::Mic => "icons/mic.svg", | |
| 179 | + IconName::MicMute => "icons/mic_mute.svg", | |
| 180 | + IconName::Minimize => "icons/minimize.svg", | |
| 181 | + IconName::Option => "icons/option.svg", | |
| 182 | + IconName::PageDown => "icons/page_down.svg", | |
| 183 | + IconName::PageUp => "icons/page_up.svg", | |
| 184 | + IconName::Plus => "icons/plus.svg", | |
| 185 | + IconName::Public => "icons/public.svg", | |
| 186 | + IconName::Quote => "icons/quote.svg", | |
| 187 | + IconName::Replace => "icons/replace.svg", | |
| 188 | + IconName::ReplaceAll => "icons/replace_all.svg", | |
| 189 | + IconName::ReplaceNext => "icons/replace_next.svg", | |
| 190 | + IconName::Return => "icons/return.svg", | |
| 191 | + IconName::Screen => "icons/desktop.svg", | |
| 192 | + IconName::SelectAll => "icons/select_all.svg", | |
| 193 | + IconName::Shift => "icons/shift.svg", | |
| 194 | + IconName::Snip => "icons/snip.svg", | |
| 195 | + IconName::Space => "icons/space.svg", | |
| 196 | + IconName::Split => "icons/split.svg", | |
| 197 | + IconName::Tab => "icons/tab.svg", | |
| 198 | + IconName::Terminal => "icons/terminal.svg", | |
| 199 | + IconName::Update => "icons/update.svg", | |
| 200 | + IconName::WholeWord => "icons/word_search.svg", | |
| 201 | + IconName::XCircle => "icons/error.svg", | |
| 202 | + IconName::ZedXCopilot => "icons/zed_x_copilot.svg", | |
| 203 | 203 | } |
| 204 | 204 | } |
| 205 | 205 | } |
| 206 | 206 | |
| 207 | 207 | #[derive(IntoElement)] |
| 208 | −pub struct IconElement { | |
| 208 | +pub struct Icon { | |
| 209 | 209 | path: SharedString, |
| 210 | 210 | color: Color, |
| 211 | 211 | size: IconSize, |
| 212 | 212 | } |
| 213 | 213 | |
| 214 | −impl RenderOnce for IconElement { | |
| 215 | − fn render(self, cx: &mut WindowContext) -> impl IntoElement { | |
| 216 | − svg() | |
| 217 | − .size(self.size.rems()) | |
| 218 | − .flex_none() | |
| 219 | − .path(self.path) | |
| 220 | − .text_color(self.color.color(cx)) | |
| 221 | − } | |
| 222 | −} | |
| 223 | − | |
| 224 | −impl IconElement { | |
| 225 | − pub fn new(icon: Icon) -> Self { | |
| 214 | +impl Icon { | |
| 215 | + pub fn new(icon: IconName) -> Self { | |
| 226 | 216 | Self { |
| 227 | 217 | path: icon.path().into(), |
| 228 | 218 | color: Color::default(), |
| @@ -248,3 +238,13 @@ impl IconElement { · 19 unmodified lines | ||
| 248 | 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 | 2 | use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; |
| 3 | 3 | |
| 4 | 4 | #[derive(IntoElement, Clone)] |
| @@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { · 21 unmodified lines | ||
| 26 | 26 | .text_color(cx.theme().colors().text_muted) |
| 27 | 27 | .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) |
| 28 | 28 | .when(keystroke.modifiers.control, |el| { |
| 29 | − el.child(KeyIcon::new(Icon::Control)) | |
| 29 | + el.child(KeyIcon::new(IconName::Control)) | |
| 30 | 30 | }) |
| 31 | 31 | .when(keystroke.modifiers.alt, |el| { |
| 32 | − el.child(KeyIcon::new(Icon::Option)) | |
| 32 | + el.child(KeyIcon::new(IconName::Option)) | |
| 33 | 33 | }) |
| 34 | 34 | .when(keystroke.modifiers.command, |el| { |
| 35 | − el.child(KeyIcon::new(Icon::Command)) | |
| 35 | + el.child(KeyIcon::new(IconName::Command)) | |
| 36 | 36 | }) |
| 37 | 37 | .when(keystroke.modifiers.shift, |el| { |
| 38 | − el.child(KeyIcon::new(Icon::Shift)) | |
| 38 | + el.child(KeyIcon::new(IconName::Shift)) | |
| 39 | 39 | }) |
| 40 | 40 | .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) |
| 41 | 41 | .when(key_icon.is_none(), |el| { |
| @@ -62,21 +62,21 @@ impl KeyBinding { · 20 unmodified lines | ||
| 62 | 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 | 66 | match keystroke.key.as_str() { |
| 67 | − "left" => Some(Icon::ArrowLeft), | |
| 68 | − "right" => Some(Icon::ArrowRight), | |
| 69 | − "up" => Some(Icon::ArrowUp), | |
| 70 | − "down" => Some(Icon::ArrowDown), | |
| 71 | − "backspace" => Some(Icon::Backspace), | |
| 72 | − "delete" => Some(Icon::Delete), | |
| 73 | − "return" => Some(Icon::Return), | |
| 74 | − "enter" => Some(Icon::Return), | |
| 75 | − "tab" => Some(Icon::Tab), | |
| 76 | − "space" => Some(Icon::Space), | |
| 77 | − "escape" => Some(Icon::Escape), | |
| 78 | − "pagedown" => Some(Icon::PageDown), | |
| 79 | − "pageup" => Some(Icon::PageUp), | |
| 67 | + "left" => Some(IconName::ArrowLeft), | |
| 68 | + "right" => Some(IconName::ArrowRight), | |
| 69 | + "up" => Some(IconName::ArrowUp), | |
| 70 | + "down" => Some(IconName::ArrowDown), | |
| 71 | + "backspace" => Some(IconName::Backspace), | |
| 72 | + "delete" => Some(IconName::Delete), | |
| 73 | + "return" => Some(IconName::Return), | |
| 74 | + "enter" => Some(IconName::Return), | |
| 75 | + "tab" => Some(IconName::Tab), | |
| 76 | + "space" => Some(IconName::Space), | |
| 77 | + "escape" => Some(IconName::Escape), | |
| 78 | + "pagedown" => Some(IconName::PageDown), | |
| 79 | + "pageup" => Some(IconName::PageUp), | |
| 80 | 80 | _ => None, |
| 81 | 81 | } |
| 82 | 82 | } |
| @@ -120,13 +120,13 @@ impl Key { · 37 unmodified lines | ||
| 120 | 120 | |
| 121 | 121 | #[derive(IntoElement)] |
| 122 | 122 | pub struct KeyIcon { |
| 123 | − icon: Icon, | |
| 123 | + icon: IconName, | |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | 126 | impl RenderOnce for KeyIcon { |
| 127 | 127 | fn render(self, _cx: &mut WindowContext) -> impl IntoElement { |
| 128 | 128 | div().w(rems(14. / 16.)).child( |
| 129 | − IconElement::new(self.icon) | |
| 129 | + Icon::new(self.icon) | |
| 130 | 130 | .size(IconSize::Small) |
| 131 | 131 | .color(Color::Muted), |
| 132 | 132 | ) |
| @@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { · 1 unmodified line | ||
| 134 | 134 | } |
| 135 | 135 | |
| 136 | 136 | impl KeyIcon { |
| 137 | − pub fn new(icon: Icon) -> Self { | |
| 137 | + pub fn new(icon: IconName) -> Self { | |
| 138 | 138 | Self { icon } |
| 139 | 139 | } |
| 140 | 140 | } |
| @@ -2,6 +2,28 @@ use gpui::WindowContext; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 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 | 27 | #[derive(IntoElement)] |
| 6 | 28 | pub struct Label { |
| 7 | 29 | base: LabelLike, |
| @@ -9,6 +31,13 @@ pub struct Label { · 1 unmodified line | ||
| 9 | 31 | } |
| 10 | 32 | |
| 11 | 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 | 41 | pub fn new(label: impl Into<SharedString>) -> Self { |
| 13 | 42 | Self { |
| 14 | 43 | base: LabelLike::new(), |
| @@ -18,21 +47,49 @@ impl Label { · 3 unmodified lines | ||
| 18 | 47 | } |
| 19 | 48 | |
| 20 | 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 | 57 | fn size(mut self, size: LabelSize) -> Self { |
| 22 | 58 | self.base = self.base.size(size); |
| 23 | 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 | 69 | fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { |
| 27 | 70 | self.base = self.base.line_height_style(line_height_style); |
| 28 | 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 | 81 | fn color(mut self, color: Color) -> Self { |
| 32 | 82 | self.base = self.base.color(color); |
| 33 | 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 | 93 | fn strikethrough(mut self, strikethrough: bool) -> Self { |
| 37 | 94 | self.base = self.base.strikethrough(strikethrough); |
| 38 | 95 | self |
| @@ -19,6 +19,7 @@ pub enum LineHeightStyle { · 18 unmodified lines | ||
| 19 | 19 | UiLabel, |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | +/// A common set of traits all labels must implement. | |
| 22 | 23 | pub trait LabelCommon { |
| 23 | 24 | fn size(self, size: LabelSize) -> Self; |
| 24 | 25 | fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; |
| @@ -1,10 +1,10 @@ | ||
| 1 | 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 | 4 | #[derive(IntoElement)] |
| 5 | 5 | pub struct ListSubHeader { |
| 6 | 6 | label: SharedString, |
| 7 | − start_slot: Option<Icon>, | |
| 7 | + start_slot: Option<IconName>, | |
| 8 | 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 | 21 | self.start_slot = left_icon; |
| 22 | 22 | self |
| 23 | 23 | } |
| @@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { · 16 unmodified lines | ||
| 40 | 40 | .flex() |
| 41 | 41 | .gap_1() |
| 42 | 42 | .items_center() |
| 43 | − .children(self.start_slot.map(|i| { | |
| 44 | − IconElement::new(i) | |
| 45 | − .color(Color::Muted) | |
| 46 | − .size(IconSize::Small) | |
| 47 | − })) | |
| 43 | + .children( | |
| 44 | + self.start_slot | |
| 45 | + .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)), | |
| 46 | + ) | |
| 48 | 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 | 112 | pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> { |
| 112 | 113 | PopoverMenu { |
| 113 | 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 | 43 | pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> { |
| 43 | 44 | RightClickMenu { |
| 44 | 45 | id: id.into(), |
| @@ -2,17 +2,13 @@ use gpui::{div, Div}; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | use crate::StyledExt; |
| 4 | 4 | |
| 5 | −/// Horizontally stacks elements. | |
| 6 | −/// | |
| 7 | −/// Sets `flex()`, `flex_row()`, `items_center()` | |
| 5 | +/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` | |
| 8 | 6 | #[track_caller] |
| 9 | 7 | pub fn h_stack() -> Div { |
| 10 | 8 | div().h_flex() |
| 11 | 9 | } |
| 12 | 10 | |
| 13 | −/// Vertically stacks elements. | |
| 14 | −/// | |
| 15 | −/// Sets `flex()`, `flex_col()` | |
| 11 | +/// Vertically stacks elements. Sets `flex()`, `flex_col()` | |
| 16 | 12 | #[track_caller] |
| 17 | 13 | pub fn v_stack() -> Div { |
| 18 | 14 | div().v_flex() |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | use gpui::Render; |
| 2 | 2 | use story::Story; |
| 3 | 3 | |
| 4 | −use crate::{prelude::*, Icon}; | |
| 4 | +use crate::{prelude::*, IconName}; | |
| 5 | 5 | use crate::{Button, ButtonStyle}; |
| 6 | 6 | |
| 7 | 7 | pub struct ButtonStory; |
| @@ -23,12 +23,12 @@ impl Render for ButtonStory { · 15 unmodified lines | ||
| 23 | 23 | .child(Story::label("With `label_color`")) |
| 24 | 24 | .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) |
| 25 | 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 | 27 | .child(Story::label("Selected with `icon`")) |
| 28 | 28 | .child( |
| 29 | 29 | Button::new("filled_and_selected_with_icon", "Click me") |
| 30 | 30 | .selected(true) |
| 31 | − .icon(Icon::FileGit), | |
| 31 | + .icon(IconName::FileGit), | |
| 32 | 32 | ) |
| 33 | 33 | .child(Story::label("Default (Subtle)")) |
| 34 | 34 | .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) |
| @@ -3,17 +3,17 @@ use story::Story; · 2 unmodified lines | ||
| 3 | 3 | use strum::IntoEnumIterator; |
| 4 | 4 | |
| 5 | 5 | use crate::prelude::*; |
| 6 | −use crate::{Icon, IconElement}; | |
| 6 | +use crate::{Icon, IconName}; | |
| 7 | 7 | |
| 8 | 8 | pub struct IconStory; |
| 9 | 9 | |
| 10 | 10 | impl Render for IconStory { |
| 11 | 11 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { |
| 12 | − let icons = Icon::iter(); | |
| 12 | + let icons = IconName::iter(); | |
| 13 | 13 | |
| 14 | 14 | Story::container() |
| 15 | − .child(Story::title_for::<IconElement>()) | |
| 15 | + .child(Story::title_for::<Icon>()) | |
| 16 | 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 | 2 | use story::{StoryContainer, StoryItem, StorySection}; |
| 3 | 3 | |
| 4 | 4 | use crate::{prelude::*, Tooltip}; |
| 5 | −use crate::{Icon, IconButton}; | |
| 5 | +use crate::{IconButton, IconName}; | |
| 6 | 6 | |
| 7 | 7 | pub struct IconButtonStory; |
| 8 | 8 | |
| @@ -10,7 +10,7 @@ impl Render for IconButtonStory { · 1 unmodified line | ||
| 10 | 10 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { |
| 11 | 11 | let default_button = StoryItem::new( |
| 12 | 12 | "Default", |
| 13 | − IconButton::new("default_icon_button", Icon::Hash), | |
| 13 | + IconButton::new("default_icon_button", IconName::Hash), | |
| 14 | 14 | ) |
| 15 | 15 | .description("Displays an icon button.") |
| 16 | 16 | .usage( |
| @@ -21,7 +21,7 @@ impl Render for IconButtonStory { · 4 unmodified lines | ||
| 21 | 21 | |
| 22 | 22 | let selected_button = StoryItem::new( |
| 23 | 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 | 26 | .description("Displays an icon button that is selected.") |
| 27 | 27 | .usage( |
| @@ -32,9 +32,9 @@ impl Render for IconButtonStory { · 4 unmodified lines | ||
| 32 | 32 | |
| 33 | 33 | let selected_with_selected_icon = StoryItem::new( |
| 34 | 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 | 36 | .selected(true) |
| 37 | − .selected_icon(Icon::AudioOff), | |
| 37 | + .selected_icon(IconName::AudioOff), | |
| 38 | 38 | ) |
| 39 | 39 | .description( |
| 40 | 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 | 50 | let disabled_button = StoryItem::new( |
| 51 | 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 | 54 | .description("Displays an icon button that is disabled.") |
| 55 | 55 | .usage( |
| @@ -60,7 +60,7 @@ impl Render for IconButtonStory { · 4 unmodified lines | ||
| 60 | 60 | |
| 61 | 61 | let with_on_click_button = StoryItem::new( |
| 62 | 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 | 64 | println!("Clicked!"); |
| 65 | 65 | }), |
| 66 | 66 | ) |
| @@ -75,7 +75,7 @@ impl Render for IconButtonStory { · 8 unmodified lines | ||
| 75 | 75 | |
| 76 | 76 | let with_tooltip_button = StoryItem::new( |
| 77 | 77 | "With `tooltip`", |
| 78 | − IconButton::new("with_tooltip_button", Icon::MessageBubbles) | |
| 78 | + IconButton::new("with_tooltip_button", IconName::MessageBubbles) | |
| 79 | 79 | .tooltip(|cx| Tooltip::text("Open messages", cx)), |
| 80 | 80 | ) |
| 81 | 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 | 89 | let selected_with_tooltip_button = StoryItem::new( |
| 90 | 90 | "Selected with `tooltip`", |
| 91 | − IconButton::new("selected_with_tooltip_button", Icon::InlayHint) | |
| 91 | + IconButton::new("selected_with_tooltip_button", IconName::InlayHint) | |
| 92 | 92 | .selected(true) |
| 93 | 93 | .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), |
| 94 | 94 | ) |
| @@ -2,7 +2,7 @@ use gpui::Render; · 1 unmodified line | ||
| 2 | 2 | use story::Story; |
| 3 | 3 | |
| 4 | 4 | use crate::{prelude::*, IconButton}; |
| 5 | −use crate::{Icon, ListHeader}; | |
| 5 | +use crate::{IconName, ListHeader}; | |
| 6 | 6 | |
| 7 | 7 | pub struct ListHeaderStory; |
| 8 | 8 | |
| @@ -13,19 +13,19 @@ impl Render for ListHeaderStory { · 4 unmodified lines | ||
| 13 | 13 | .child(Story::label("Default")) |
| 14 | 14 | .child(ListHeader::new("Section 1")) |
| 15 | 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 | 17 | .child(Story::label("With left icon and meta")) |
| 18 | 18 | .child( |
| 19 | 19 | ListHeader::new("Section 3") |
| 20 | − .start_slot(IconElement::new(Icon::BellOff)) | |
| 21 | − .end_slot(IconButton::new("action_1", Icon::Bolt)), | |
| 20 | + .start_slot(Icon::new(IconName::BellOff)) | |
| 21 | + .end_slot(IconButton::new("action_1", IconName::Bolt)), | |
| 22 | 22 | ) |
| 23 | 23 | .child(Story::label("With multiple meta")) |
| 24 | 24 | .child( |
| 25 | 25 | ListHeader::new("Section 4") |
| 26 | − .end_slot(IconButton::new("action_1", Icon::Bolt)) | |
| 27 | − .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle)) | |
| 28 | − .end_slot(IconButton::new("action_3", Icon::Plus)), | |
| 26 | + .end_slot(IconButton::new("action_1", IconName::Bolt)) | |
| 27 | + .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle)) | |
| 28 | + .end_slot(IconButton::new("action_3", IconName::Plus)), | |
| 29 | 29 | ) |
| 30 | 30 | } |
| 31 | 31 | } |
| @@ -1,8 +1,8 @@ | ||
| 1 | −use gpui::Render; | |
| 1 | +use gpui::{Render, SharedUrl}; | |
| 2 | 2 | use story::Story; |
| 3 | 3 | |
| 4 | 4 | use crate::{prelude::*, Avatar}; |
| 5 | −use crate::{Icon, ListItem}; | |
| 5 | +use crate::{IconName, ListItem}; | |
| 6 | 6 | |
| 7 | 7 | pub struct ListItemStory; |
| 8 | 8 | |
| @@ -18,13 +18,13 @@ impl Render for ListItemStory { · 9 unmodified lines | ||
| 18 | 18 | ListItem::new("inset_list_item") |
| 19 | 19 | .inset(true) |
| 20 | 20 | .start_slot( |
| 21 | − IconElement::new(Icon::Bell) | |
| 21 | + Icon::new(IconName::Bell) | |
| 22 | 22 | .size(IconSize::Small) |
| 23 | 23 | .color(Color::Muted), |
| 24 | 24 | ) |
| 25 | 25 | .child("Hello, world!") |
| 26 | 26 | .end_slot( |
| 27 | − IconElement::new(Icon::Bell) | |
| 27 | + Icon::new(IconName::Bell) | |
| 28 | 28 | .size(IconSize::Small) |
| 29 | 29 | .color(Color::Muted), |
| 30 | 30 | ), |
| @@ -34,7 +34,7 @@ impl Render for ListItemStory { · 3 unmodified lines | ||
| 34 | 34 | ListItem::new("with start slot_icon") |
| 35 | 35 | .child("Hello, world!") |
| 36 | 36 | .start_slot( |
| 37 | − IconElement::new(Icon::Bell) | |
| 37 | + Icon::new(IconName::Bell) | |
| 38 | 38 | .size(IconSize::Small) |
| 39 | 39 | .color(Color::Muted), |
| 40 | 40 | ), |
| @@ -43,7 +43,7 @@ impl Render for ListItemStory { · 2 unmodified lines | ||
| 43 | 43 | .child( |
| 44 | 44 | ListItem::new("with_start slot avatar") |
| 45 | 45 | .child("Hello, world!") |
| 46 | − .start_slot(Avatar::new(SharedString::from( | |
| 46 | + .start_slot(Avatar::new(SharedUrl::from( | |
| 47 | 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 | 51 | .child( |
| 52 | 52 | ListItem::new("with_left_avatar") |
| 53 | 53 | .child("Hello, world!") |
| 54 | − .end_slot(Avatar::new(SharedString::from( | |
| 54 | + .end_slot(Avatar::new(SharedUrl::from( | |
| 55 | 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 | 62 | .end_slot( |
| 63 | 63 | h_stack() |
| 64 | 64 | .gap_2() |
| 65 | − .child(Avatar::new(SharedString::from( | |
| 65 | + .child(Avatar::new(SharedUrl::from( | |
| 66 | 66 | "https://avatars.githubusercontent.com/u/1789?v=4", |
| 67 | 67 | ))) |
| 68 | − .child(Avatar::new(SharedString::from( | |
| 68 | + .child(Avatar::new(SharedUrl::from( | |
| 69 | 69 | "https://avatars.githubusercontent.com/u/1789?v=4", |
| 70 | 70 | ))) |
| 71 | − .child(Avatar::new(SharedString::from( | |
| 71 | + .child(Avatar::new(SharedUrl::from( | |
| 72 | 72 | "https://avatars.githubusercontent.com/u/1789?v=4", |
| 73 | 73 | ))) |
| 74 | − .child(Avatar::new(SharedString::from( | |
| 74 | + .child(Avatar::new(SharedUrl::from( | |
| 75 | 75 | "https://avatars.githubusercontent.com/u/1789?v=4", |
| 76 | 76 | ))) |
| 77 | − .child(Avatar::new(SharedString::from( | |
| 77 | + .child(Avatar::new(SharedUrl::from( | |
| 78 | 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 | 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 | 27 | h_stack().child( |
| 28 | 28 | Tab::new("tab_1") |
| 29 | 29 | .end_slot( |
| 30 | − IconButton::new("close_button", Icon::Close) | |
| 30 | + IconButton::new("close_button", IconName::Close) | |
| 31 | 31 | .icon_color(Color::Muted) |
| 32 | 32 | .size(ButtonSize::None) |
| 33 | 33 | .icon_size(IconSize::XSmall), |
| @@ -38,16 +38,19 @@ impl Render for TabBarStory { · 37 unmodified lines | ||
| 38 | 38 | h_stack().child( |
| 39 | 39 | TabBar::new("tab_bar_1") |
| 40 | 40 | .start_child( |
| 41 | − IconButton::new("navigate_backward", Icon::ArrowLeft) | |
| 41 | + IconButton::new("navigate_backward", IconName::ArrowLeft) | |
| 42 | 42 | .icon_size(IconSize::Small), |
| 43 | 43 | ) |
| 44 | 44 | .start_child( |
| 45 | − IconButton::new("navigate_forward", Icon::ArrowRight) | |
| 45 | + IconButton::new("navigate_forward", IconName::ArrowRight) | |
| 46 | 46 | .icon_size(IconSize::Small), |
| 47 | 47 | ) |
| 48 | − .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small)) | |
| 49 | 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 | 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 | 2 | pub trait Disableable { |
| 3 | 3 | /// Sets whether the element is disabled. |
| 4 | 4 | fn disabled(self, disabled: bool) -> Self; |
| @@ -1,6 +1,6 @@ | ||
| 1 | 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 | 4 | pub trait FixedWidth { |
| 5 | 5 | /// Sets the width of the element. |
| 6 | 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 | 3 | pub use gpui::prelude::*; |
| 2 | 4 | pub use gpui::{ |
| 3 | 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 | 17 | pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; |
| 16 | 18 | pub use crate::{ButtonCommon, Color, StyledExt}; |
| 17 | 19 | pub use crate::{Headline, HeadlineSize}; |
| 18 | −pub use crate::{Icon, IconElement, IconPosition, IconSize}; | |
| 20 | +pub use crate::{Icon, IconName, IconPosition, IconSize}; | |
| 19 | 21 | pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; |
| 20 | 22 | pub use theme::ActiveTheme; |
| @@ -1,18 +1,27 @@ | ||
| 1 | 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 | 4 | pub trait Selectable { |
| 3 | 5 | /// Sets whether the element is selected. |
| 4 | 6 | fn selected(self, selected: bool) -> Self; |
| 5 | 7 | } |
| 6 | 8 | |
| 9 | +/// Represents the selection status of an element. | |
| 7 | 10 | #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] |
| 8 | 11 | pub enum Selection { |
| 12 | + /// The element is not selected. | |
| 9 | 13 | #[default] |
| 10 | 14 | Unselected, |
| 15 | + /// The selection state of the element is indeterminate. | |
| 11 | 16 | Indeterminate, |
| 17 | + /// The element is selected. | |
| 12 | 18 | Selected, |
| 13 | 19 | } |
| 14 | 20 | |
| 15 | 21 | impl Selection { |
| 22 | + /// Returns the inverse of the current selection status. | |
| 23 | + /// | |
| 24 | + /// Indeterminate states become selected if inverted. | |
| 16 | 25 | pub fn inverse(&self) -> Self { |
| 17 | 26 | match self { |
| 18 | 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 | 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 | 18 | pub trait StyledExt: Styled + Sized { |
| 19 | 19 | /// Horizontally stacks elements. |
| 20 | 20 | /// |
| @@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines | ||
| 30 | 30 | self.flex().flex_col() |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | + /// Sets the text size using a [`UiTextSize`]. | |
| 33 | 34 | fn text_ui_size(self, size: UiTextSize) -> Self { |
| 34 | 35 | self.text_size(size.rems()) |
| 35 | 36 | } |
| @@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines | ||
| 40 | 41 | /// |
| 41 | 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 | 45 | fn text_ui(self) -> Self { |
| 45 | 46 | self.text_size(UiTextSize::default().rems()) |
| 46 | 47 | } |
| @@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines | ||
| 51 | 52 | /// |
| 52 | 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 | 56 | fn text_ui_sm(self) -> Self { |
| 56 | 57 | self.text_size(UiTextSize::Small.rems()) |
| 57 | 58 | } |
| @@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { · 4 unmodified lines | ||
| 62 | 63 | /// |
| 63 | 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 | 67 | fn text_ui_xs(self) -> Self { |
| 67 | 68 | self.text_size(UiTextSize::XSmall.rems()) |
| 68 | 69 | } |
| @@ -78,7 +79,7 @@ pub trait StyledExt: Styled + Sized { · 9 unmodified lines | ||
| 78 | 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 | 84 | /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` |
| 84 | 85 | /// |
| @@ -87,7 +88,7 @@ pub trait StyledExt: Styled + Sized { · 2 unmodified lines | ||
| 87 | 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 | 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 | 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 | 106 | /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` |
| 106 | 107 | /// |
| @@ -119,26 +120,32 @@ pub trait StyledExt: Styled + Sized { · 12 unmodified lines | ||
| 119 | 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 | 124 | fn debug_bg_red(self) -> Self { |
| 123 | 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 | 129 | fn debug_bg_green(self) -> Self { |
| 127 | 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 | 134 | fn debug_bg_blue(self) -> Self { |
| 131 | 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 | 139 | fn debug_bg_yellow(self) -> Self { |
| 135 | 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 | 144 | fn debug_bg_cyan(self) -> Self { |
| 139 | 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 | 149 | fn debug_bg_magenta(self) -> Self { |
| 143 | 150 | self.bg(hsla(300. / 360., 1., 0.5, 1.)) |
| 144 | 151 | } |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | use gpui::{Hsla, WindowContext}; |
| 2 | 2 | use theme::ActiveTheme; |
| 3 | 3 | |
| 4 | +/// Sets a color that has a consistent meaning across all themes. | |
| 4 | 5 | #[derive(Debug, Default, PartialEq, Copy, Clone)] |
| 5 | 6 | pub enum Color { |
| 6 | 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 | 89 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 89 | 90 | pub enum ElementIndex { |
| 90 | 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 | 42 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] |
| 42 | 43 | pub enum HeadlineSize { |
| 43 | 44 | XSmall, |
| @@ -3,8 +3,6 @@ · 2 unmodified lines | ||
| 3 | 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 | 6 | mod clickable; |
| 9 | 7 | mod components; |
| 10 | 8 | mod disableable; |
| @@ -1,3 +1,5 @@ | ||
| 1 | +//! UI-related utilities (e.g. converting dates to a human-readable form). | |
| 2 | + | |
| 1 | 3 | mod format_distance; |
| 2 | 4 | |
| 3 | 5 | pub use format_distance::*; |
| @@ -7,10 +7,10 @@ pub enum DateTimeType { · 6 unmodified lines | ||
| 7 | 7 | } |
| 8 | 8 | |
| 9 | 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. | |
| 13 | − /// If the DateTimeType is a DateTime<Local>, it will be converted to a NaiveDateTime. | |
| 12 | + /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is. | |
| 13 | + /// If the [`DateTimeType`] is a [`DateTime<Local>`], it will be converted to a [`NaiveDateTime`]. | |
| 14 | 14 | pub fn to_naive(&self) -> NaiveDateTime { |
| 15 | 15 | match self { |
| 16 | 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 | 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 | 74 | /// ## Arguments |
| 75 | 75 | /// |
| 76 | −/// * `date` - A NaiveDateTime object representing the date of interest | |
| 77 | −/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made | |
| 76 | +/// * `date` - A [NaiveDateTime`] object representing the date of interest | |
| 77 | +/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made | |
| 78 | 78 | fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { |
| 79 | 79 | let duration = date.signed_duration_since(base_date); |
| 80 | 80 | -duration.num_seconds() |
| @@ -233,12 +233,12 @@ fn distance_string( · 152 unmodified lines | ||
| 233 | 233 | /// |
| 234 | 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 | 238 | /// # Arguments |
| 239 | 239 | /// |
| 240 | −/// * `date` - The NaiveDateTime to compare. | |
| 241 | −/// * `base_date` - The NaiveDateTime to compare against. | |
| 240 | +/// * `date` - The [`NaiveDateTime`] to compare. | |
| 241 | +/// * `base_date` - The [`NaiveDateTime`] to compare against. | |
| 242 | 242 | /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed |
| 243 | 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 | 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 | 278 | /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed |
| 279 | 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 | 28 | fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>); |
| 29 | 29 | fn size(&self, cx: &WindowContext) -> Pixels; |
| 30 | 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 | 32 | fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; |
| 33 | 33 | fn toggle_action(&self) -> Box<dyn Action>; |
| 34 | 34 | fn icon_label(&self, _: &WindowContext) -> Option<String> { |
| @@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { · 17 unmodified lines | ||
| 52 | 52 | fn set_active(&self, active: bool, cx: &mut WindowContext); |
| 53 | 53 | fn size(&self, cx: &WindowContext) -> Pixels; |
| 54 | 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 | 56 | fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; |
| 57 | 57 | fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>; |
| 58 | 58 | fn icon_label(&self, cx: &WindowContext) -> Option<String>; |
| @@ -104,7 +104,7 @@ where · 45 unmodified lines | ||
| 104 | 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 | 108 | self.read(cx).icon(cx) |
| 109 | 109 | } |
| 110 | 110 | |
| @@ -395,7 +395,6 @@ impl Dock { · 284 unmodified lines | ||
| 395 | 395 | }) |
| 396 | 396 | .ok(); |
| 397 | 397 | } |
| 398 | − // todo!() we do not use this event in the production code (even in zed1), remove it | |
| 399 | 398 | PanelEvent::Activate => { |
| 400 | 399 | if let Some(ix) = this |
| 401 | 400 | .panel_entries |
| @@ -775,7 +774,7 @@ pub mod test { · 373 unmodified lines | ||
| 775 | 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 | 778 | None |
| 780 | 779 | } |
| 781 | 780 | |
| @@ -101,6 +101,10 @@ impl ModalLayer { · 100 unmodified lines | ||
| 101 | 101 | let active_modal = self.active_modal.as_ref()?; |
| 102 | 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 | 110 | impl Render for ModalLayer { |
| @@ -175,7 +175,7 @@ pub mod simple_message_notification { · 174 unmodified lines | ||
| 175 | 175 | }; |
| 176 | 176 | use std::sync::Arc; |
| 177 | 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 | 180 | pub struct MessageNotification { |
| 181 | 181 | message: SharedString, |
| @@ -230,7 +230,7 @@ pub mod simple_message_notification { · 48 unmodified lines | ||
| 230 | 230 | .child( |
| 231 | 231 | div() |
| 232 | 232 | .id("cancel") |
| 233 | − .child(IconElement::new(Icon::Close)) | |
| 233 | + .child(Icon::new(IconName::Close)) | |
| 234 | 234 | .cursor_pointer() |
| 235 | 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 | 252 | pub trait NotifyResultExt { |
| @@ -31,8 +31,8 @@ use std::{ · 30 unmodified lines | ||
| 31 | 31 | use theme::ThemeSettings; |
| 32 | 32 | |
| 33 | 33 | use ui::{ |
| 34 | − prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, | |
| 35 | − Tab, TabBar, TabPosition, Tooltip, | |
| 34 | + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, | |
| 35 | + Label, Tab, TabBar, TabPosition, Tooltip, | |
| 36 | 36 | }; |
| 37 | 37 | use ui::{v_stack, ContextMenu}; |
| 38 | 38 | use util::{maybe, truncate_and_remove_front, ResultExt}; |
| @@ -384,7 +384,7 @@ impl Pane { · 345 unmodified lines | ||
| 384 | 384 | h_stack() |
| 385 | 385 | .gap_2() |
| 386 | 386 | .child( |
| 387 | − IconButton::new("plus", Icon::Plus) | |
| 387 | + IconButton::new("plus", IconName::Plus) | |
| 388 | 388 | .icon_size(IconSize::Small) |
| 389 | 389 | .icon_color(Color::Muted) |
| 390 | 390 | .on_click(cx.listener(|pane, _, cx| { |
| @@ -406,7 +406,7 @@ impl Pane { · 15 unmodified lines | ||
| 406 | 406 | el.child(Self::render_menu_overlay(new_item_menu)) |
| 407 | 407 | }) |
| 408 | 408 | .child( |
| 409 | − IconButton::new("split", Icon::Split) | |
| 409 | + IconButton::new("split", IconName::Split) | |
| 410 | 410 | .icon_size(IconSize::Small) |
| 411 | 411 | .icon_color(Color::Muted) |
| 412 | 412 | .on_click(cx.listener(|pane, _, cx| { |
| @@ -427,11 +427,11 @@ impl Pane { · 14 unmodified lines | ||
| 427 | 427 | ) |
| 428 | 428 | .child({ |
| 429 | 429 | let zoomed = pane.is_zoomed(); |
| 430 | − IconButton::new("toggle_zoom", Icon::Maximize) | |
| 430 | + IconButton::new("toggle_zoom", IconName::Maximize) | |
| 431 | 431 | .icon_size(IconSize::Small) |
| 432 | 432 | .icon_color(Color::Muted) |
| 433 | 433 | .selected(zoomed) |
| 434 | − .selected_icon(Icon::Minimize) | |
| 434 | + .selected_icon(IconName::Minimize) | |
| 435 | 435 | .on_click(cx.listener(|pane, _, cx| { |
| 436 | 436 | pane.toggle_zoom(&crate::ToggleZoom, cx); |
| 437 | 437 | })) |
| @@ -1570,7 +1570,7 @@ impl Pane { · 1132 unmodified lines | ||
| 1570 | 1570 | }) |
| 1571 | 1571 | .start_slot::<Indicator>(indicator) |
| 1572 | 1572 | .end_slot( |
| 1573 | − IconButton::new("close tab", Icon::Close) | |
| 1573 | + IconButton::new("close tab", IconName::Close) | |
| 1574 | 1574 | .icon_color(Color::Muted) |
| 1575 | 1575 | .size(ButtonSize::None) |
| 1576 | 1576 | .icon_size(IconSize::XSmall) |
| @@ -1676,7 +1676,7 @@ impl Pane { · 99 unmodified lines | ||
| 1676 | 1676 | h_stack() |
| 1677 | 1677 | .gap_2() |
| 1678 | 1678 | .child( |
| 1679 | − IconButton::new("navigate_backward", Icon::ArrowLeft) | |
| 1679 | + IconButton::new("navigate_backward", IconName::ArrowLeft) | |
| 1680 | 1680 | .icon_size(IconSize::Small) |
| 1681 | 1681 | .on_click({ |
| 1682 | 1682 | let view = cx.view().clone(); |
| @@ -1686,7 +1686,7 @@ impl Pane { · 3 unmodified lines | ||
| 1686 | 1686 | .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)), |
| 1687 | 1687 | ) |
| 1688 | 1688 | .child( |
| 1689 | − IconButton::new("navigate_forward", Icon::ArrowRight) | |
| 1689 | + IconButton::new("navigate_forward", IconName::ArrowRight) | |
| 1690 | 1690 | .icon_size(IconSize::Small) |
| 1691 | 1691 | .on_click({ |
| 1692 | 1692 | let view = cx.view().clone(); |
| @@ -12,7 +12,7 @@ use serde::Deserialize; · 11 unmodified lines | ||
| 12 | 12 | use std::sync::Arc; |
| 13 | 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 | 16 | const HORIZONTAL_MIN_SIZE: f32 = 80.; |
| 17 | 17 | const VERTICAL_MIN_SIZE: f32 = 100.; |
| 18 | 18 | |
| @@ -579,12 +579,15 @@ mod element { · 560 unmodified lines | ||
| 579 | 579 | Size, Style, WeakView, WindowContext, |
| 580 | 580 | }; |
| 581 | 581 | use parking_lot::Mutex; |
| 582 | + use settings::Settings; | |
| 582 | 583 | use smallvec::SmallVec; |
| 583 | 584 | use ui::prelude::*; |
| 584 | 585 | use util::ResultExt; |
| 585 | 586 | |
| 586 | 587 | use crate::Workspace; |
| 587 | 588 | |
| 589 | + use crate::WorkspaceSettings; | |
| 590 | + | |
| 588 | 591 | use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; |
| 589 | 592 | |
| 590 | 593 | const DIVIDER_SIZE: f32 = 1.0; |
| @@ -704,7 +707,6 @@ mod element { · 113 unmodified lines | ||
| 704 | 707 | proposed_current_pixel_change -= current_pixel_change; |
| 705 | 708 | } |
| 706 | 709 | |
| 707 | − // todo!(schedule serialize) | |
| 708 | 710 | workspace |
| 709 | 711 | .update(cx, |this, cx| this.schedule_serialize(cx)) |
| 710 | 712 | .log_err(); |
| @@ -834,20 +836,39 @@ mod element { · 123 unmodified lines | ||
| 834 | 836 | debug_assert!(flexes.len() == len); |
| 835 | 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 | 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 | 855 | let mut bounding_boxes = self.bounding_boxes.lock(); |
| 841 | 856 | bounding_boxes.clear(); |
| 842 | 857 | |
| 843 | 858 | for (ix, child) in self.children.iter_mut().enumerate() { |
| 844 | − //todo!(active_pane_magnification) | |
| 845 | − // If using active pane magnification, need to switch to using | |
| 846 | − // 1 for all non-active panes, and then the magnification for the | |
| 847 | − // active pane. | |
| 859 | + let child_flex = active_pane_magnification | |
| 860 | + .map(|magnification| { | |
| 861 | + if self.active_pane_ix == Some(ix) { | |
| 862 | + magnification | |
| 863 | + } else { | |
| 864 | + 1. | |
| 865 | + } | |
| 866 | + }) | |
| 867 | + .unwrap_or_else(|| flexes[ix]); | |
| 868 | + | |
| 848 | 869 | let child_size = bounds |
| 849 | 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 | 873 | let child_bounds = Bounds { |
| 853 | 874 | origin, |
| @@ -857,20 +878,23 @@ mod element { · 3 unmodified lines | ||
| 857 | 878 | cx.with_z_index(0, |cx| { |
| 858 | 879 | child.draw(origin, child_size.into(), cx); |
| 859 | 880 | }); |
| 860 | − cx.with_z_index(1, |cx| { | |
| 861 | − if ix < len - 1 { | |
| 862 | − Self::push_handle( | |
| 863 | − self.flexes.clone(), | |
| 864 | − state.clone(), | |
| 865 | − self.axis, | |
| 866 | − ix, | |
| 867 | − child_bounds, | |
| 868 | − bounds, | |
| 869 | − self.workspace.clone(), | |
| 870 | − cx, | |
| 871 | − ); | |
| 872 | − } | |
| 873 | − }); | |
| 881 | + | |
| 882 | + if active_pane_magnification.is_none() { | |
| 883 | + cx.with_z_index(1, |cx| { | |
| 884 | + if ix < len - 1 { | |
| 885 | + Self::push_handle( | |
| 886 | + self.flexes.clone(), | |
| 887 | + state.clone(), | |
| 888 | + self.axis, | |
| 889 | + ix, | |
| 890 | + child_bounds, | |
| 891 | + bounds, | |
| 892 | + self.workspace.clone(), | |
| 893 | + cx, | |
| 894 | + ); | |
| 895 | + } | |
| 896 | + }); | |
| 897 | + } | |
| 874 | 898 | |
| 875 | 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 | 12 | WindowContext, |
| 13 | 13 | }; |
| 14 | 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 | 17 | pub enum Event { |
| 18 | 18 | Close, |
| @@ -100,7 +100,7 @@ impl Item for SharedScreen { · 81 unmodified lines | ||
| 100 | 100 | ) -> gpui::AnyElement { |
| 101 | 101 | h_stack() |
| 102 | 102 | .gap_1() |
| 103 | − .child(IconElement::new(Icon::Screen)) | |
| 103 | + .child(Icon::new(IconName::Screen)) | |
| 104 | 104 | .child( |
| 105 | 105 | Label::new(format!("{}'s screen", self.user.github_login)).color(if selected { |
| 106 | 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 | 136 | impl Toolbar { |
| 213 | 137 | pub fn new() -> Self { |
| 214 | 138 | Self { |
| @@ -312,10 +236,3 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> { · 97 unmodified lines | ||
| 312 | 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 | 943 | cx: &mut ViewContext<Workspace>, |
| 944 | 944 | ) -> Task<Result<()>> { |
| 945 | 945 | let to_load = if let Some(pane) = pane.upgrade() { |
| 946 | − // todo!("focus") | |
| 947 | − // cx.focus(&pane); | |
| 948 | − | |
| 949 | 946 | pane.update(cx, |pane, cx| { |
| 947 | + pane.focus(cx); | |
| 950 | 948 | loop { |
| 951 | 949 | // Retrieve the weak item handle from the history. |
| 952 | 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") | |
| 1635 | − // cx.focus_self(); | |
| 1632 | + cx.focus_self(); | |
| 1636 | 1633 | cx.notify(); |
| 1637 | 1634 | self.serialize_workspace(cx); |
| 1638 | 1635 | } |
| @@ -1713,6 +1710,7 @@ impl Workspace { · 74 unmodified lines | ||
| 1713 | 1710 | cx.notify(); |
| 1714 | 1711 | } |
| 1715 | 1712 | |
| 1713 | + // todo!() | |
| 1716 | 1714 | // #[cfg(any(test, feature = "test-support"))] |
| 1717 | 1715 | // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> { |
| 1718 | 1716 | // self.zoomed.and_then(|view| view.upgrade(cx)) |
| @@ -2992,7 +2990,6 @@ impl Workspace { · 1273 unmodified lines | ||
| 2992 | 2990 | cx.notify(); |
| 2993 | 2991 | } |
| 2994 | 2992 | |
| 2995 | − #[allow(unused)] | |
| 2996 | 2993 | fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) { |
| 2997 | 2994 | self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { |
| 2998 | 2995 | cx.background_executor() |
| @@ -3386,6 +3383,10 @@ impl Workspace { · 387 unmodified lines | ||
| 3386 | 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 | 3390 | pub fn active_modal<V: ManagedView + 'static>( |
| 3390 | 3391 | &mut self, |
| 3391 | 3392 | cx: &ViewContext<Self>, |
| @@ -764,7 +764,6 @@ fn open_bundled_file( · 763 unmodified lines | ||
| 764 | 764 | .detach_and_log_err(cx); |
| 765 | 765 | } |
| 766 | 766 | |
| 767 | −// todo!() | |
| 768 | 767 | #[cfg(test)] |
| 769 | 768 | mod tests { |
| 770 | 769 | use super::*; |