git_ui: Commit modal refinement (#25484)
Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
ceb7fc2cb2
commit
30af8d0a81
14 changed files with 510 additions and 146 deletions
|
@ -742,6 +742,14 @@
|
||||||
"alt-up": "git_panel::FocusChanges"
|
"alt-up": "git_panel::FocusChanges"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitCommit > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"enter": "editor::Newline",
|
||||||
|
"ctrl-enter": "git::Commit"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -157,7 +157,8 @@
|
||||||
"cmd->": "assistant::QuoteSelection",
|
"cmd->": "assistant::QuoteSelection",
|
||||||
"cmd-<": "assistant::InsertIntoEditor",
|
"cmd-<": "assistant::InsertIntoEditor",
|
||||||
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
||||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
"alt-enter": "editor::OpenSelectionsInMultibuffer",
|
||||||
|
"cmd-g": "git::Commit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -742,14 +743,6 @@
|
||||||
"escape": "git_panel::ToggleFocus"
|
"escape": "git_panel::ToggleFocus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "GitCommit > Editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"enter": "editor::Newline",
|
|
||||||
"cmd-enter": "git::Commit"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "GitPanel > Editor",
|
"context": "GitPanel > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
@ -761,6 +754,14 @@
|
||||||
"alt-up": "git_panel::FocusChanges"
|
"alt-up": "git_panel::FocusChanges"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitCommit > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"enter": "editor::Newline",
|
||||||
|
"cmd-enter": "git::Commit"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|
|
@ -1234,8 +1234,8 @@ impl ContextEditor {
|
||||||
.px_1()
|
.px_1()
|
||||||
.mr_0p5()
|
.mr_0p5()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(theme::color_alpha(colors.border_variant, 0.6))
|
.border_color(colors.border_variant.alpha(0.6))
|
||||||
.bg(theme::color_alpha(colors.element_background, 0.6))
|
.bg(colors.element_background.alpha(0.6))
|
||||||
.child("esc"),
|
.child("esc"),
|
||||||
)
|
)
|
||||||
.child("to cancel")
|
.child("to cancel")
|
||||||
|
|
|
@ -53,10 +53,7 @@ impl RenderOnce for ExtensionCard {
|
||||||
.size_full()
|
.size_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.bg(theme::color_alpha(
|
.bg(cx.theme().colors().elevated_surface_background.alpha(0.8))
|
||||||
cx.theme().colors().elevated_surface_background,
|
|
||||||
0.8,
|
|
||||||
))
|
|
||||||
.child(Label::new("Overridden by dev extension.")),
|
.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 crate::repository_selector::RepositorySelector;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use git::Commit;
|
use git::Commit;
|
||||||
|
use language::language_settings::LanguageSettings;
|
||||||
use language::Buffer;
|
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 settings::Settings;
|
||||||
use theme::ThemeSettings;
|
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 gpui::*;
|
||||||
use project::git::Repository;
|
use project::git::Repository;
|
||||||
use project::{Fs, Project};
|
use project::{Fs, Project};
|
||||||
|
@ -18,6 +22,8 @@ use std::sync::Arc;
|
||||||
use workspace::dock::{Dock, DockPosition, PanelHandle};
|
use workspace::dock::{Dock, DockPosition, PanelHandle};
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
// actions!(commit_modal, [NextSuggestion, PrevSuggestion]);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
||||||
let Some(window) = window else {
|
let Some(window) = window else {
|
||||||
|
@ -32,6 +38,8 @@ pub struct CommitModal {
|
||||||
git_panel: Entity<GitPanel>,
|
git_panel: Entity<GitPanel>,
|
||||||
commit_editor: Entity<Editor>,
|
commit_editor: Entity<Editor>,
|
||||||
restore_dock: RestoreDock,
|
restore_dock: RestoreDock,
|
||||||
|
current_suggestion: Option<usize>,
|
||||||
|
suggested_messages: Vec<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for CommitModal {
|
impl Focusable for CommitModal {
|
||||||
|
@ -114,6 +122,7 @@ impl CommitModal {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let panel = git_panel.read(cx);
|
let panel = git_panel.read(cx);
|
||||||
|
let suggested_message = panel.suggest_commit_message();
|
||||||
|
|
||||||
let commit_editor = git_panel.update(cx, |git_panel, cx| {
|
let commit_editor = git_panel.update(cx, |git_panel, cx| {
|
||||||
git_panel.set_modal_open(true, 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))
|
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 {
|
Self {
|
||||||
git_panel,
|
git_panel,
|
||||||
commit_editor,
|
commit_editor,
|
||||||
restore_dock,
|
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(
|
pub fn render_commit_editor(
|
||||||
&self,
|
&self,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let editor = self.commit_editor.clone();
|
let (width, padding_x, modal_border_radius) = self.container_properties(window, cx);
|
||||||
|
|
||||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
let border_radius = modal_border_radius - padding_x / 2.0;
|
||||||
|
|
||||||
|
let editor = self.commit_editor.clone();
|
||||||
|
let editor_focus_handle = editor.focus_handle(cx);
|
||||||
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let line_height = relative(settings.buffer_line_height.value())
|
let line_height = relative(settings.buffer_line_height.value())
|
||||||
.to_pixels(settings.buffer_font_size(cx).into(), window.rem_size());
|
.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()
|
v_flex()
|
||||||
.justify_between()
|
.id("editor-container")
|
||||||
.relative()
|
|
||||||
.w_full()
|
|
||||||
.h_full()
|
|
||||||
.pt_2()
|
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
.flex_1()
|
||||||
.child(self.render_footer(window, cx))
|
.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 {
|
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)
|
(branch, tooltip, title, co_authors)
|
||||||
});
|
});
|
||||||
|
|
||||||
let branch_selector = Button::new("branch-selector", branch)
|
let branch_selector = panel_button(branch)
|
||||||
.color(Color::Muted)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.icon(IconName::GitBranch)
|
.icon(IconName::GitBranch)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.size(ButtonSize::Compact)
|
|
||||||
.icon_position(IconPosition::Start)
|
.icon_position(IconPosition::Start)
|
||||||
.tooltip(Tooltip::for_action_title(
|
.tooltip(Tooltip::for_action_title(
|
||||||
"Switch Branch",
|
"Switch Branch",
|
||||||
|
@ -197,13 +443,28 @@ impl CommitModal {
|
||||||
}))
|
}))
|
||||||
.style(ButtonStyle::Transparent);
|
.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()
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.h(px(36.0))
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(branch_selector)
|
.px_3()
|
||||||
|
.child(h_flex().child(branch_selector))
|
||||||
.child(
|
.child(
|
||||||
h_flex().children(co_authors).child(
|
h_flex().gap_1p5().children(co_authors).child(
|
||||||
panel_filled_button(title)
|
Button::new("stage-button", title)
|
||||||
.tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
|
.tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.commit(&Default::default(), 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>) {
|
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
@ -224,27 +489,33 @@ impl CommitModal {
|
||||||
|
|
||||||
impl Render for CommitModal {
|
impl Render for CommitModal {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||||
|
let (width, _, border_radius) = self.container_properties(window, cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("commit-modal")
|
.id("commit-modal")
|
||||||
.key_context("GitCommit")
|
.key_context("GitCommit")
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
|
.overflow_hidden()
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::commit))
|
.on_action(cx.listener(Self::commit))
|
||||||
|
// .on_action(cx.listener(Self::next_suggestion))
|
||||||
|
// .on_action(cx.listener(Self::prev_suggestion))
|
||||||
.relative()
|
.relative()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.justify_between()
|
||||||
.rounded(px(16.))
|
.bg(cx.theme().colors().elevated_surface_background)
|
||||||
|
.rounded(px(border_radius))
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.py_2()
|
.w(px(width))
|
||||||
.px_4()
|
.h(px(360.))
|
||||||
.w(px(480.))
|
|
||||||
.min_h(rems(18.))
|
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
.p_2()
|
||||||
.child(self.render_commit_editor(None, window, cx)),
|
.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,
|
pending_remote_operations: RemoteOperations,
|
||||||
pub(crate) active_repository: Option<Entity<Repository>>,
|
pub(crate) active_repository: Option<Entity<Repository>>,
|
||||||
commit_editor: Entity<Editor>,
|
commit_editor: Entity<Editor>,
|
||||||
|
suggested_commit_message: Option<String>,
|
||||||
conflicted_count: usize,
|
conflicted_count: usize,
|
||||||
conflicted_staged_count: usize,
|
conflicted_staged_count: usize,
|
||||||
current_modifiers: Modifiers,
|
current_modifiers: Modifiers,
|
||||||
|
@ -308,6 +309,7 @@ impl GitPanel {
|
||||||
remote_operation_id: 0,
|
remote_operation_id: 0,
|
||||||
active_repository,
|
active_repository,
|
||||||
commit_editor,
|
commit_editor,
|
||||||
|
suggested_commit_message: None,
|
||||||
conflicted_count: 0,
|
conflicted_count: 0,
|
||||||
conflicted_staged_count: 0,
|
conflicted_staged_count: 0,
|
||||||
current_modifiers: window.modifiers(),
|
current_modifiers: window.modifiers(),
|
||||||
|
@ -1038,6 +1040,10 @@ impl GitPanel {
|
||||||
.detach();
|
.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> {
|
pub fn commit_message_buffer(&self, cx: &App) -> Entity<Buffer> {
|
||||||
self.commit_editor
|
self.commit_editor
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -1200,6 +1206,57 @@ impl GitPanel {
|
||||||
self.pending_commit = Some(task);
|
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>) {
|
fn fetch(&mut self, _: &git::Fetch, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(repo) = self.active_repository.clone() else {
|
let Some(repo) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
|
@ -1444,6 +1501,7 @@ impl GitPanel {
|
||||||
git_panel.clear_pending();
|
git_panel.clear_pending();
|
||||||
}
|
}
|
||||||
git_panel.update_visible_entries(cx);
|
git_panel.update_visible_entries(cx);
|
||||||
|
git_panel.update_editor_placeholder(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -486,7 +486,31 @@ impl Hsla {
|
||||||
self.a *= 1.0 - factor.clamp(0., 1.);
|
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 {
|
pub fn opacity(&self, factor: f32) -> Self {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: self.h,
|
h: self.h,
|
||||||
|
@ -495,6 +519,35 @@ impl Hsla {
|
||||||
a: self.a * factor.clamp(0., 1.),
|
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 {
|
impl From<Rgba> for Hsla {
|
||||||
|
|
|
@ -15,7 +15,7 @@ use language::{Outline, OutlineItem};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::{color_alpha, ActiveTheme, ThemeSettings};
|
use theme::{ActiveTheme, ThemeSettings};
|
||||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{DismissDecision, ModalView};
|
use workspace::{DismissDecision, ModalView};
|
||||||
|
@ -332,7 +332,7 @@ pub fn render_item<T>(
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> StyledText {
|
) -> StyledText {
|
||||||
let highlight_style = HighlightStyle {
|
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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let custom_highlights = match_ranges
|
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());
|
let id = ElementId::Name(label.clone().to_lowercase().replace(' ', "_").into());
|
||||||
ui::Button::new(id, label)
|
ui::Button::new(id, label)
|
||||||
.label_size(ui::LabelSize::Small)
|
.label_size(ui::LabelSize::Small)
|
||||||
|
.icon_size(ui::IconSize::Small)
|
||||||
// TODO: Change this once we use on_surface_bg in button_like
|
// TODO: Change this once we use on_surface_bg in button_like
|
||||||
.layer(ui::ElevationIndex::ModalSurface)
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
.size(ui::ButtonSize::Compact)
|
.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.
|
/// Asynchronously reads the user theme from the specified path.
|
||||||
pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
|
pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
|
||||||
let reader = fs.open_sync(theme_path).await?;
|
let reader = fs.open_sync(theme_path).await?;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
use crate::KeyBinding;
|
||||||
use crate::{h_flex, prelude::*};
|
use crate::{h_flex, prelude::*};
|
||||||
use crate::{ElevationIndex, KeyBinding};
|
use gpui::{point, AnyElement, App, BoxShadow, FontStyle, Hsla, IntoElement, Window};
|
||||||
use gpui::{point, AnyElement, App, BoxShadow, IntoElement, Window};
|
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
|
use theme::Appearance;
|
||||||
|
|
||||||
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
|
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
|
||||||
///
|
///
|
||||||
|
@ -23,7 +24,7 @@ pub struct KeybindingHint {
|
||||||
suffix: Option<SharedString>,
|
suffix: Option<SharedString>,
|
||||||
keybinding: KeyBinding,
|
keybinding: KeyBinding,
|
||||||
size: Option<Pixels>,
|
size: Option<Pixels>,
|
||||||
elevation: Option<ElevationIndex>,
|
background_color: Hsla,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeybindingHint {
|
impl KeybindingHint {
|
||||||
|
@ -37,15 +38,15 @@ impl KeybindingHint {
|
||||||
/// ```
|
/// ```
|
||||||
/// use ui::prelude::*;
|
/// 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 {
|
Self {
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
keybinding,
|
keybinding,
|
||||||
size: None,
|
size: None,
|
||||||
elevation: None,
|
background_color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,15 +60,19 @@ impl KeybindingHint {
|
||||||
/// ```
|
/// ```
|
||||||
/// use ui::prelude::*;
|
/// 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 {
|
Self {
|
||||||
prefix: Some(prefix.into()),
|
prefix: Some(prefix.into()),
|
||||||
suffix: None,
|
suffix: None,
|
||||||
keybinding,
|
keybinding,
|
||||||
size: None,
|
size: None,
|
||||||
elevation: None,
|
background_color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,15 +86,19 @@ impl KeybindingHint {
|
||||||
/// ```
|
/// ```
|
||||||
/// use ui::prelude::*;
|
/// 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 {
|
Self {
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: Some(suffix.into()),
|
suffix: Some(suffix.into()),
|
||||||
keybinding,
|
keybinding,
|
||||||
size: None,
|
size: None,
|
||||||
elevation: None,
|
background_color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,46 +152,37 @@ impl KeybindingHint {
|
||||||
self.size = size.into();
|
self.size = size.into();
|
||||||
self
|
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 {
|
impl RenderOnce for KeybindingHint {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let colors = cx.theme().colors().clone();
|
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
|
let size = self
|
||||||
.size
|
.size
|
||||||
.unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
|
.unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
|
||||||
let kb_size = size - px(2.0);
|
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()
|
let mut base = h_flex();
|
||||||
.items_center()
|
|
||||||
|
base.text_style()
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.font_style = Some(FontStyle::Italic);
|
||||||
|
|
||||||
|
base.items_center()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.font_buffer(cx)
|
.font_buffer(cx)
|
||||||
.text_size(size)
|
.text_size(size)
|
||||||
.text_color(colors.text_muted)
|
.text_color(colors.text_disabled)
|
||||||
.children(self.prefix)
|
.children(self.prefix)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -191,10 +191,10 @@ impl RenderOnce for KeybindingHint {
|
||||||
.px_0p5()
|
.px_0p5()
|
||||||
.mr_0p5()
|
.mr_0p5()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(kb_bg)
|
.border_color(border_color)
|
||||||
.bg(kb_bg.opacity(0.8))
|
.bg(bg_color)
|
||||||
.shadow(smallvec![BoxShadow {
|
.shadow(smallvec![BoxShadow {
|
||||||
color: cx.theme().colors().editor_background.opacity(0.8),
|
color: shadow_color,
|
||||||
offset: point(px(0.), px(1.)),
|
offset: point(px(0.), px(1.)),
|
||||||
blur_radius: px(0.),
|
blur_radius: px(0.),
|
||||||
spread_radius: px(0.),
|
spread_radius: px(0.),
|
||||||
|
@ -212,6 +212,8 @@ impl ComponentPreview for KeybindingHint {
|
||||||
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
|
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
|
||||||
.unwrap_or(KeyBinding::new(enter_fallback, cx));
|
.unwrap_or(KeyBinding::new(enter_fallback, cx));
|
||||||
|
|
||||||
|
let bg_color = cx.theme().colors().surface_background;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.children(vec![
|
.children(vec![
|
||||||
|
@ -220,17 +222,17 @@ impl ComponentPreview for KeybindingHint {
|
||||||
vec![
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"With Prefix",
|
"With Prefix",
|
||||||
KeybindingHint::with_prefix("Go to Start:", enter.clone())
|
KeybindingHint::with_prefix("Go to Start:", enter.clone(), bg_color)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"With Suffix",
|
"With Suffix",
|
||||||
KeybindingHint::with_suffix(enter.clone(), "Go to End")
|
KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"With Prefix and Suffix",
|
"With Prefix and Suffix",
|
||||||
KeybindingHint::new(enter.clone())
|
KeybindingHint::new(enter.clone(), bg_color)
|
||||||
.prefix("Confirm:")
|
.prefix("Confirm:")
|
||||||
.suffix("Execute selected action")
|
.suffix("Execute selected action")
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
|
@ -242,21 +244,21 @@ impl ComponentPreview for KeybindingHint {
|
||||||
vec![
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Small",
|
"Small",
|
||||||
KeybindingHint::new(enter.clone())
|
KeybindingHint::new(enter.clone(), bg_color)
|
||||||
.size(Pixels::from(12.0))
|
.size(Pixels::from(12.0))
|
||||||
.prefix("Small:")
|
.prefix("Small:")
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Medium",
|
"Medium",
|
||||||
KeybindingHint::new(enter.clone())
|
KeybindingHint::new(enter.clone(), bg_color)
|
||||||
.size(Pixels::from(16.0))
|
.size(Pixels::from(16.0))
|
||||||
.suffix("Medium")
|
.suffix("Medium")
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Large",
|
"Large",
|
||||||
KeybindingHint::new(enter.clone())
|
KeybindingHint::new(enter.clone(), bg_color)
|
||||||
.size(Pixels::from(20.0))
|
.size(Pixels::from(20.0))
|
||||||
.prefix("Large:")
|
.prefix("Large:")
|
||||||
.suffix("Size")
|
.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()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
use gpui::{hsla, point, px, App, BoxShadow, Hsla};
|
use gpui::{hsla, point, px, App, BoxShadow, Hsla};
|
||||||
use smallvec::{smallvec, SmallVec};
|
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.
|
/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
|
||||||
///
|
///
|
||||||
|
@ -40,19 +40,14 @@ impl Display for ElevationIndex {
|
||||||
|
|
||||||
impl ElevationIndex {
|
impl ElevationIndex {
|
||||||
/// Returns an appropriate shadow for the given elevation index.
|
/// 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 {
|
match self {
|
||||||
ElevationIndex::Surface => smallvec![],
|
ElevationIndex::Surface => smallvec![],
|
||||||
ElevationIndex::EditorSurface => smallvec![],
|
ElevationIndex::EditorSurface => smallvec![],
|
||||||
|
|
||||||
ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
|
ElevationIndex::ElevatedSurface => smallvec![
|
||||||
color: hsla(0., 0., 0., 0.12),
|
|
||||||
offset: point(px(0.), px(2.)),
|
|
||||||
blur_radius: px(3.),
|
|
||||||
spread_radius: px(0.),
|
|
||||||
}],
|
|
||||||
|
|
||||||
ElevationIndex::ModalSurface => smallvec![
|
|
||||||
BoxShadow {
|
BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.12),
|
color: hsla(0., 0., 0., 0.12),
|
||||||
offset: point(px(0.), px(2.)),
|
offset: point(px(0.), px(2.)),
|
||||||
|
@ -60,7 +55,22 @@ impl ElevationIndex {
|
||||||
spread_radius: px(0.),
|
spread_radius: px(0.),
|
||||||
},
|
},
|
||||||
BoxShadow {
|
BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.08),
|
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., 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., if is_light { 0.06 } else { 0.08 }),
|
||||||
offset: point(px(0.), px(3.)),
|
offset: point(px(0.), px(3.)),
|
||||||
blur_radius: px(6.),
|
blur_radius: px(6.),
|
||||||
spread_radius: px(0.),
|
spread_radius: px(0.),
|
||||||
|
@ -71,6 +81,12 @@ impl ElevationIndex {
|
||||||
blur_radius: px(12.),
|
blur_radius: px(12.),
|
||||||
spread_radius: px(0.),
|
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![],
|
_ => smallvec![],
|
||||||
|
|
|
@ -8,13 +8,13 @@ fn elevated<E: Styled>(this: E, cx: &App, index: ElevationIndex) -> E {
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.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 {
|
fn elevated_borderless<E: Styled>(this: E, cx: &mut App, index: ElevationIndex) -> E {
|
||||||
this.bg(cx.theme().colors().elevated_surface_background)
|
this.bg(cx.theme().colors().elevated_surface_background)
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.shadow(index.shadow())
|
.shadow(index.shadow(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extends [`gpui::Styled`] with Zed-specific styling methods.
|
/// Extends [`gpui::Styled`] with Zed-specific styling methods.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue