Detailed changes
@@ -742,6 +742,14 @@
"alt-up": "git_panel::FocusChanges"
}
},
+ {
+ "context": "GitCommit > Editor",
+ "use_key_equivalents": true,
+ "bindings": {
+ "enter": "editor::Newline",
+ "ctrl-enter": "git::Commit"
+ }
+ },
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -157,7 +157,8 @@
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
- "alt-enter": "editor::OpenSelectionsInMultibuffer"
+ "alt-enter": "editor::OpenSelectionsInMultibuffer",
+ "cmd-g": "git::Commit"
}
},
{
@@ -743,22 +744,22 @@
}
},
{
- "context": "GitCommit > Editor",
+ "context": "GitPanel > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
- "cmd-enter": "git::Commit"
+ "cmd-enter": "git::Commit",
+ "tab": "git_panel::FocusChanges",
+ "shift-tab": "git_panel::FocusChanges",
+ "alt-up": "git_panel::FocusChanges"
}
},
{
- "context": "GitPanel > Editor",
+ "context": "GitCommit > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
- "cmd-enter": "git::Commit",
- "tab": "git_panel::FocusChanges",
- "shift-tab": "git_panel::FocusChanges",
- "alt-up": "git_panel::FocusChanges"
+ "cmd-enter": "git::Commit"
}
},
{
@@ -1234,8 +1234,8 @@ impl ContextEditor {
.px_1()
.mr_0p5()
.border_1()
- .border_color(theme::color_alpha(colors.border_variant, 0.6))
- .bg(theme::color_alpha(colors.element_background, 0.6))
+ .border_color(colors.border_variant.alpha(0.6))
+ .bg(colors.element_background.alpha(0.6))
.child("esc"),
)
.child("to cancel")
@@ -53,10 +53,7 @@ impl RenderOnce for ExtensionCard {
.size_full()
.items_center()
.justify_center()
- .bg(theme::color_alpha(
- cx.theme().colors().elevated_surface_background,
- 0.8,
- ))
+ .bg(cx.theme().colors().elevated_surface_background.alpha(0.8))
.child(Label::new("Overridden by dev extension.")),
)
}),
@@ -4,13 +4,17 @@ use crate::git_panel::{commit_message_editor, GitPanel};
use crate::repository_selector::RepositorySelector;
use anyhow::Result;
use git::Commit;
+use language::language_settings::LanguageSettings;
use language::Buffer;
-use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
+use panel::{
+ panel_button, panel_editor_container, panel_editor_style, panel_filled_button,
+ panel_icon_button,
+};
use settings::Settings;
use theme::ThemeSettings;
-use ui::{prelude::*, Tooltip};
+use ui::{prelude::*, KeybindingHint, Tooltip};
-use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
+use editor::{Direction, Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer};
use gpui::*;
use project::git::Repository;
use project::{Fs, Project};
@@ -18,6 +22,8 @@ use std::sync::Arc;
use workspace::dock::{Dock, DockPosition, PanelHandle};
use workspace::{ModalView, Workspace};
+// actions!(commit_modal, [NextSuggestion, PrevSuggestion]);
+
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
@@ -32,6 +38,8 @@ pub struct CommitModal {
git_panel: Entity<GitPanel>,
commit_editor: Entity<Editor>,
restore_dock: RestoreDock,
+ current_suggestion: Option<usize>,
+ suggested_messages: Vec<SharedString>,
}
impl Focusable for CommitModal {
@@ -114,6 +122,7 @@ impl CommitModal {
cx: &mut Context<Self>,
) -> Self {
let panel = git_panel.read(cx);
+ let suggested_message = panel.suggest_commit_message();
let commit_editor = git_panel.update(cx, |git_panel, cx| {
git_panel.set_modal_open(true, cx);
@@ -122,36 +131,276 @@ impl CommitModal {
cx.new(|cx| commit_message_editor(buffer, project.clone(), false, window, cx))
});
+ let commit_message = commit_editor.read(cx).text(cx);
+
+ if let Some(suggested_message) = suggested_message {
+ if commit_message.is_empty() {
+ commit_editor.update(cx, |editor, cx| {
+ editor.set_text(suggested_message, window, cx);
+ editor.select_all(&Default::default(), window, cx);
+ });
+ } else {
+ if commit_message.as_str().trim() == suggested_message.trim() {
+ commit_editor.update(cx, |editor, cx| {
+ // select the message to make it easy to delete
+ editor.select_all(&Default::default(), window, cx);
+ });
+ }
+ }
+ }
+
Self {
git_panel,
commit_editor,
restore_dock,
+ current_suggestion: None,
+ suggested_messages: vec![],
}
}
+ /// Returns container `(width, x padding, border radius)`
+ fn container_properties(&self, window: &mut Window, cx: &mut Context<Self>) -> (f32, f32, f32) {
+ // TODO: Let's set the width based on your set wrap guide if possible
+
+ // let settings = EditorSettings::get_global(cx);
+
+ // let first_wrap_guide = self
+ // .commit_editor
+ // .read(cx)
+ // .wrap_guides(cx)
+ // .iter()
+ // .next()
+ // .map(|(guide, active)| if *active { Some(*guide) } else { None })
+ // .flatten();
+
+ // let preferred_width = if let Some(guide) = first_wrap_guide {
+ // guide
+ // } else {
+ // 80
+ // };
+
+ let border_radius = 16.0;
+
+ let preferred_width = 50; // (chars wide)
+
+ let mut width = 460.0;
+ let padding_x = 16.0;
+
+ let mut snapshot = self
+ .commit_editor
+ .update(cx, |editor, cx| editor.snapshot(window, cx));
+ let style = window.text_style().clone();
+
+ let font_id = window.text_system().resolve_font(&style.font());
+ let font_size = style.font_size.to_pixels(window.rem_size());
+ let line_height = style.line_height_in_pixels(window.rem_size());
+ if let Ok(em_width) = window.text_system().em_width(font_id, font_size) {
+ width = preferred_width as f32 * em_width.0 + (padding_x * 2.0);
+ cx.notify();
+ }
+
+ // cx.notify();
+
+ (width, padding_x, border_radius)
+ }
+
+ // fn cycle_suggested_messages(&mut self, direction: Direction, cx: &mut Context<Self>) {
+ // let new_index = match direction {
+ // Direction::Next => {
+ // (self.current_suggestion.unwrap_or(0) + 1).rem_euclid(self.suggested_messages.len())
+ // }
+ // Direction::Prev => {
+ // (self.current_suggestion.unwrap_or(0) + self.suggested_messages.len() - 1)
+ // .rem_euclid(self.suggested_messages.len())
+ // }
+ // };
+ // self.current_suggestion = Some(new_index);
+
+ // cx.notify();
+ // }
+
+ // fn next_suggestion(&mut self, _: &NextSuggestion, window: &mut Window, cx: &mut Context<Self>) {
+ // self.current_suggestion = Some(1);
+ // self.apply_suggestion(window, cx);
+ // }
+
+ // fn prev_suggestion(&mut self, _: &PrevSuggestion, window: &mut Window, cx: &mut Context<Self>) {
+ // self.current_suggestion = Some(0);
+ // self.apply_suggestion(window, cx);
+ // }
+
+ // fn set_commit_message(&mut self, message: &str, window: &mut Window, cx: &mut Context<Self>) {
+ // self.commit_editor.update(cx, |editor, cx| {
+ // editor.set_text(message.to_string(), window, cx)
+ // });
+ // self.current_suggestion = Some(0);
+ // cx.notify();
+ // }
+
+ // fn apply_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ // let suggested_messages = self.suggested_messages.clone();
+
+ // if let Some(suggestion) = self.current_suggestion {
+ // let suggested_message = &suggested_messages[suggestion];
+
+ // self.set_commit_message(suggested_message, window, cx);
+ // }
+
+ // cx.notify();
+ // }
+
+ fn commit_editor_element(&self, window: &mut Window, cx: &mut Context<Self>) -> EditorElement {
+ let mut editor = self.commit_editor.clone();
+
+ let editor_style = panel_editor_style(true, window, cx);
+
+ EditorElement::new(&self.commit_editor, editor_style)
+ }
+
pub fn render_commit_editor(
&self,
name_and_email: Option<(SharedString, SharedString)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
- let editor = self.commit_editor.clone();
+ let (width, padding_x, modal_border_radius) = self.container_properties(window, cx);
+
+ let border_radius = modal_border_radius - padding_x / 2.0;
- let panel_editor_style = panel_editor_style(true, window, cx);
+ let editor = self.commit_editor.clone();
+ let editor_focus_handle = editor.focus_handle(cx);
let settings = ThemeSettings::get_global(cx);
let line_height = relative(settings.buffer_line_height.value())
.to_pixels(settings.buffer_font_size(cx).into(), window.rem_size());
+ let mut snapshot = self
+ .commit_editor
+ .update(cx, |editor, cx| editor.snapshot(window, cx));
+ let style = window.text_style().clone();
+
+ let font_id = window.text_system().resolve_font(&style.font());
+ let font_size = style.font_size.to_pixels(window.rem_size());
+ let line_height = style.line_height_in_pixels(window.rem_size());
+ let em_width = window.text_system().em_width(font_id, font_size);
+
+ let (branch, tooltip, commit_label, co_authors) =
+ self.git_panel.update(cx, |git_panel, cx| {
+ let branch = git_panel
+ .active_repository
+ .as_ref()
+ .and_then(|repo| repo.read(cx).current_branch().map(|b| b.name.clone()))
+ .unwrap_or_else(|| "<no branch>".into());
+ let tooltip = if git_panel.has_staged_changes() {
+ "Commit staged changes"
+ } else {
+ "Commit changes to tracked files"
+ };
+ let title = if git_panel.has_staged_changes() {
+ "Commit"
+ } else {
+ "Commit Tracked"
+ };
+ let co_authors = git_panel.render_co_authors(cx);
+ (branch, tooltip, title, co_authors)
+ });
+
+ let branch_selector = panel_button(branch)
+ .icon(IconName::GitBranch)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Placeholder)
+ .color(Color::Muted)
+ .icon_position(IconPosition::Start)
+ .tooltip(Tooltip::for_action_title(
+ "Switch Branch",
+ &zed_actions::git::Branch,
+ ))
+ .on_click(cx.listener(|_, _, window, cx| {
+ window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
+ }))
+ .style(ButtonStyle::Transparent);
+
+ let changes_count = self.git_panel.read(cx).total_staged_count();
+
+ let close_kb_hint =
+ if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
+ Some(
+ KeybindingHint::new(close_kb, cx.theme().colors().editor_background)
+ .suffix("Cancel"),
+ )
+ } else {
+ None
+ };
+
+ let fake_commit_kb =
+ ui::KeyBinding::new(gpui::KeyBinding::new("cmd-enter", gpui::NoAction, None), cx);
+
+ let commit_hint =
+ KeybindingHint::new(fake_commit_kb, cx.theme().colors().editor_background)
+ .suffix(commit_label);
+
+ let focus_handle = self.focus_handle(cx);
+
+ // let next_suggestion_kb =
+ // ui::KeyBinding::for_action_in(&NextSuggestion, &focus_handle.clone(), window, cx);
+ // let next_suggestion_hint = next_suggestion_kb.map(|kb| {
+ // KeybindingHint::new(kb, cx.theme().colors().editor_background).suffix("Next Suggestion")
+ // });
+
+ // let prev_suggestion_kb =
+ // ui::KeyBinding::for_action_in(&PrevSuggestion, &focus_handle.clone(), window, cx);
+ // let prev_suggestion_hint = prev_suggestion_kb.map(|kb| {
+ // KeybindingHint::new(kb, cx.theme().colors().editor_background)
+ // .suffix("Previous Suggestion")
+ // });
+
v_flex()
- .justify_between()
- .relative()
- .w_full()
- .h_full()
- .pt_2()
+ .id("editor-container")
.bg(cx.theme().colors().editor_background)
- .child(EditorElement::new(&self.commit_editor, panel_editor_style))
- .child(self.render_footer(window, cx))
+ .flex_1()
+ .size_full()
+ .rounded(px(border_radius))
+ .overflow_hidden()
+ .border_1()
+ .border_color(cx.theme().colors().border_variant)
+ .py_2()
+ .px_3()
+ .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
+ window.focus(&editor_focus_handle);
+ }))
+ .child(
+ div()
+ .size_full()
+ .flex_1()
+ .child(self.commit_editor_element(window, cx)),
+ )
+ .child(
+ h_flex()
+ .group("commit_editor_footer")
+ .flex_none()
+ .w_full()
+ .items_center()
+ .justify_between()
+ .w_full()
+ .pt_2()
+ .pb_0p5()
+ .gap_1()
+ .child(h_flex().gap_1().child(branch_selector).children(co_authors))
+ .child(div().flex_1())
+ .child(
+ h_flex()
+ .opacity(0.7)
+ .group_hover("commit_editor_footer", |this| this.opacity(1.0))
+ .items_center()
+ .justify_end()
+ .flex_none()
+ .px_1()
+ .gap_4()
+ .children(close_kb_hint)
+ // .children(next_suggestion_hint)
+ .child(commit_hint),
+ ),
+ )
}
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
@@ -180,13 +429,10 @@ impl CommitModal {
(branch, tooltip, title, co_authors)
});
- let branch_selector = Button::new("branch-selector", branch)
- .color(Color::Muted)
- .style(ButtonStyle::Subtle)
+ let branch_selector = panel_button(branch)
.icon(IconName::GitBranch)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
- .size(ButtonSize::Compact)
.icon_position(IconPosition::Start)
.tooltip(Tooltip::for_action_title(
"Switch Branch",
@@ -197,13 +443,28 @@ impl CommitModal {
}))
.style(ButtonStyle::Transparent);
+ let changes_count = self.git_panel.read(cx).total_staged_count();
+
+ let close_kb_hint =
+ if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
+ Some(
+ KeybindingHint::new(close_kb, cx.theme().colors().editor_background)
+ .suffix("Cancel"),
+ )
+ } else {
+ None
+ };
+
h_flex()
+ .items_center()
+ .h(px(36.0))
.w_full()
.justify_between()
- .child(branch_selector)
+ .px_3()
+ .child(h_flex().child(branch_selector))
.child(
- h_flex().children(co_authors).child(
- panel_filled_button(title)
+ h_flex().gap_1p5().children(co_authors).child(
+ Button::new("stage-button", title)
.tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
.on_click(cx.listener(|this, _, window, cx| {
this.commit(&Default::default(), window, cx);
@@ -212,6 +473,10 @@ impl CommitModal {
)
}
+ fn border_radius(&self) -> f32 {
+ 8.0
+ }
+
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
@@ -224,27 +489,33 @@ impl CommitModal {
impl Render for CommitModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
+ let (width, _, border_radius) = self.container_properties(window, cx);
+
v_flex()
.id("commit-modal")
.key_context("GitCommit")
.elevation_3(cx)
+ .overflow_hidden()
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::commit))
+ // .on_action(cx.listener(Self::next_suggestion))
+ // .on_action(cx.listener(Self::prev_suggestion))
.relative()
- .bg(cx.theme().colors().editor_background)
- .rounded(px(16.))
+ .justify_between()
+ .bg(cx.theme().colors().elevated_surface_background)
+ .rounded(px(border_radius))
.border_1()
.border_color(cx.theme().colors().border)
- .py_2()
- .px_4()
- .w(px(480.))
- .min_h(rems(18.))
+ .w(px(width))
+ .h(px(360.))
.flex_1()
.overflow_hidden()
.child(
v_flex()
.flex_1()
+ .p_2()
.child(self.render_commit_editor(None, window, cx)),
)
+ // .child(self.render_footer(window, cx))
}
}
@@ -184,6 +184,7 @@ pub struct GitPanel {
pending_remote_operations: RemoteOperations,
pub(crate) active_repository: Option<Entity<Repository>>,
commit_editor: Entity<Editor>,
+ suggested_commit_message: Option<String>,
conflicted_count: usize,
conflicted_staged_count: usize,
current_modifiers: Modifiers,
@@ -308,6 +309,7 @@ impl GitPanel {
remote_operation_id: 0,
active_repository,
commit_editor,
+ suggested_commit_message: None,
conflicted_count: 0,
conflicted_staged_count: 0,
current_modifiers: window.modifiers(),
@@ -1038,6 +1040,10 @@ impl GitPanel {
.detach();
}
+ pub fn total_staged_count(&self) -> usize {
+ self.tracked_staged_count + self.new_staged_count + self.conflicted_staged_count
+ }
+
pub fn commit_message_buffer(&self, cx: &App) -> Entity<Buffer> {
self.commit_editor
.read(cx)
@@ -1200,6 +1206,57 @@ impl GitPanel {
self.pending_commit = Some(task);
}
+ /// Suggests a commit message based on the changed files and their statuses
+ pub fn suggest_commit_message(&self) -> Option<String> {
+ let entries = self
+ .entries
+ .iter()
+ .filter_map(|entry| {
+ if let GitListEntry::GitStatusEntry(status_entry) = entry {
+ Some(status_entry)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<&GitStatusEntry>>();
+
+ if entries.is_empty() {
+ None
+ } else if entries.len() == 1 {
+ let entry = &entries[0];
+ let file_name = entry
+ .repo_path
+ .file_name()
+ .unwrap_or_default()
+ .to_string_lossy();
+
+ if entry.status.is_deleted() {
+ Some(format!("Delete {}", file_name))
+ } else if entry.status.is_created() {
+ Some(format!("Create {}", file_name))
+ } else if entry.status.is_modified() {
+ Some(format!("Update {}", file_name))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) {
+ let suggested_commit_message = self.suggest_commit_message();
+ self.suggested_commit_message = suggested_commit_message.clone();
+
+ if let Some(suggested_commit_message) = suggested_commit_message {
+ self.commit_editor.update(cx, |editor, cx| {
+ editor.set_placeholder_text(Arc::from(suggested_commit_message), cx)
+ });
+ }
+
+ cx.notify();
+ }
+
fn fetch(&mut self, _: &git::Fetch, _window: &mut Window, cx: &mut Context<Self>) {
let Some(repo) = self.active_repository.clone() else {
return;
@@ -1444,6 +1501,7 @@ impl GitPanel {
git_panel.clear_pending();
}
git_panel.update_visible_entries(cx);
+ git_panel.update_editor_placeholder(cx);
})
.ok();
}
@@ -486,7 +486,31 @@ impl Hsla {
self.a *= 1.0 - factor.clamp(0., 1.);
}
- /// Returns a new HSLA color with the same hue, saturation, and lightness, but with a modified alpha value.
+ /// Multiplies the alpha value of the color by a given factor
+ /// and returns a new HSLA color.
+ ///
+ /// Useful for transforming colors with dynamic opacity,
+ /// like a color from an external source.
+ ///
+ /// Example:
+ /// ```
+ /// let color = gpui::red();
+ /// let faded_color = color.opacity(0.5);
+ /// assert_eq!(faded_color.a, 0.5);
+ /// ```
+ ///
+ /// This will return a red color with half the opacity.
+ ///
+ /// Example:
+ /// ```
+ /// let color = hlsa(0.7, 1.0, 0.5, 0.7); // A saturated blue
+ /// let faded_color = color.opacity(0.16);
+ /// assert_eq!(faded_color.a, 0.112);
+ /// ```
+ ///
+ /// This will return a blue color with around ~10% opacity,
+ /// suitable for an element's hover or selected state.
+ ///
pub fn opacity(&self, factor: f32) -> Self {
Hsla {
h: self.h,
@@ -495,6 +519,35 @@ impl Hsla {
a: self.a * factor.clamp(0., 1.),
}
}
+
+ /// Returns a new HSLA color with the same hue, saturation,
+ /// and lightness, but with a new alpha value.
+ ///
+ /// Example:
+ /// ```
+ /// let color = gpui::red();
+ /// let red_color = color.alpha(0.25);
+ /// assert_eq!(red_color.a, 0.25);
+ /// ```
+ ///
+ /// This will return a red color with half the opacity.
+ ///
+ /// Example:
+ /// ```
+ /// let color = hsla(0.7, 1.0, 0.5, 0.7); // A saturated blue
+ /// let faded_color = color.alpha(0.25);
+ /// assert_eq!(faded_color.a, 0.25);
+ /// ```
+ ///
+ /// This will return a blue color with 25% opacity.
+ pub fn alpha(&self, a: f32) -> Self {
+ Hsla {
+ h: self.h,
+ s: self.s,
+ l: self.l,
+ a: a.clamp(0., 1.),
+ }
+ }
}
impl From<Rgba> for Hsla {
@@ -15,7 +15,7 @@ use language::{Outline, OutlineItem};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use settings::Settings;
-use theme::{color_alpha, ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{DismissDecision, ModalView};
@@ -332,7 +332,7 @@ pub fn render_item<T>(
cx: &App,
) -> StyledText {
let highlight_style = HighlightStyle {
- background_color: Some(color_alpha(cx.theme().colors().text_accent, 0.3)),
+ background_color: Some(cx.theme().colors().text_accent.alpha(0.3)),
..Default::default()
};
let custom_highlights = match_ranges
@@ -49,6 +49,7 @@ pub fn panel_button(label: impl Into<SharedString>) -> ui::Button {
let id = ElementId::Name(label.clone().to_lowercase().replace(' ', "_").into());
ui::Button::new(id, label)
.label_size(ui::LabelSize::Small)
+ .icon_size(ui::IconSize::Small)
// TODO: Change this once we use on_surface_bg in button_like
.layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
@@ -330,14 +330,6 @@ impl Theme {
}
}
-/// Compounds a color with an alpha value.
-/// TODO: Replace this with a method on Hsla.
-pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla {
- let mut color = color;
- color.a = alpha;
- color
-}
-
/// Asynchronously reads the user theme from the specified path.
pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
let reader = fs.open_sync(theme_path).await?;
@@ -1,7 +1,8 @@
+use crate::KeyBinding;
use crate::{h_flex, prelude::*};
-use crate::{ElevationIndex, KeyBinding};
-use gpui::{point, AnyElement, App, BoxShadow, IntoElement, Window};
+use gpui::{point, AnyElement, App, BoxShadow, FontStyle, Hsla, IntoElement, Window};
use smallvec::smallvec;
+use theme::Appearance;
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
///
@@ -23,7 +24,7 @@ pub struct KeybindingHint {
suffix: Option<SharedString>,
keybinding: KeyBinding,
size: Option<Pixels>,
- elevation: Option<ElevationIndex>,
+ background_color: Hsla,
}
impl KeybindingHint {
@@ -37,15 +38,15 @@ impl KeybindingHint {
/// ```
/// use ui::prelude::*;
///
- /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"));
+ /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
/// ```
- pub fn new(keybinding: KeyBinding) -> Self {
+ pub fn new(keybinding: KeyBinding, background_color: Hsla) -> Self {
Self {
prefix: None,
suffix: None,
keybinding,
size: None,
- elevation: None,
+ background_color,
}
}
@@ -59,15 +60,19 @@ impl KeybindingHint {
/// ```
/// use ui::prelude::*;
///
- /// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"));
+ /// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
/// ```
- pub fn with_prefix(prefix: impl Into<SharedString>, keybinding: KeyBinding) -> Self {
+ pub fn with_prefix(
+ prefix: impl Into<SharedString>,
+ keybinding: KeyBinding,
+ background_color: Hsla,
+ ) -> Self {
Self {
prefix: Some(prefix.into()),
suffix: None,
keybinding,
size: None,
- elevation: None,
+ background_color,
}
}
@@ -81,15 +86,19 @@ impl KeybindingHint {
/// ```
/// use ui::prelude::*;
///
- /// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste");
+ /// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste", Hsla::new(0.0, 0.0, 0.0, 1.0));
/// ```
- pub fn with_suffix(keybinding: KeyBinding, suffix: impl Into<SharedString>) -> Self {
+ pub fn with_suffix(
+ keybinding: KeyBinding,
+ suffix: impl Into<SharedString>,
+ background_color: Hsla,
+ ) -> Self {
Self {
prefix: None,
suffix: Some(suffix.into()),
keybinding,
size: None,
- elevation: None,
+ background_color,
}
}
@@ -143,46 +152,37 @@ impl KeybindingHint {
self.size = size.into();
self
}
-
- /// Sets the elevation of the keybinding hint.
- ///
- /// This method allows specifying the elevation index for the keybinding hint,
- /// which affects its visual appearance in terms of depth or layering.
- ///
- /// # Examples
- ///
- /// ```
- /// use ui::prelude::*;
- ///
- /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+A"))
- /// .elevation(ElevationIndex::new(1));
- /// ```
- pub fn elevation(mut self, elevation: impl Into<Option<ElevationIndex>>) -> Self {
- self.elevation = elevation.into();
- self
- }
}
impl RenderOnce for KeybindingHint {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let colors = cx.theme().colors().clone();
+ let is_light = cx.theme().appearance() == Appearance::Light;
+
+ let border_color =
+ self.background_color
+ .blend(colors.text.alpha(if is_light { 0.08 } else { 0.16 }));
+ let bg_color =
+ self.background_color
+ .blend(colors.text.alpha(if is_light { 0.06 } else { 0.12 }));
+ let shadow_color = colors.text.alpha(if is_light { 0.04 } else { 0.08 });
let size = self
.size
.unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
let kb_size = size - px(2.0);
- let kb_bg = if let Some(elevation) = self.elevation {
- elevation.on_elevation_bg(cx)
- } else {
- theme::color_alpha(colors.element_background, 0.6)
- };
- h_flex()
- .items_center()
+ let mut base = h_flex();
+
+ base.text_style()
+ .get_or_insert_with(Default::default)
+ .font_style = Some(FontStyle::Italic);
+
+ base.items_center()
.gap_0p5()
.font_buffer(cx)
.text_size(size)
- .text_color(colors.text_muted)
+ .text_color(colors.text_disabled)
.children(self.prefix)
.child(
h_flex()
@@ -191,10 +191,10 @@ impl RenderOnce for KeybindingHint {
.px_0p5()
.mr_0p5()
.border_1()
- .border_color(kb_bg)
- .bg(kb_bg.opacity(0.8))
+ .border_color(border_color)
+ .bg(bg_color)
.shadow(smallvec![BoxShadow {
- color: cx.theme().colors().editor_background.opacity(0.8),
+ color: shadow_color,
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
spread_radius: px(0.),
@@ -212,6 +212,8 @@ impl ComponentPreview for KeybindingHint {
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
.unwrap_or(KeyBinding::new(enter_fallback, cx));
+ let bg_color = cx.theme().colors().surface_background;
+
v_flex()
.gap_6()
.children(vec![
@@ -220,17 +222,17 @@ impl ComponentPreview for KeybindingHint {
vec![
single_example(
"With Prefix",
- KeybindingHint::with_prefix("Go to Start:", enter.clone())
+ KeybindingHint::with_prefix("Go to Start:", enter.clone(), bg_color)
.into_any_element(),
),
single_example(
"With Suffix",
- KeybindingHint::with_suffix(enter.clone(), "Go to End")
+ KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
.into_any_element(),
),
single_example(
"With Prefix and Suffix",
- KeybindingHint::new(enter.clone())
+ KeybindingHint::new(enter.clone(), bg_color)
.prefix("Confirm:")
.suffix("Execute selected action")
.into_any_element(),
@@ -242,21 +244,21 @@ impl ComponentPreview for KeybindingHint {
vec![
single_example(
"Small",
- KeybindingHint::new(enter.clone())
+ KeybindingHint::new(enter.clone(), bg_color)
.size(Pixels::from(12.0))
.prefix("Small:")
.into_any_element(),
),
single_example(
"Medium",
- KeybindingHint::new(enter.clone())
+ KeybindingHint::new(enter.clone(), bg_color)
.size(Pixels::from(16.0))
.suffix("Medium")
.into_any_element(),
),
single_example(
"Large",
- KeybindingHint::new(enter.clone())
+ KeybindingHint::new(enter.clone(), bg_color)
.size(Pixels::from(20.0))
.prefix("Large:")
.suffix("Size")
@@ -264,41 +266,6 @@ impl ComponentPreview for KeybindingHint {
),
],
),
- example_group_with_title(
- "Elevations",
- vec![
- single_example(
- "Surface",
- KeybindingHint::new(enter.clone())
- .elevation(ElevationIndex::Surface)
- .prefix("Surface:")
- .into_any_element(),
- ),
- single_example(
- "Elevated Surface",
- KeybindingHint::new(enter.clone())
- .elevation(ElevationIndex::ElevatedSurface)
- .suffix("Elevated")
- .into_any_element(),
- ),
- single_example(
- "Editor Surface",
- KeybindingHint::new(enter.clone())
- .elevation(ElevationIndex::EditorSurface)
- .prefix("Editor:")
- .suffix("Surface")
- .into_any_element(),
- ),
- single_example(
- "Modal Surface",
- KeybindingHint::new(enter.clone())
- .elevation(ElevationIndex::ModalSurface)
- .prefix("Modal:")
- .suffix("Enter")
- .into_any_element(),
- ),
- ],
- ),
])
.into_any_element()
}
@@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use gpui::{hsla, point, px, App, BoxShadow, Hsla};
use smallvec::{smallvec, SmallVec};
-use theme::ActiveTheme;
+use theme::{ActiveTheme, Appearance};
/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
///
@@ -40,27 +40,37 @@ impl Display for ElevationIndex {
impl ElevationIndex {
/// Returns an appropriate shadow for the given elevation index.
- pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
+ pub fn shadow(self, cx: &App) -> SmallVec<[BoxShadow; 2]> {
+ let is_light = cx.theme().appearance() == Appearance::Light;
+
match self {
ElevationIndex::Surface => smallvec![],
ElevationIndex::EditorSurface => smallvec![],
- ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
- color: hsla(0., 0., 0., 0.12),
- offset: point(px(0.), px(2.)),
- blur_radius: px(3.),
- spread_radius: px(0.),
- }],
+ ElevationIndex::ElevatedSurface => smallvec![
+ BoxShadow {
+ color: hsla(0., 0., 0., 0.12),
+ offset: point(px(0.), px(2.)),
+ blur_radius: px(3.),
+ spread_radius: px(0.),
+ },
+ BoxShadow {
+ color: hsla(0., 0., 0., if is_light { 0.03 } else { 0.06 }),
+ offset: point(px(1.), px(1.)),
+ blur_radius: px(0.),
+ spread_radius: px(0.),
+ }
+ ],
ElevationIndex::ModalSurface => smallvec![
BoxShadow {
- color: hsla(0., 0., 0., 0.12),
+ color: hsla(0., 0., 0., if is_light { 0.06 } else { 0.12 }),
offset: point(px(0.), px(2.)),
blur_radius: px(3.),
spread_radius: px(0.),
},
BoxShadow {
- color: hsla(0., 0., 0., 0.08),
+ color: hsla(0., 0., 0., if is_light { 0.06 } else { 0.08 }),
offset: point(px(0.), px(3.)),
blur_radius: px(6.),
spread_radius: px(0.),
@@ -71,6 +81,12 @@ impl ElevationIndex {
blur_radius: px(12.),
spread_radius: px(0.),
},
+ BoxShadow {
+ color: hsla(0., 0., 0., if is_light { 0.04 } else { 0.12 }),
+ offset: point(px(1.), px(1.)),
+ blur_radius: px(0.),
+ spread_radius: px(0.),
+ },
],
_ => smallvec![],
@@ -8,13 +8,13 @@ fn elevated<E: Styled>(this: E, cx: &App, index: ElevationIndex) -> E {
.rounded_lg()
.border_1()
.border_color(cx.theme().colors().border_variant)
- .shadow(index.shadow())
+ .shadow(index.shadow(cx))
}
fn elevated_borderless<E: Styled>(this: E, cx: &mut App, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.rounded_lg()
- .shadow(index.shadow())
+ .shadow(index.shadow(cx))
}
/// Extends [`gpui::Styled`] with Zed-specific styling methods.