git_ui: Update git panel commit editor, start on quick commit
- 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
This commit is contained in:
parent
69d415c8d0
commit
de8d4d00ce
10 changed files with 586 additions and 120 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -9044,7 +9044,10 @@ dependencies = [
|
||||||
name = "panel"
|
name = "panel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"editor",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"settings",
|
||||||
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,33 +6,32 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::actions::MoveToEnd;
|
use editor::{
|
||||||
use editor::scroll::ScrollbarAutoHide;
|
actions::MoveToEnd, scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode,
|
||||||
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
EditorSettings, MultiBuffer, ShowScrollbar,
|
||||||
use git::repository::RepoPath;
|
};
|
||||||
use git::status::FileStatus;
|
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||||
use git::{Commit, ToggleStaged};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use language::{Buffer, File};
|
use language::{Buffer, File};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
use multi_buffer::ExcerptInfo;
|
use multi_buffer::ExcerptInfo;
|
||||||
use panel::PanelHeader;
|
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
|
||||||
use project::git::{GitEvent, Repository};
|
use project::{
|
||||||
use project::{Fs, Project, ProjectPath};
|
git::{GitEvent, Repository},
|
||||||
|
Fs, Project, ProjectPath,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors,
|
prelude::*, ButtonLike, Checkbox, CheckboxWithLabel, Divider, DividerColor, ElevationIndex,
|
||||||
ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
IndentGuideColors, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::notifications::{DetachAndPromptErr, NotificationId};
|
|
||||||
use workspace::Toast;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
Workspace,
|
notifications::{DetachAndPromptErr, NotificationId},
|
||||||
|
Toast, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
|
@ -147,33 +146,33 @@ struct PendingOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GitPanel {
|
pub struct GitPanel {
|
||||||
|
active_repository: Option<Entity<Repository>>,
|
||||||
|
commit_editor: Entity<Editor>,
|
||||||
|
conflicted_count: usize,
|
||||||
|
conflicted_staged_count: usize,
|
||||||
current_modifiers: Modifiers,
|
current_modifiers: Modifiers,
|
||||||
|
enable_auto_coauthors: bool,
|
||||||
|
entries: Vec<GitListEntry>,
|
||||||
|
entries_by_path: collections::HashMap<RepoPath, usize>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
|
new_count: usize,
|
||||||
|
new_staged_count: usize,
|
||||||
|
pending: Vec<PendingOperation>,
|
||||||
|
pending_commit: Option<Task<()>>,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
active_repository: Option<Entity<Repository>>,
|
repository_selector: Entity<RepositorySelector>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
selected_entry: Option<usize>,
|
selected_entry: Option<usize>,
|
||||||
show_scrollbar: bool,
|
show_scrollbar: bool,
|
||||||
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,
|
tracked_count: usize,
|
||||||
new_staged_count: usize,
|
tracked_staged_count: usize,
|
||||||
new_count: usize,
|
update_visible_entries_task: Task<()>,
|
||||||
|
width: Option<Pixels>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_message_editor(
|
fn commit_message_editor(
|
||||||
|
@ -181,23 +180,10 @@ fn commit_message_editor(
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<'_, Editor>,
|
cx: &mut Context<'_, Editor>,
|
||||||
) -> 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 mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
|
||||||
Editor::new(
|
Editor::new(
|
||||||
EditorMode::AutoHeight { max_lines: 10 },
|
EditorMode::AutoHeight { max_lines: 6 },
|
||||||
buffer,
|
buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
@ -205,13 +191,12 @@ fn commit_message_editor(
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Editor::auto_height(10, window, cx)
|
Editor::auto_height(6, window, cx)
|
||||||
};
|
};
|
||||||
commit_editor.set_use_autoclose(false);
|
commit_editor.set_use_autoclose(false);
|
||||||
commit_editor.set_show_gutter(false, cx);
|
commit_editor.set_show_gutter(false, cx);
|
||||||
commit_editor.set_show_wrap_guides(false, cx);
|
commit_editor.set_show_wrap_guides(false, cx);
|
||||||
commit_editor.set_show_indent_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.set_placeholder_text("Enter commit message", cx);
|
||||||
commit_editor
|
commit_editor
|
||||||
}
|
}
|
||||||
|
@ -260,37 +245,40 @@ impl GitPanel {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
let scrollbar_state =
|
||||||
|
ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity());
|
||||||
|
|
||||||
let repository_selector =
|
let repository_selector =
|
||||||
cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
|
cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
|
||||||
|
|
||||||
let mut git_panel = Self {
|
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,
|
active_repository,
|
||||||
scroll_handle,
|
|
||||||
fs,
|
|
||||||
commit_editor,
|
commit_editor,
|
||||||
project,
|
|
||||||
workspace,
|
|
||||||
conflicted_count: 0,
|
conflicted_count: 0,
|
||||||
conflicted_staged_count: 0,
|
conflicted_staged_count: 0,
|
||||||
tracked_staged_count: 0,
|
current_modifiers: window.modifiers(),
|
||||||
tracked_count: 0,
|
enable_auto_coauthors: true,
|
||||||
new_staged_count: 0,
|
entries: Vec::new(),
|
||||||
|
entries_by_path: HashMap::default(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
fs,
|
||||||
|
hide_scrollbar_task: None,
|
||||||
new_count: 0,
|
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.schedule_update(false, window, cx);
|
||||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||||
|
@ -990,6 +978,26 @@ impl GitPanel {
|
||||||
cx.notify();
|
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) {
|
fn update_counts(&mut self, repo: &Repository) {
|
||||||
self.conflicted_count = 0;
|
self.conflicted_count = 0;
|
||||||
self.conflicted_staged_count = 0;
|
self.conflicted_staged_count = 0;
|
||||||
|
@ -1043,21 +1051,6 @@ impl GitPanel {
|
||||||
self.conflicted_count > 0 && self.conflicted_count != self.conflicted_staged_count
|
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) {
|
fn show_err_toast(&self, e: anyhow::Error, cx: &mut App) {
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return;
|
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 editor = self.commit_editor.clone();
|
||||||
let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
|
let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
|
||||||
&& self.pending_commit.is_none()
|
&& self.pending_commit.is_none()
|
||||||
&& !editor.read(cx).is_empty(cx)
|
&& !editor.read(cx).is_empty(cx)
|
||||||
&& !self.has_unstaged_conflicts()
|
&& !self.has_unstaged_conflicts()
|
||||||
&& self.has_write_access(cx);
|
&& 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 editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||||
|
|
||||||
let focus_handle_1 = self.focus_handle(cx).clone();
|
let focus_handle_1 = self.focus_handle(cx).clone();
|
||||||
|
@ -1186,8 +1187,7 @@ impl GitPanel {
|
||||||
"Commit All"
|
"Commit All"
|
||||||
};
|
};
|
||||||
|
|
||||||
let commit_button = self
|
let commit_button = panel_filled_button(title)
|
||||||
.panel_button("commit-changes", title)
|
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |window, cx| {
|
||||||
let focus_handle = focus_handle_1.clone();
|
let focus_handle = focus_handle_1.clone();
|
||||||
Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
|
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))
|
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(
|
let enable_coauthors = CheckboxWithLabel::new(
|
||||||
v_flex()
|
"enable-coauthors",
|
||||||
.id("commit-editor-container")
|
Label::new("Add Co-authors")
|
||||||
.relative()
|
.color(Color::Disabled)
|
||||||
.h_full()
|
.size(LabelSize::XSmall),
|
||||||
.py_2p5()
|
self.enable_auto_coauthors.into(),
|
||||||
.px_3()
|
cx.listener(move |this, _, _, cx| this.toggle_auto_coauthors(cx)),
|
||||||
.bg(cx.theme().colors().editor_background)
|
);
|
||||||
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
|
|
||||||
window.focus(&editor_focus_handle);
|
let footer_size = px(32.);
|
||||||
}))
|
let gap = px(16.0);
|
||||||
.child(self.commit_editor.clone())
|
|
||||||
.child(
|
let max_height = window.line_height() * 6. + gap + footer_size;
|
||||||
h_flex()
|
|
||||||
.absolute()
|
panel_editor_container(window, cx)
|
||||||
.bottom_2p5()
|
.id("commit-editor-container")
|
||||||
.right_3()
|
.relative()
|
||||||
.gap_1p5()
|
.h(max_height)
|
||||||
.child(div().gap_1().flex_grow())
|
.w_full()
|
||||||
.child(commit_button),
|
.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 {
|
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
@ -1348,6 +1370,7 @@ impl GitPanel {
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.flex_grow()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
uniform_list(cx.entity().clone(), "entries", entry_count, {
|
uniform_list(cx.entity().clone(), "entries", entry_count, {
|
||||||
|
@ -1496,7 +1519,7 @@ impl GitPanel {
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.start_slot(start_slot)
|
.start_slot(start_slot)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.focused(selected && self.focus_handle.is_focused(window))
|
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||||
.disabled(!has_write_access)
|
.disabled(!has_write_access)
|
||||||
.on_click({
|
.on_click({
|
||||||
cx.listener(move |this, _, _, cx| {
|
cx.listener(move |this, _, _, cx| {
|
||||||
|
@ -1599,7 +1622,7 @@ impl GitPanel {
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.start_slot(start_slot)
|
.start_slot(start_slot)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.focused(selected && self.focus_handle.is_focused(window))
|
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||||
.disabled(!has_write_access)
|
.disabled(!has_write_access)
|
||||||
.on_click({
|
.on_click({
|
||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
@ -1705,7 +1728,7 @@ impl Render for GitPanel {
|
||||||
} else {
|
} else {
|
||||||
self.render_empty_state(cx).into_any_element()
|
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;
|
pub mod git_panel;
|
||||||
mod git_panel_settings;
|
mod git_panel_settings;
|
||||||
pub mod project_diff;
|
pub mod project_diff;
|
||||||
|
// mod quick_commit;
|
||||||
pub mod repository_selector;
|
pub mod repository_selector;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
GitPanelSettings::register(cx);
|
GitPanelSettings::register(cx);
|
||||||
branch_picker::init(cx);
|
branch_picker::init(cx);
|
||||||
cx.observe_new(ProjectDiff::register).detach();
|
cx.observe_new(ProjectDiff::register).detach();
|
||||||
|
// quick_commit::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add updated status colors to theme
|
// TODO: Add updated status colors to theme
|
||||||
|
|
307
crates/git_ui/src/quick_commit.rs
Normal file
307
crates/git_ui/src/quick_commit.rs
Normal file
|
@ -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"
|
path = "src/panel.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
editor.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
settings.workspace = true
|
||||||
|
theme.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
//! # panel
|
//! # panel
|
||||||
use gpui::actions;
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
|
use gpui::{actions, Entity, TextStyle};
|
||||||
|
use settings::Settings;
|
||||||
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, Tab};
|
use ui::{prelude::*, Tab};
|
||||||
|
|
||||||
actions!(panel, [NextPanelTab, PreviousPanelTab]);
|
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());
|
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)
|
||||||
.layer(ui::ElevationIndex::Surface)
|
// TODO: Change this once we use on_surface_bg in button_like
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
.size(ui::ButtonSize::Compact)
|
.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 {
|
pub fn panel_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
|
||||||
let id = ElementId::Name(id.into());
|
let id = ElementId::Name(id.into());
|
||||||
ui::IconButton::new(id, icon)
|
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)
|
.size(ui::ButtonSize::Compact)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn panel_filled_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
|
pub fn panel_filled_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
|
||||||
panel_icon_button(id, icon).style(ui::ButtonStyle::Filled)
|
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: Option<IconName>,
|
||||||
selected_icon_color: Option<Color>,
|
selected_icon_color: Option<Color>,
|
||||||
key_binding: Option<KeyBinding>,
|
key_binding: Option<KeyBinding>,
|
||||||
keybinding_position: KeybindingPosition,
|
key_binding_position: KeybindingPosition,
|
||||||
alpha: Option<f32>,
|
alpha: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ impl Button {
|
||||||
selected_icon: None,
|
selected_icon: None,
|
||||||
selected_icon_color: None,
|
selected_icon_color: None,
|
||||||
key_binding: None,
|
key_binding: None,
|
||||||
keybinding_position: KeybindingPosition::default(),
|
key_binding_position: KeybindingPosition::default(),
|
||||||
alpha: None,
|
alpha: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ impl Button {
|
||||||
/// This method allows you to specify where the keybinding should be displayed
|
/// This method allows you to specify where the keybinding should be displayed
|
||||||
/// in relation to the button's label.
|
/// in relation to the button's label.
|
||||||
pub fn key_binding_position(mut self, position: KeybindingPosition) -> Self {
|
pub fn key_binding_position(mut self, position: KeybindingPosition) -> Self {
|
||||||
self.keybinding_position = position;
|
self.key_binding_position = position;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +427,7 @@ impl RenderOnce for Button {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.when(
|
.when(
|
||||||
self.keybinding_position == KeybindingPosition::Start,
|
self.key_binding_position == KeybindingPosition::Start,
|
||||||
|this| this.flex_row_reverse(),
|
|this| this.flex_row_reverse(),
|
||||||
)
|
)
|
||||||
.gap(DynamicSpacing::Base06.rems(cx))
|
.gap(DynamicSpacing::Base06.rems(cx))
|
||||||
|
|
|
@ -506,7 +506,9 @@ impl RenderOnce for ButtonLike {
|
||||||
.group("")
|
.group("")
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.h(self.height.unwrap_or(self.size.rems().into()))
|
.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 {
|
.when_some(self.rounding, |this, rounding| match rounding {
|
||||||
ButtonLikeRounding::All => this.rounded_md(),
|
ButtonLikeRounding::All => this.rounded_md(),
|
||||||
ButtonLikeRounding::Left => this.rounded_l_md(),
|
ButtonLikeRounding::Left => this.rounded_l_md(),
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub struct IconButton {
|
||||||
icon_size: IconSize,
|
icon_size: IconSize,
|
||||||
icon_color: Color,
|
icon_color: Color,
|
||||||
selected_icon: Option<IconName>,
|
selected_icon: Option<IconName>,
|
||||||
|
selected_icon_color: Option<Color>,
|
||||||
indicator: Option<Indicator>,
|
indicator: Option<Indicator>,
|
||||||
indicator_border_color: Option<Hsla>,
|
indicator_border_color: Option<Hsla>,
|
||||||
alpha: Option<f32>,
|
alpha: Option<f32>,
|
||||||
|
@ -36,6 +37,7 @@ impl IconButton {
|
||||||
icon_size: IconSize::default(),
|
icon_size: IconSize::default(),
|
||||||
icon_color: Color::Default,
|
icon_color: Color::Default,
|
||||||
selected_icon: None,
|
selected_icon: None,
|
||||||
|
selected_icon_color: None,
|
||||||
indicator: None,
|
indicator: None,
|
||||||
indicator_border_color: None,
|
indicator_border_color: None,
|
||||||
alpha: None,
|
alpha: None,
|
||||||
|
@ -69,6 +71,12 @@ impl IconButton {
|
||||||
self
|
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 {
|
pub fn indicator(mut self, indicator: Indicator) -> Self {
|
||||||
self.indicator = Some(indicator);
|
self.indicator = Some(indicator);
|
||||||
self
|
self
|
||||||
|
@ -181,6 +189,7 @@ impl RenderOnce for IconButton {
|
||||||
.disabled(is_disabled)
|
.disabled(is_disabled)
|
||||||
.toggle_state(is_selected)
|
.toggle_state(is_selected)
|
||||||
.selected_icon(self.selected_icon)
|
.selected_icon(self.selected_icon)
|
||||||
|
.selected_icon_color(self.selected_icon_color)
|
||||||
.when_some(selected_style, |this, style| this.selected_style(style))
|
.when_some(selected_style, |this, style| this.selected_style(style))
|
||||||
.when_some(self.indicator, |this, indicator| {
|
.when_some(self.indicator, |this, indicator| {
|
||||||
this.indicator(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 {
|
impl ComponentPreview for Checkbox {
|
||||||
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue