Merge pull request #2146 from zed-industries/feedback-editor-polish

Joseph T. Lyons created

Feedback editor polish

Change summary

crates/feedback/src/feedback_editor.rs | 62 ++++++++++++++++++++++++
crates/gpui/src/elements.rs            | 10 +++
crates/gpui/src/elements/clipped.rs    | 69 ++++++++++++++++++++++++++++
crates/theme/src/theme.rs              |  1 
crates/zed/src/zed.rs                  |  4 +
styles/src/styleTree/feedback.ts       |  4 
6 files changed, 144 insertions(+), 6 deletions(-)

Detailed changes

crates/feedback/src/feedback_editor.rs 🔗

@@ -31,7 +31,6 @@ use workspace::{
 use crate::system_specs::SystemSpecs;
 
 const FEEDBACK_CHAR_LIMIT: RangeInclusive<usize> = 10..=5000;
-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.";
 
@@ -117,7 +116,6 @@ impl FeedbackEditor {
         let editor = cx.add_view(|cx| {
             let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
             editor.set_vertical_scroll_margin(5, cx);
-            editor.set_placeholder_text(FEEDBACK_PLACEHOLDER_TEXT, cx);
             editor
         });
 
@@ -483,6 +481,13 @@ impl View for SubmitFeedbackButton {
         .aligned()
         .contained()
         .with_margin_left(theme.feedback.button_margin)
+        .with_tooltip::<Self, _>(
+            0,
+            "cmd-s".into(),
+            Some(Box::new(SubmitFeedback)),
+            theme.tooltip.clone(),
+            cx,
+        )
         .boxed()
     }
 }
@@ -504,3 +509,56 @@ impl ToolbarItemView for SubmitFeedbackButton {
         }
     }
 }
+
+pub struct FeedbackInfoText {
+    active_item: Option<ViewHandle<FeedbackEditor>>,
+}
+
+impl FeedbackInfoText {
+    pub fn new() -> Self {
+        Self {
+            active_item: Default::default(),
+        }
+    }
+}
+
+impl Entity for FeedbackInfoText {
+    type Event = ();
+}
+
+impl View for FeedbackInfoText {
+    fn ui_name() -> &'static str {
+        "FeedbackInfoText"
+    }
+
+    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+        let theme = cx.global::<Settings>().theme.clone();
+        let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub.";
+        Label::new(text.to_string(), theme.feedback.info_text.text.clone())
+            .contained()
+            .aligned()
+            .left()
+            .clipped()
+            .boxed()
+    }
+}
+
+impl ToolbarItemView for FeedbackInfoText {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> workspace::ToolbarItemLocation {
+        cx.notify();
+        if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::<FeedbackEditor>())
+        {
+            self.active_item = Some(feedback_editor);
+            ToolbarItemLocation::PrimaryLeft {
+                flex: Some((1., false)),
+            }
+        } else {
+            self.active_item = None;
+            ToolbarItemLocation::Hidden
+        }
+    }
+}

crates/gpui/src/elements.rs 🔗

@@ -1,5 +1,6 @@
 mod align;
 mod canvas;
+mod clipped;
 mod constrained_box;
 mod container;
 mod empty;
@@ -19,12 +20,12 @@ mod text;
 mod tooltip;
 mod uniform_list;
 
-use self::expanded::Expanded;
 pub use self::{
     align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*,
     keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
     stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
 };
+use self::{clipped::Clipped, expanded::Expanded};
 pub use crate::presenter::ChildView;
 use crate::{
     geometry::{
@@ -135,6 +136,13 @@ pub trait Element {
         Align::new(self.boxed())
     }
 
+    fn clipped(self) -> Clipped
+    where
+        Self: 'static + Sized,
+    {
+        Clipped::new(self.boxed())
+    }
+
     fn contained(self) -> Container
     where
         Self: 'static + Sized,

crates/gpui/src/elements/clipped.rs 🔗

@@ -0,0 +1,69 @@
+use std::ops::Range;
+
+use pathfinder_geometry::{rect::RectF, vector::Vector2F};
+use serde_json::json;
+
+use crate::{
+    json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext,
+    SizeConstraint,
+};
+
+pub struct Clipped {
+    child: ElementBox,
+}
+
+impl Clipped {
+    pub fn new(child: ElementBox) -> Self {
+        Self { child }
+    }
+}
+
+impl Element for Clipped {
+    type LayoutState = ();
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        cx: &mut LayoutContext,
+    ) -> (Vector2F, Self::LayoutState) {
+        (self.child.layout(constraint, cx), ())
+    }
+
+    fn paint(
+        &mut self,
+        bounds: RectF,
+        visible_bounds: RectF,
+        _: &mut Self::LayoutState,
+        cx: &mut PaintContext,
+    ) -> Self::PaintState {
+        cx.scene.push_layer(Some(bounds));
+        self.child.paint(bounds.origin(), visible_bounds, cx);
+        cx.scene.pop_layer();
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        _: RectF,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        cx: &MeasurementContext,
+    ) -> Option<RectF> {
+        self.child.rect_for_text_range(range_utf16, cx)
+    }
+
+    fn debug(
+        &self,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        cx: &DebugContext,
+    ) -> json::Value {
+        json!({
+            "type": "Clipped",
+            "child": self.child.debug(cx)
+        })
+    }
+}

crates/theme/src/theme.rs 🔗

@@ -811,6 +811,7 @@ pub struct TerminalStyle {
 pub struct FeedbackStyle {
     pub submit_button: Interactive<ContainedText>,
     pub button_margin: f32,
+    pub info_text: ContainedText,
 }
 
 #[derive(Clone, Deserialize, Default)]

crates/zed/src/zed.rs 🔗

@@ -11,7 +11,7 @@ use collections::VecDeque;
 pub use editor;
 use editor::{Editor, MultiBuffer};
 
-use feedback::feedback_editor::SubmitFeedbackButton;
+use feedback::feedback_editor::{FeedbackInfoText, SubmitFeedbackButton};
 use futures::StreamExt;
 use gpui::{
     actions,
@@ -290,6 +290,8 @@ pub fn initialize_workspace(
                         toolbar.add_item(project_search_bar, cx);
                         let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new());
                         toolbar.add_item(submit_feedback_button, cx);
+                        let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
+                        toolbar.add_item(feedback_info_text, cx);
                     })
                 });
             }

styles/src/styleTree/feedback.ts 🔗

@@ -5,7 +5,6 @@ import { background, border, text } from "./components";
 export default function feedback(colorScheme: ColorScheme) {
   let layer = colorScheme.highest;
 
-  // Currently feedback only needs style for the submit feedback button
   return {
     submit_button: {
       ...text(layer, "mono", "on"),
@@ -32,6 +31,7 @@ export default function feedback(colorScheme: ColorScheme) {
         border: border(layer, "on", "hovered"),
       },
     },
-    button_margin: 8
+    button_margin: 8,
+    info_text: text(layer, "sans", "default", { size: "xs" }),
   };
 }