From d4d9a142fcb2507f3a892b225d84e95cda3d6470 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 6 Feb 2023 17:41:36 -0500 Subject: [PATCH 1/4] Implement a button for submitting feedback Co-Authored-By: Kay Simmons <3323631+Kethku@users.noreply.github.com> --- crates/feedback/src/feedback_editor.rs | 98 +++++++++++++++++++++----- crates/theme/src/theme.rs | 7 ++ crates/zed/src/zed.rs | 5 +- styles/src/styleTree/app.ts | 2 + styles/src/styleTree/feedback.ts | 37 ++++++++++ 5 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 4120f38cfd..bd9f32e73c 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -25,7 +25,7 @@ use settings::Settings; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - AppState, StatusItemView, Workspace, + AppState, StatusItemView, ToolbarItemLocation, ToolbarItemView, Workspace, }; use crate::system_specs::SystemSpecs; @@ -35,7 +35,7 @@ const FEEDBACK_PLACEHOLDER_TEXT: &str = "Save to submit feedback as Markdown."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; -actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); +actions!(feedback, [GiveFeedback, SubmitFeedback]); pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut MutableAppContext) { cx.add_action({ @@ -43,17 +43,27 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx); } }); + + cx.add_async_action( + |toolbar_button: &mut ToolbarButton, _: &SubmitFeedback, cx| { + if let Some(active_item) = toolbar_button.active_item.as_ref() { + Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx))) + } else { + None + } + }, + ); } -pub struct FeedbackButton; +pub struct StatusBarButton; -impl Entity for FeedbackButton { +impl Entity for StatusBarButton { type Event = (); } -impl View for FeedbackButton { +impl View for StatusBarButton { fn ui_name() -> &'static str { - "FeedbackButton" + "StatusBarButton" } fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { @@ -77,7 +87,7 @@ impl View for FeedbackButton { } } -impl StatusItemView for FeedbackButton { +impl StatusItemView for StatusBarButton { fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} } @@ -120,11 +130,7 @@ impl FeedbackEditor { } } - fn handle_save( - &mut self, - _: ModelHandle, - cx: &mut ViewContext, - ) -> Task> { + fn handle_save(&mut self, cx: &mut ViewContext) -> Task> { let feedback_text = self.editor.read(cx).text(cx); let feedback_char_count = feedback_text.chars().count(); let feedback_text = feedback_text.trim().to_string(); @@ -304,19 +310,19 @@ impl Item for FeedbackEditor { fn save( &mut self, - project: ModelHandle, + _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project, cx) + self.handle_save(cx) } fn save_as( &mut self, - project: ModelHandle, + _: ModelHandle, _: std::path::PathBuf, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project, cx) + self.handle_save(cx) } fn reload( @@ -435,3 +441,63 @@ impl SearchableItem for FeedbackEditor { .update(cx, |editor, cx| editor.active_match_index(matches, cx)) } } + +pub struct ToolbarButton { + active_item: Option>, +} + +impl ToolbarButton { + pub fn new() -> Self { + Self { + active_item: Default::default(), + } + } +} + +impl Entity for ToolbarButton { + type Event = (); +} + +impl View for ToolbarButton { + fn ui_name() -> &'static str { + "ToolbarButton" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + enum SubmitFeedbackButton {} + MouseEventHandler::::new(0, cx, |state, _| { + let style = theme.feedback.submit_button.style_for(state, false); + Label::new("Submit as Markdown".into(), style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(SubmitFeedback) + }) + .aligned() + .contained() + .with_margin_left(theme.feedback.button_margin) + .boxed() + } +} + +impl ToolbarItemView for ToolbarButton { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryRight { flex: None } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e463310b98..b8ec3e0329 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -36,6 +36,7 @@ pub struct Theme { pub incoming_call_notification: IncomingCallNotification, pub tooltip: TooltipStyle, pub terminal: TerminalStyle, + pub feedback: FeedbackStyle, pub color_scheme: ColorScheme, } @@ -806,6 +807,12 @@ pub struct TerminalStyle { pub dim_foreground: Color, } +#[derive(Clone, Deserialize, Default)] +pub struct FeedbackStyle { + pub submit_button: Interactive, + pub button_margin: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct ColorScheme { pub name: String, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fc7f96a384..c118951b34 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,6 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use feedback::feedback_editor::ToolbarButton; use futures::StreamExt; use gpui::{ actions, @@ -287,6 +288,8 @@ pub fn initialize_workspace( toolbar.add_item(buffer_search_bar, cx); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); toolbar.add_item(project_search_bar, cx); + let submit_feedback_button = cx.add_view(|_| ToolbarButton::new()); + toolbar.add_item(submit_feedback_button, cx); }) }); } @@ -344,7 +347,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_editor::FeedbackButton {}); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::StatusBarButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 267d830506..5d04050fe1 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -19,6 +19,7 @@ import terminal from "./terminal"; import contactList from "./contactList"; import incomingCallNotification from "./incomingCallNotification"; import { ColorScheme } from "../themes/common/colorScheme"; +import feedback from "./feedback"; export default function app(colorScheme: ColorScheme): Object { return { @@ -51,6 +52,7 @@ export default function app(colorScheme: ColorScheme): Object { simpleMessageNotification: simpleMessageNotification(colorScheme), tooltip: tooltip(colorScheme), terminal: terminal(colorScheme), + feedback: feedback(colorScheme), colorScheme: { ...colorScheme, players: Object.values(colorScheme.players), diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts new file mode 100644 index 0000000000..daee2bb969 --- /dev/null +++ b/styles/src/styleTree/feedback.ts @@ -0,0 +1,37 @@ + +import { ColorScheme } from "../themes/common/colorScheme"; +import { background, border, text } from "./components"; + +export default function search(colorScheme: ColorScheme) { + let layer = colorScheme.highest; + + // Currently feedback only needs style for the submit feedback button + return { + submit_button: { + ...text(layer, "mono", "on"), + background: background(layer, "on"), + cornerRadius: 6, + border: border(layer, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + clicked: { + ...text(layer, "mono", "on", "pressed"), + background: background(layer, "on", "pressed"), + border: border(layer, "on", "pressed"), + }, + hover: { + ...text(layer, "mono", "on", "hovered"), + background: background(layer, "on", "hovered"), + border: border(layer, "on", "hovered"), + }, + }, + button_margin: 8 + }; +} From 8228618b9ef4ea1d3fff3134862f501ec5ad31f4 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 6 Feb 2023 18:19:15 -0500 Subject: [PATCH 2/4] Correct theme function name --- styles/src/styleTree/feedback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index daee2bb969..eb0f61ecfd 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/styleTree/feedback.ts @@ -2,7 +2,7 @@ import { ColorScheme } from "../themes/common/colorScheme"; import { background, border, text } from "./components"; -export default function search(colorScheme: ColorScheme) { +export default function feedback(colorScheme: ColorScheme) { let layer = colorScheme.highest; // Currently feedback only needs style for the submit feedback button From 7cef4a5d405034892ce998d5f83169aa80fbeb5a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 13:39:48 -0500 Subject: [PATCH 3/4] Allocate theme struct directly into the heap Co-Authored-By: Max Brunsfeld Co-Authored-By: Mikayla Maki --- crates/theme/src/theme_registry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index cc5e5490af..d47625289b 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -55,13 +55,13 @@ impl ThemeRegistry { .load(&asset_path) .with_context(|| format!("failed to load theme file {}", asset_path))?; - let mut theme: Theme = fonts::with_font_cache(self.font_cache.clone(), || { + // Allocate into the heap directly, the Theme struct is too large to fit in the stack. + let mut theme: Arc = fonts::with_font_cache(self.font_cache.clone(), || { serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(&theme_json)) })?; // Reset name to be the file path, so that we can use it to access the stored themes - theme.meta.name = name.into(); - let theme = Arc::new(theme); + Arc::get_mut(&mut theme).unwrap().meta.name = name.into(); self.themes.lock().insert(name.to_string(), theme.clone()); Ok(theme) } From 317eb7535cbaa148b01d9fa28c4392e2c5dea272 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 21:08:07 -0500 Subject: [PATCH 4/4] Fix variable names --- crates/feedback/src/feedback_editor.rs | 26 +++++++++++++------------- crates/zed/src/zed.rs | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index bd9f32e73c..8a5e2038de 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -45,8 +45,8 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl }); cx.add_async_action( - |toolbar_button: &mut ToolbarButton, _: &SubmitFeedback, cx| { - if let Some(active_item) = toolbar_button.active_item.as_ref() { + |submit_feedback_button: &mut SubmitFeedbackButton, _: &SubmitFeedback, cx| { + if let Some(active_item) = submit_feedback_button.active_item.as_ref() { Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx))) } else { None @@ -55,15 +55,15 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl ); } -pub struct StatusBarButton; +pub struct DeployFeedbackButton; -impl Entity for StatusBarButton { +impl Entity for DeployFeedbackButton { type Event = (); } -impl View for StatusBarButton { +impl View for DeployFeedbackButton { fn ui_name() -> &'static str { - "StatusBarButton" + "DeployFeedbackButton" } fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { @@ -87,7 +87,7 @@ impl View for StatusBarButton { } } -impl StatusItemView for StatusBarButton { +impl StatusItemView for DeployFeedbackButton { fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} } @@ -442,11 +442,11 @@ impl SearchableItem for FeedbackEditor { } } -pub struct ToolbarButton { +pub struct SubmitFeedbackButton { active_item: Option>, } -impl ToolbarButton { +impl SubmitFeedbackButton { pub fn new() -> Self { Self { active_item: Default::default(), @@ -454,13 +454,13 @@ impl ToolbarButton { } } -impl Entity for ToolbarButton { +impl Entity for SubmitFeedbackButton { type Event = (); } -impl View for ToolbarButton { +impl View for SubmitFeedbackButton { fn ui_name() -> &'static str { - "ToolbarButton" + "SubmitFeedbackButton" } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { @@ -484,7 +484,7 @@ impl View for ToolbarButton { } } -impl ToolbarItemView for ToolbarButton { +impl ToolbarItemView for SubmitFeedbackButton { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c118951b34..60ec440cc8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,7 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; -use feedback::feedback_editor::ToolbarButton; +use feedback::feedback_editor::SubmitFeedbackButton; use futures::StreamExt; use gpui::{ actions, @@ -288,7 +288,7 @@ pub fn initialize_workspace( toolbar.add_item(buffer_search_bar, cx); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); toolbar.add_item(project_search_bar, cx); - let submit_feedback_button = cx.add_view(|_| ToolbarButton::new()); + let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); toolbar.add_item(submit_feedback_button, cx); }) }); @@ -347,7 +347,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_editor::StatusBarButton {}); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::DeployFeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx);