diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 9363cb2e3b..7681b160e9 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -31,7 +31,6 @@ use workspace::{ use crate::system_specs::SystemSpecs; const FEEDBACK_CHAR_LIMIT: RangeInclusive = 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::( + 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>, +} + +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) -> ElementBox { + let theme = cx.global::().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, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryLeft { + flex: Some((1., false)), + } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 9f11f09f8e..41a802feb3 100644 --- a/crates/gpui/src/elements.rs +++ b/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, diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs new file mode 100644 index 0000000000..2ee7b542a8 --- /dev/null +++ b/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, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + 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) + }) + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b8ec3e0329..bc338bbe26 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -811,6 +811,7 @@ pub struct TerminalStyle { pub struct FeedbackStyle { pub submit_button: Interactive, pub button_margin: f32, + pub info_text: ContainedText, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d1f861cd93..6c92d4c0b8 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::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); }) }); } diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index eb0f61ecfd..46cb867ad9 100644 --- a/styles/src/styleTree/feedback.ts +++ b/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" }), }; }