Cargo.lock 🔗
@@ -9044,7 +9044,10 @@ dependencies = [
name = "panel"
version = "0.1.0"
dependencies = [
+ "editor",
"gpui",
+ "settings",
+ "theme",
"ui",
"workspace",
]
Nate Butler created
- Fixes commit editor issues & updates style
- Starts on quick commit (not hooked up to anything)
- Updates some panel styles
- Adds SwitchWithLabel
-
Release Notes:
- N/A
Cargo.lock | 3
crates/git_ui/src/git_panel.rs | 239 ++++++++-------
crates/git_ui/src/git_ui.rs | 2
crates/git_ui/src/quick_commit.rs | 307 ++++++++++++++++++++
crates/panel/Cargo.toml | 3
crates/panel/src/panel.rs | 65 ++++
crates/ui/src/components/button/button.rs | 8
crates/ui/src/components/button/button_like.rs | 4
crates/ui/src/components/button/icon_button.rs | 9
crates/ui/src/components/toggle.rs | 58 +++
10 files changed, 582 insertions(+), 116 deletions(-)
@@ -9044,7 +9044,10 @@ dependencies = [
name = "panel"
version = "0.1.0"
dependencies = [
+ "editor",
"gpui",
+ "settings",
+ "theme",
"ui",
"workspace",
]
@@ -6,33 +6,32 @@ use crate::{
};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
-use editor::actions::MoveToEnd;
-use editor::scroll::ScrollbarAutoHide;
-use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
-use git::repository::RepoPath;
-use git::status::FileStatus;
-use git::{Commit, ToggleStaged};
+use editor::{
+ actions::MoveToEnd, scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode,
+ EditorSettings, MultiBuffer, ShowScrollbar,
+};
+use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
use gpui::*;
use language::{Buffer, File};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
use multi_buffer::ExcerptInfo;
-use panel::PanelHeader;
-use project::git::{GitEvent, Repository};
-use project::{Fs, Project, ProjectPath};
+use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
+use project::{
+ git::{GitEvent, Repository},
+ Fs, Project, ProjectPath,
+};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
-use theme::ThemeSettings;
use ui::{
- prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors,
- ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
+ prelude::*, ButtonLike, Checkbox, CheckboxWithLabel, Divider, DividerColor, ElevationIndex,
+ IndentGuideColors, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
};
use util::{maybe, ResultExt, TryFutureExt};
-use workspace::notifications::{DetachAndPromptErr, NotificationId};
-use workspace::Toast;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
- Workspace,
+ notifications::{DetachAndPromptErr, NotificationId},
+ Toast, Workspace,
};
actions!(
@@ -147,33 +146,33 @@ struct PendingOperation {
}
pub struct GitPanel {
+ active_repository: Option<Entity<Repository>>,
+ commit_editor: Entity<Editor>,
+ conflicted_count: usize,
+ conflicted_staged_count: usize,
current_modifiers: Modifiers,
+ enable_auto_coauthors: bool,
+ entries: Vec<GitListEntry>,
+ entries_by_path: collections::HashMap<RepoPath, usize>,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
hide_scrollbar_task: Option<Task<()>>,
+ new_count: usize,
+ new_staged_count: usize,
+ pending: Vec<PendingOperation>,
+ pending_commit: Option<Task<()>>,
pending_serialization: Task<Option<()>>,
- workspace: WeakEntity<Workspace>,
project: Entity<Project>,
- active_repository: Option<Entity<Repository>>,
+ repository_selector: Entity<RepositorySelector>,
scroll_handle: UniformListScrollHandle,
scrollbar_state: ScrollbarState,
selected_entry: Option<usize>,
show_scrollbar: bool,
+ tracked_count: usize,
+ tracked_staged_count: usize,
update_visible_entries_task: Task<()>,
- repository_selector: Entity<RepositorySelector>,
- commit_editor: Entity<Editor>,
- entries: Vec<GitListEntry>,
- entries_by_path: collections::HashMap<RepoPath, usize>,
width: Option<Pixels>,
- pending: Vec<PendingOperation>,
- pending_commit: Option<Task<()>>,
-
- conflicted_staged_count: usize,
- conflicted_count: usize,
- tracked_staged_count: usize,
- tracked_count: usize,
- new_staged_count: usize,
- new_count: usize,
+ workspace: WeakEntity<Workspace>,
}
fn commit_message_editor(
@@ -181,23 +180,10 @@ fn commit_message_editor(
window: &mut Window,
cx: &mut Context<'_, Editor>,
) -> Editor {
- let theme = ThemeSettings::get_global(cx);
-
- let mut text_style = window.text_style();
- let refinement = TextStyleRefinement {
- font_family: Some(theme.buffer_font.family.clone()),
- font_features: Some(FontFeatures::disable_ligatures()),
- font_size: Some(px(12.).into()),
- color: Some(cx.theme().colors().editor_foreground),
- background_color: Some(gpui::transparent_black()),
- ..Default::default()
- };
- text_style.refine(&refinement);
-
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
Editor::new(
- EditorMode::AutoHeight { max_lines: 10 },
+ EditorMode::AutoHeight { max_lines: 6 },
buffer,
None,
false,
@@ -205,13 +191,12 @@ fn commit_message_editor(
cx,
)
} else {
- Editor::auto_height(10, window, cx)
+ Editor::auto_height(6, window, cx)
};
commit_editor.set_use_autoclose(false);
commit_editor.set_show_gutter(false, cx);
commit_editor.set_show_wrap_guides(false, cx);
commit_editor.set_show_indent_guides(false, cx);
- commit_editor.set_text_style_refinement(refinement);
commit_editor.set_placeholder_text("Enter commit message", cx);
commit_editor
}
@@ -260,37 +245,40 @@ impl GitPanel {
)
.detach();
+ let scrollbar_state =
+ ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity());
+
let repository_selector =
cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
let mut git_panel = Self {
- focus_handle: cx.focus_handle(),
- pending_serialization: Task::ready(None),
+ active_repository,
+ commit_editor,
+ conflicted_count: 0,
+ conflicted_staged_count: 0,
+ current_modifiers: window.modifiers(),
+ enable_auto_coauthors: true,
entries: Vec::new(),
entries_by_path: HashMap::default(),
+ focus_handle: cx.focus_handle(),
+ fs,
+ hide_scrollbar_task: None,
+ new_count: 0,
+ new_staged_count: 0,
pending: Vec::new(),
- current_modifiers: window.modifiers(),
- width: Some(px(360.)),
- scrollbar_state: ScrollbarState::new(scroll_handle.clone())
- .parent_entity(&cx.entity()),
+ pending_commit: None,
+ pending_serialization: Task::ready(None),
+ project,
repository_selector,
+ scroll_handle,
+ scrollbar_state,
selected_entry: None,
show_scrollbar: false,
- hide_scrollbar_task: None,
+ tracked_count: 0,
+ tracked_staged_count: 0,
update_visible_entries_task: Task::ready(()),
- pending_commit: None,
- active_repository,
- scroll_handle,
- fs,
- commit_editor,
- project,
+ width: Some(px(360.)),
workspace,
- conflicted_count: 0,
- conflicted_staged_count: 0,
- tracked_staged_count: 0,
- tracked_count: 0,
- new_staged_count: 0,
- new_count: 0,
};
git_panel.schedule_update(false, window, cx);
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
@@ -990,6 +978,26 @@ impl GitPanel {
cx.notify();
}
+ fn toggle_auto_coauthors(&mut self, cx: &mut Context<Self>) {
+ self.enable_auto_coauthors = !self.enable_auto_coauthors;
+ cx.notify();
+ }
+
+ fn header_state(&self, header_type: Section) -> ToggleState {
+ let (staged_count, count) = match header_type {
+ Section::New => (self.new_staged_count, self.new_count),
+ Section::Tracked => (self.tracked_staged_count, self.tracked_count),
+ Section::Conflict => (self.conflicted_staged_count, self.conflicted_count),
+ };
+ if staged_count == 0 {
+ ToggleState::Unselected
+ } else if count == staged_count {
+ ToggleState::Selected
+ } else {
+ ToggleState::Indeterminate
+ }
+ }
+
fn update_counts(&mut self, repo: &Repository) {
self.conflicted_count = 0;
self.conflicted_staged_count = 0;
@@ -1043,21 +1051,6 @@ impl GitPanel {
self.conflicted_count > 0 && self.conflicted_count != self.conflicted_staged_count
}
- fn header_state(&self, header_type: Section) -> ToggleState {
- let (staged_count, count) = match header_type {
- Section::New => (self.new_staged_count, self.new_count),
- Section::Tracked => (self.tracked_staged_count, self.tracked_count),
- Section::Conflict => (self.conflicted_staged_count, self.conflicted_count),
- };
- if staged_count == 0 {
- ToggleState::Unselected
- } else if count == staged_count {
- ToggleState::Selected
- } else {
- ToggleState::Indeterminate
- }
- }
-
fn show_err_toast(&self, e: anyhow::Error, cx: &mut App) {
let Some(workspace) = self.workspace.upgrade() else {
return;
@@ -1165,13 +1158,21 @@ impl GitPanel {
)
}
- pub fn render_commit_editor(&self, cx: &Context<Self>) -> impl IntoElement {
+ pub fn render_commit_editor(
+ &self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
let editor = self.commit_editor.clone();
let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
&& self.pending_commit.is_none()
&& !editor.read(cx).is_empty(cx)
&& !self.has_unstaged_conflicts()
&& self.has_write_access(cx);
+ // let can_commit_all =
+ // !self.commit_pending && self.can_commit_all && !editor.read(cx).is_empty(cx);
+ let panel_editor_style = panel_editor_style(true, window, cx);
+
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone();
@@ -1186,8 +1187,7 @@ impl GitPanel {
"Commit All"
};
- let commit_button = self
- .panel_button("commit-changes", title)
+ let commit_button = panel_filled_button(title)
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
@@ -1197,28 +1197,50 @@ impl GitPanel {
cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
});
- div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
- v_flex()
- .id("commit-editor-container")
- .relative()
- .h_full()
- .py_2p5()
- .px_3()
- .bg(cx.theme().colors().editor_background)
- .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
- window.focus(&editor_focus_handle);
- }))
- .child(self.commit_editor.clone())
- .child(
- h_flex()
- .absolute()
- .bottom_2p5()
- .right_3()
- .gap_1p5()
- .child(div().gap_1().flex_grow())
- .child(commit_button),
- ),
- )
+ let enable_coauthors = CheckboxWithLabel::new(
+ "enable-coauthors",
+ Label::new("Add Co-authors")
+ .color(Color::Disabled)
+ .size(LabelSize::XSmall),
+ self.enable_auto_coauthors.into(),
+ cx.listener(move |this, _, _, cx| this.toggle_auto_coauthors(cx)),
+ );
+
+ let footer_size = px(32.);
+ let gap = px(16.0);
+
+ let max_height = window.line_height() * 6. + gap + footer_size;
+
+ panel_editor_container(window, cx)
+ .id("commit-editor-container")
+ .relative()
+ .h(max_height)
+ .w_full()
+ .border_t_1()
+ .border_color(cx.theme().colors().border)
+ .bg(cx.theme().colors().editor_background)
+ .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
+ window.focus(&editor_focus_handle);
+ }))
+ .child(EditorElement::new(&self.commit_editor, panel_editor_style))
+ .child(
+ h_flex()
+ .absolute()
+ .bottom_0()
+ .left_2()
+ .h(footer_size)
+ .flex_none()
+ .child(enable_coauthors),
+ )
+ .child(
+ h_flex()
+ .absolute()
+ .bottom_0()
+ .right_2()
+ .h(footer_size)
+ .flex_none()
+ .child(commit_button),
+ )
}
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
@@ -1348,6 +1370,7 @@ impl GitPanel {
v_flex()
.size_full()
+ .flex_grow()
.overflow_hidden()
.child(
uniform_list(cx.entity().clone(), "entries", entry_count, {
@@ -1496,7 +1519,7 @@ impl GitPanel {
.spacing(ListItemSpacing::Sparse)
.start_slot(start_slot)
.toggle_state(selected)
- .focused(selected && self.focus_handle.is_focused(window))
+ .focused(selected && self.focus_handle(cx).is_focused(window))
.disabled(!has_write_access)
.on_click({
cx.listener(move |this, _, _, cx| {
@@ -1599,7 +1622,7 @@ impl GitPanel {
.spacing(ListItemSpacing::Sparse)
.start_slot(start_slot)
.toggle_state(selected)
- .focused(selected && self.focus_handle.is_focused(window))
+ .focused(selected && self.focus_handle(cx).is_focused(window))
.disabled(!has_write_access)
.on_click({
cx.listener(move |this, _, window, cx| {
@@ -1705,7 +1728,7 @@ impl Render for GitPanel {
} else {
self.render_empty_state(cx).into_any_element()
})
- .child(self.render_commit_editor(cx))
+ .child(self.render_commit_editor(window, cx))
}
}
@@ -9,12 +9,14 @@ pub mod branch_picker;
pub mod git_panel;
mod git_panel_settings;
pub mod project_diff;
+// mod quick_commit;
pub mod repository_selector;
pub fn init(cx: &mut App) {
GitPanelSettings::register(cx);
branch_picker::init(cx);
cx.observe_new(ProjectDiff::register).detach();
+ // quick_commit::init(cx);
}
// TODO: Add updated status colors to theme
@@ -0,0 +1,307 @@
+#![allow(unused, dead_code)]
+
+use crate::repository_selector::RepositorySelector;
+use anyhow::Result;
+use git::{CommitAllChanges, CommitChanges};
+use language::Buffer;
+use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
+use ui::{prelude::*, Tooltip};
+
+use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
+use gpui::*;
+use project::git::Repository;
+use project::{Fs, Project};
+use std::sync::Arc;
+use workspace::{ModalView, Workspace};
+
+actions!(
+ git,
+ [QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]
+);
+
+pub fn init(cx: &mut App) {
+ cx.observe_new(|workspace: &mut Workspace, window, cx| {
+ let Some(window) = window else {
+ return;
+ };
+ QuickCommitModal::register(workspace, window, cx)
+ })
+ .detach();
+}
+
+fn commit_message_editor(
+ commit_message_buffer: Option<Entity<Buffer>>,
+ window: &mut Window,
+ cx: &mut Context<'_, Editor>,
+) -> Editor {
+ let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
+ let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
+ Editor::new(
+ EditorMode::AutoHeight { max_lines: 10 },
+ buffer,
+ None,
+ false,
+ window,
+ cx,
+ )
+ } else {
+ Editor::auto_height(10, window, cx)
+ };
+ commit_editor.set_use_autoclose(false);
+ commit_editor.set_show_gutter(false, cx);
+ commit_editor.set_show_wrap_guides(false, cx);
+ commit_editor.set_show_indent_guides(false, cx);
+ commit_editor.set_placeholder_text("Enter commit message", cx);
+ commit_editor
+}
+
+pub struct QuickCommitModal {
+ focus_handle: FocusHandle,
+ fs: Arc<dyn Fs>,
+ project: Entity<Project>,
+ active_repository: Option<Entity<Repository>>,
+ repository_selector: Entity<RepositorySelector>,
+ commit_editor: Entity<Editor>,
+ width: Option<Pixels>,
+ commit_task: Task<Result<()>>,
+ commit_pending: bool,
+ can_commit: bool,
+ can_commit_all: bool,
+ enable_auto_coauthors: bool,
+}
+
+impl Focusable for QuickCommitModal {
+ fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl EventEmitter<DismissEvent> for QuickCommitModal {}
+impl ModalView for QuickCommitModal {}
+
+impl QuickCommitModal {
+ pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
+ workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| {
+ let project = workspace.project().clone();
+ let fs = workspace.app_state().fs.clone();
+
+ workspace.toggle_modal(window, cx, move |window, cx| {
+ QuickCommitModal::new(project, fs, window, None, cx)
+ });
+ });
+ }
+
+ pub fn new(
+ project: Entity<Project>,
+ fs: Arc<dyn Fs>,
+ window: &mut Window,
+ commit_message_buffer: Option<Entity<Buffer>>,
+ cx: &mut Context<Self>,
+ ) -> Self {
+ let git_state = project.read(cx).git_state().clone();
+ let active_repository = project.read(cx).active_repository(cx);
+
+ let focus_handle = cx.focus_handle();
+
+ let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
+ commit_editor.update(cx, |editor, cx| {
+ editor.clear(window, cx);
+ });
+
+ let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
+
+ Self {
+ focus_handle,
+ fs,
+ project,
+ active_repository,
+ repository_selector,
+ commit_editor,
+ width: None,
+ commit_task: Task::ready(Ok(())),
+ commit_pending: false,
+ can_commit: false,
+ can_commit_all: false,
+ enable_auto_coauthors: true,
+ }
+ }
+
+ pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ let all_repositories = self
+ .project
+ .read(cx)
+ .git_state()
+ .read(cx)
+ .all_repositories();
+ let entry_count = self
+ .active_repository
+ .as_ref()
+ .map_or(0, |repo| repo.read(cx).entry_count());
+
+ let changes_string = match entry_count {
+ 0 => "No changes".to_string(),
+ 1 => "1 change".to_string(),
+ n => format!("{} changes", n),
+ };
+
+ div().absolute().top_0().right_0().child(
+ panel_icon_button("open_change_list", IconName::PanelRight)
+ .disabled(true)
+ .tooltip(Tooltip::text("Changes list coming soon!")),
+ )
+ }
+
+ 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 can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
+ let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
+
+ let focus_handle_1 = self.focus_handle(cx).clone();
+ let focus_handle_2 = self.focus_handle(cx).clone();
+
+ let panel_editor_style = panel_editor_style(true, window, cx);
+
+ let commit_staged_button = panel_filled_button("Commit")
+ .tooltip(move |window, cx| {
+ let focus_handle = focus_handle_1.clone();
+ Tooltip::for_action_in(
+ "Commit all staged changes",
+ &CommitChanges,
+ &focus_handle,
+ window,
+ cx,
+ )
+ })
+ .when(!can_commit, |this| {
+ this.disabled(true).style(ButtonStyle::Transparent)
+ });
+ // .on_click({
+ // let name_and_email = name_and_email.clone();
+ // cx.listener(move |this, _: &ClickEvent, window, cx| {
+ // this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
+ // })
+ // });
+
+ let commit_all_button = panel_filled_button("Commit All")
+ .tooltip(move |window, cx| {
+ let focus_handle = focus_handle_2.clone();
+ Tooltip::for_action_in(
+ "Commit all changes, including unstaged changes",
+ &CommitAllChanges,
+ &focus_handle,
+ window,
+ cx,
+ )
+ })
+ .when(!can_commit, |this| {
+ this.disabled(true).style(ButtonStyle::Transparent)
+ });
+ // .on_click({
+ // let name_and_email = name_and_email.clone();
+ // cx.listener(move |this, _: &ClickEvent, window, cx| {
+ // this.commit_tracked_changes(
+ // &CommitAllChanges,
+ // name_and_email.clone(),
+ // window,
+ // cx,
+ // )
+ // })
+ // });
+
+ let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup)
+ .icon_color(if self.enable_auto_coauthors {
+ Color::Muted
+ } else {
+ Color::Accent
+ })
+ .icon_size(IconSize::Small)
+ .toggle_state(self.enable_auto_coauthors)
+ // .on_click({
+ // cx.listener(move |this, _: &ClickEvent, _, cx| {
+ // this.toggle_auto_coauthors(cx);
+ // })
+ // })
+ .tooltip(move |window, cx| {
+ Tooltip::with_meta(
+ "Toggle automatic co-authors",
+ None,
+ "Automatically adds current collaborators",
+ window,
+ cx,
+ )
+ });
+
+ panel_editor_container(window, cx)
+ .id("commit-editor-container")
+ .relative()
+ .w_full()
+ .border_t_1()
+ .border_color(cx.theme().colors().border)
+ .h(px(140.))
+ .bg(cx.theme().colors().editor_background)
+ .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
+ window.focus(&editor_focus_handle);
+ }))
+ .child(EditorElement::new(&self.commit_editor, panel_editor_style))
+ .child(div().flex_1())
+ .child(
+ h_flex()
+ .items_center()
+ .h_8()
+ .justify_between()
+ .gap_1()
+ .child(co_author_button)
+ .child(commit_all_button)
+ .child(commit_staged_button),
+ )
+ }
+
+ pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(h_flex().child("cmd+esc clear message"))
+ .child(
+ h_flex()
+ .child(panel_filled_button("Commit"))
+ .child(panel_filled_button("Commit All")),
+ )
+ }
+
+ fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
+ cx.emit(DismissEvent);
+ }
+}
+
+impl Render for QuickCommitModal {
+ fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
+ v_flex()
+ .id("quick-commit-modal")
+ .key_context("QuickCommit")
+ .on_action(cx.listener(Self::dismiss))
+ .relative()
+ .bg(cx.theme().colors().elevated_surface_background)
+ .rounded(px(16.))
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .py_2()
+ .px_4()
+ .w(self.width.unwrap_or(px(640.)))
+ .h(px(450.))
+ .flex_1()
+ .overflow_hidden()
+ .child(self.render_header(window, cx))
+ .child(
+ v_flex()
+ .flex_1()
+ // TODO: pass name_and_email
+ .child(self.render_commit_editor(None, window, cx)),
+ )
+ .child(self.render_footer(window, cx))
+ }
+}
@@ -12,6 +12,9 @@ workspace = true
path = "src/panel.rs"
[dependencies]
+editor.workspace = true
gpui.workspace = true
+settings.workspace = true
+theme.workspace = true
ui.workspace = true
workspace.workspace = true
@@ -1,5 +1,8 @@
//! # panel
-use gpui::actions;
+use editor::{Editor, EditorElement, EditorStyle};
+use gpui::{actions, Entity, TextStyle};
+use settings::Settings;
+use theme::ThemeSettings;
use ui::{prelude::*, Tab};
actions!(panel, [NextPanelTab, PreviousPanelTab]);
@@ -46,7 +49,8 @@ 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)
- .layer(ui::ElevationIndex::Surface)
+ // TODO: Change this once we use on_surface_bg in button_like
+ .layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
}
@@ -57,10 +61,65 @@ pub fn panel_filled_button(label: impl Into<SharedString>) -> ui::Button {
pub fn panel_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
let id = ElementId::Name(id.into());
ui::IconButton::new(id, icon)
- .layer(ui::ElevationIndex::Surface)
+ // TODO: Change this once we use on_surface_bg in button_like
+ .layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
}
pub fn panel_filled_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
panel_icon_button(id, icon).style(ui::ButtonStyle::Filled)
}
+
+pub fn panel_editor_container(_window: &mut Window, cx: &mut App) -> Div {
+ v_flex()
+ .size_full()
+ .gap(px(8.))
+ .p_2()
+ .bg(cx.theme().colors().editor_background)
+}
+
+pub fn panel_editor_style(monospace: bool, window: &mut Window, cx: &mut App) -> EditorStyle {
+ let settings = ThemeSettings::get_global(cx);
+
+ let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
+
+ let (font_family, font_features, font_weight, line_height) = if monospace {
+ (
+ settings.buffer_font.family.clone(),
+ settings.buffer_font.features.clone(),
+ settings.buffer_font.weight,
+ font_size * settings.buffer_line_height.value(),
+ )
+ } else {
+ (
+ settings.ui_font.family.clone(),
+ settings.ui_font.features.clone(),
+ settings.ui_font.weight,
+ window.line_height(),
+ )
+ };
+
+ EditorStyle {
+ background: cx.theme().colors().editor_background,
+ local_player: cx.theme().players().local(),
+ text: TextStyle {
+ color: cx.theme().colors().text,
+ font_family,
+ font_features,
+ font_size: TextSize::Small.rems(cx).into(),
+ font_weight,
+ line_height: line_height.into(),
+ ..Default::default()
+ },
+ ..Default::default()
+ }
+}
+
+pub fn panel_editor_element(
+ editor: &Entity<Editor>,
+ monospace: bool,
+ window: &mut Window,
+ cx: &mut App,
+) -> EditorElement {
+ EditorElement::new(editor, panel_editor_style(monospace, window, cx))
+}
@@ -95,7 +95,7 @@ pub struct Button {
selected_icon: Option<IconName>,
selected_icon_color: Option<Color>,
key_binding: Option<KeyBinding>,
- keybinding_position: KeybindingPosition,
+ key_binding_position: KeybindingPosition,
alpha: Option<f32>,
}
@@ -121,7 +121,7 @@ impl Button {
selected_icon: None,
selected_icon_color: None,
key_binding: None,
- keybinding_position: KeybindingPosition::default(),
+ key_binding_position: KeybindingPosition::default(),
alpha: None,
}
}
@@ -197,7 +197,7 @@ impl Button {
/// This method allows you to specify where the keybinding should be displayed
/// in relation to the button's label.
pub fn key_binding_position(mut self, position: KeybindingPosition) -> Self {
- self.keybinding_position = position;
+ self.key_binding_position = position;
self
}
@@ -427,7 +427,7 @@ impl RenderOnce for Button {
.child(
h_flex()
.when(
- self.keybinding_position == KeybindingPosition::Start,
+ self.key_binding_position == KeybindingPosition::Start,
|this| this.flex_row_reverse(),
)
.gap(DynamicSpacing::Base06.rems(cx))
@@ -506,7 +506,9 @@ impl RenderOnce for ButtonLike {
.group("")
.flex_none()
.h(self.height.unwrap_or(self.size.rems().into()))
- .when_some(self.width, |this, width| this.w(width).justify_center())
+ .when_some(self.width, |this, width| {
+ this.w(width).justify_center().text_center()
+ })
.when_some(self.rounding, |this, rounding| match rounding {
ButtonLikeRounding::All => this.rounded_md(),
ButtonLikeRounding::Left => this.rounded_l_md(),
@@ -22,6 +22,7 @@ pub struct IconButton {
icon_size: IconSize,
icon_color: Color,
selected_icon: Option<IconName>,
+ selected_icon_color: Option<Color>,
indicator: Option<Indicator>,
indicator_border_color: Option<Hsla>,
alpha: Option<f32>,
@@ -36,6 +37,7 @@ impl IconButton {
icon_size: IconSize::default(),
icon_color: Color::Default,
selected_icon: None,
+ selected_icon_color: None,
indicator: None,
indicator_border_color: None,
alpha: None,
@@ -69,6 +71,12 @@ impl IconButton {
self
}
+ /// Sets the icon color used when the button is in a selected state.
+ pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
+ self.selected_icon_color = color.into();
+ self
+ }
+
pub fn indicator(mut self, indicator: Indicator) -> Self {
self.indicator = Some(indicator);
self
@@ -181,6 +189,7 @@ impl RenderOnce for IconButton {
.disabled(is_disabled)
.toggle_state(is_selected)
.selected_icon(self.selected_icon)
+ .selected_icon_color(self.selected_icon_color)
.when_some(selected_style, |this, style| this.selected_style(style))
.when_some(self.indicator, |this, indicator| {
this.indicator(indicator)
@@ -450,6 +450,64 @@ impl RenderOnce for Switch {
}
}
+/// A [`Switch`] that has a [`Label`].
+#[derive(IntoElement)]
+// #[component(scope = "input")]
+pub struct SwitchWithLabel {
+ id: ElementId,
+ label: Label,
+ toggle_state: ToggleState,
+ on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
+ disabled: bool,
+}
+
+impl SwitchWithLabel {
+ /// Creates a switch with an attached label.
+ pub fn new(
+ id: impl Into<ElementId>,
+ label: Label,
+ toggle_state: impl Into<ToggleState>,
+ on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
+ ) -> Self {
+ Self {
+ id: id.into(),
+ label,
+ toggle_state: toggle_state.into(),
+ on_click: Arc::new(on_click),
+ disabled: false,
+ }
+ }
+
+ /// Sets the disabled state of the [`SwitchWithLabel`].
+ pub fn disabled(mut self, disabled: bool) -> Self {
+ self.disabled = disabled;
+ self
+ }
+}
+
+impl RenderOnce for SwitchWithLabel {
+ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ h_flex()
+ .id(SharedString::from(format!("{}-container", self.id)))
+ .gap(DynamicSpacing::Base08.rems(cx))
+ .child(
+ Switch::new(self.id.clone(), self.toggle_state)
+ .disabled(self.disabled)
+ .on_click({
+ let on_click = self.on_click.clone();
+ move |checked, window, cx| {
+ (on_click)(checked, window, cx);
+ }
+ }),
+ )
+ .child(
+ div()
+ .id(SharedString::from(format!("{}-label", self.id)))
+ .child(self.label),
+ )
+ }
+}
+
impl ComponentPreview for Checkbox {
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
v_flex()