diff --git a/Cargo.lock b/Cargo.lock index 3fb5ee2f9f..40f302224a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9044,7 +9044,10 @@ dependencies = [ name = "panel" version = "0.1.0" dependencies = [ + "editor", "gpui", + "settings", + "theme", "ui", "workspace", ] diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index c92eb56e52..d8a676313c 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -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>, + commit_editor: Entity, + conflicted_count: usize, + conflicted_staged_count: usize, current_modifiers: Modifiers, + enable_auto_coauthors: bool, + entries: Vec, + entries_by_path: collections::HashMap, focus_handle: FocusHandle, fs: Arc, hide_scrollbar_task: Option>, + new_count: usize, + new_staged_count: usize, + pending: Vec, + pending_commit: Option>, pending_serialization: Task>, - workspace: WeakEntity, project: Entity, - active_repository: Option>, + repository_selector: Entity, scroll_handle: UniformListScrollHandle, scrollbar_state: ScrollbarState, selected_entry: Option, show_scrollbar: bool, - update_visible_entries_task: Task<()>, - repository_selector: Entity, - commit_editor: Entity, - entries: Vec, - entries_by_path: collections::HashMap, - width: Option, - pending: Vec, - pending_commit: Option>, - - conflicted_staged_count: usize, - conflicted_count: usize, - tracked_staged_count: usize, tracked_count: usize, - new_staged_count: usize, - new_count: usize, + tracked_staged_count: usize, + update_visible_entries_task: Task<()>, + width: Option, + workspace: WeakEntity, } 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), - entries: Vec::new(), - entries_by_path: HashMap::default(), - pending: Vec::new(), - current_modifiers: window.modifiers(), - width: Some(px(360.)), - scrollbar_state: ScrollbarState::new(scroll_handle.clone()) - .parent_entity(&cx.entity()), - repository_selector, - selected_entry: None, - show_scrollbar: false, - hide_scrollbar_task: None, - update_visible_entries_task: Task::ready(()), - pending_commit: None, active_repository, - scroll_handle, - fs, commit_editor, - project, - workspace, conflicted_count: 0, conflicted_staged_count: 0, - tracked_staged_count: 0, - tracked_count: 0, - new_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(), + pending_commit: None, + pending_serialization: Task::ready(None), + project, + repository_selector, + scroll_handle, + scrollbar_state, + selected_entry: None, + show_scrollbar: false, + tracked_count: 0, + tracked_staged_count: 0, + update_visible_entries_task: Task::ready(()), + width: Some(px(360.)), + workspace, }; 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.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) -> impl IntoElement { + pub fn render_commit_editor( + &self, + window: &mut Window, + cx: &mut Context, + ) -> 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) -> 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)) } } diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index a8313aa9d5..300c589ecd 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -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 diff --git a/crates/git_ui/src/quick_commit.rs b/crates/git_ui/src/quick_commit.rs new file mode 100644 index 0000000000..be7f3fa84d --- /dev/null +++ b/crates/git_ui/src/quick_commit.rs @@ -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>, + 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, + project: Entity, + active_repository: Option>, + repository_selector: Entity, + commit_editor: Entity, + width: Option, + commit_task: Task>, + 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 for QuickCommitModal {} +impl ModalView for QuickCommitModal {} + +impl QuickCommitModal { + pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context) { + 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, + fs: Arc, + window: &mut Window, + commit_message_buffer: Option>, + cx: &mut Context, + ) -> 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) -> 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, + ) -> 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) -> 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) { + 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)) + } +} diff --git a/crates/panel/Cargo.toml b/crates/panel/Cargo.toml index 4e7c81804d..3c51e6d6dc 100644 --- a/crates/panel/Cargo.toml +++ b/crates/panel/Cargo.toml @@ -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 diff --git a/crates/panel/src/panel.rs b/crates/panel/src/panel.rs index 017a362b0e..934d8281a3 100644 --- a/crates/panel/src/panel.rs +++ b/crates/panel/src/panel.rs @@ -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) -> 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) -> ui::Button { pub fn panel_icon_button(id: impl Into, 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, 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, + monospace: bool, + window: &mut Window, + cx: &mut App, +) -> EditorElement { + EditorElement::new(editor, panel_editor_style(monospace, window, cx)) +} diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 4194b3c8d2..0209fd3d17 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -95,7 +95,7 @@ pub struct Button { selected_icon: Option, selected_icon_color: Option, key_binding: Option, - keybinding_position: KeybindingPosition, + key_binding_position: KeybindingPosition, alpha: Option, } @@ -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)) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 0b78be0786..96d093c249 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -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(), diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index c28c5ae9ac..204ea8e564 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -22,6 +22,7 @@ pub struct IconButton { icon_size: IconSize, icon_color: Color, selected_icon: Option, + selected_icon_color: Option, indicator: Option, indicator_border_color: Option, alpha: Option, @@ -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>) -> 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) diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index 13811883bc..1f095065c3 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -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, + disabled: bool, +} + +impl SwitchWithLabel { + /// Creates a switch with an attached label. + pub fn new( + id: impl Into, + label: Label, + toggle_state: impl Into, + 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()