feat(ui): wire Git actions and operation dialogs

This commit is contained in:
2026-06-18 19:34:51 -05:00
parent 2fd4da87cd
commit 2c9b370ed9

View File

@@ -40,9 +40,21 @@ std::string g_notice;
bool g_init_popup = false;
bool g_about_popup = false;
bool g_licenses_popup = false;
bool g_branch_create_popup = false;
bool g_tag_create_popup = false;
bool g_remote_add_popup = false;
bool g_worktree_add_popup = false;
bool g_submodule_add_popup = false;
bool g_discard_all_popup = false;
std::array<char, 256> g_git_name{};
std::array<char, 1024> g_git_value{};
std::array<char, 1024> g_git_path{};
std::string g_git_target;
enum class FileViewMode { path, tree };
FileViewMode g_file_view_mode = FileViewMode::path;
bool g_view_all_files = false;
bool g_sidebar_workspace_view = false;
bool g_request_branch_selector = false;
std::array<char, 128> g_commit_summary{};
std::array<char, 1024> g_commit_description{};
float g_ui_scale = 1.0f;
@@ -289,7 +301,17 @@ bool sidebar_section_header(const char* label, int count, const char* add_toolti
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.43f, 0.90f, 0.51f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.25f, 0.72f, 0.35f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(5.0f), ui(1.0f)});
if (ImGui::SmallButton(ICON_FA_PLUS "##add")) g_notice = add_notice;
if (ImGui::SmallButton(ICON_FA_PLUS "##add")) {
const std::string section = label;
if (section.find("LOCAL") != std::string::npos) {
g_git_target.clear();
g_branch_create_popup = true;
}
else if (section.find("REMOTE") != std::string::npos) g_remote_add_popup = true;
else if (section.find("WORKTREES") != std::string::npos) g_worktree_add_popup = true;
else if (section.find("SUBMODULES") != std::string::npos) g_submodule_add_popup = true;
else g_notice = add_notice;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) ImGui::SetTooltip("%s", add_tooltip);
ImGui::PopStyleVar();
ImGui::PopStyleColor(2);
@@ -344,7 +366,8 @@ void section(const char* label, const std::vector<std::string>& items, const cha
const bool open = sidebar_section_header(label, static_cast<int>(items.size()), add_tooltip, add_notice);
if (open && body_height >= ui(1.0f)) {
ImGui::BeginChild((std::string(label) + "##body").c_str(), {-1, body_height});
for (const auto& item : items) {
const std::vector<std::string> snapshot = items;
for (const auto& item : snapshot) {
sidebar_item_row(item_icon, item, item);
sidebar_item_context(item, kind);
}
@@ -379,7 +402,10 @@ void branch_context(const std::string& branch, bool remote) {
if (!ImGui::BeginPopupContextItem()) return;
if (!remote && ImGui::MenuItem(ICON_FA_CODE_BRANCH " Checkout"))
g_git_manager->checkoutBranch(repo(), branch, g_notice);
if (remote && ImGui::MenuItem(ICON_FA_DOWNLOAD " Fetch")) g_notice = "Remote fetch is not wired yet";
if (remote && ImGui::MenuItem(ICON_FA_DOWNLOAD " Fetch")) {
const size_t slash = branch.find('/');
g_git_manager->fetch(repo(), slash == std::string::npos ? branch : branch.substr(0, slash), g_notice);
}
ImGui::Separator();
if (ImGui::MenuItem(ICON_FA_COPY " Copy branch name")) ImGui::SetClipboardText(branch.c_str());
ImGui::EndPopup();
@@ -434,12 +460,26 @@ void draw_sidebar(float width) {
ImGui::BeginChild("sidebar", {width, -ui(28.0f)}, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
const float mode_button_width = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(ICON_FA_LIST " List", {mode_button_width, ui(30)})) {}
const bool list_selected = !g_sidebar_workspace_view;
if (list_selected)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.25f, 0.40f, 1.0f));
if (ImGui::Button(ICON_FA_LIST " List", {mode_button_width, ui(30)})) g_sidebar_workspace_view = false;
if (list_selected) ImGui::PopStyleColor();
ImGui::SameLine();
if (ImGui::Button(ICON_FA_LAYER_GROUP " Workspace", {mode_button_width, ui(30)})) {}
ImGui::TextDisabled(ICON_FA_EYE " VIEWING %d", static_cast<int>(repo().local_branches.size() + repo().remote_branches.size()));
const bool workspace_selected = g_sidebar_workspace_view;
if (workspace_selected)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.25f, 0.40f, 1.0f));
if (ImGui::Button(ICON_FA_LAYER_GROUP " Workspace", {mode_button_width, ui(30)}))
g_sidebar_workspace_view = true;
if (workspace_selected) ImGui::PopStyleColor();
const int viewing_count = g_sidebar_workspace_view
? static_cast<int>(repo().worktrees.size() + repo().submodules.size())
: static_cast<int>(repo().local_branches.size() + repo().remote_branches.size());
ImGui::TextDisabled(ICON_FA_EYE " VIEWING %d", viewing_count);
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##filter", ICON_FA_MAGNIFYING_GLASS " Search branches...", g_filter.data(), g_filter.size());
ImGui::InputTextWithHint("##filter", g_sidebar_workspace_view
? ICON_FA_MAGNIFYING_GLASS " Search workspace..."
: ICON_FA_MAGNIFYING_GLASS " Search branches...", g_filter.data(), g_filter.size());
ImGui::Spacing();
ImGui::BeginChild("sidebar_sections", {-1, -1}, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
@@ -455,11 +495,15 @@ void draw_sidebar(float width) {
std::array<bool, 4> section_open{};
int open_count = 0;
for (size_t index = 0; index < section_open.size(); ++index) {
if (g_sidebar_workspace_view ? index < 2 : index >= 2) {
section_open[index] = false;
continue;
}
section_open[index] = sidebar_section_is_open(section_ids[index]);
if (!section_open[index]) continue;
++open_count;
}
const float header_space = (ui(22.0f) + 1.0f) * static_cast<float>(section_open.size());
const float header_space = (ui(22.0f) + 1.0f) * 2.0f;
const float splitter_space = ui(5.0f) * open_count;
const float body_space = std::max(0.0f, ImGui::GetContentRegionAvail().y - header_space - splitter_space);
std::array<float, 4> section_heights{};
@@ -488,13 +532,16 @@ void draw_sidebar(float width) {
maximum_heights[index] = std::max(1.0f, logical_body_space - other_heights);
}
branch_section(ICON_FA_HOUSE " LOCAL", repo().local_branches, ICON_FA_CODE_BRANCH, ICON_FA_FOLDER,
"Create local branch", "Branch creation is not wired yet", false, 0, section_heights[0], maximum_heights[0]);
branch_section(ICON_FA_CLOUD " REMOTE", repo().remote_branches, ICON_FA_CODE_BRANCH, ICON_FA_GLOBE,
"Add remote", "Remote creation is not wired yet", true, 1, section_heights[1], maximum_heights[1]);
section(ICON_FA_DIAGRAM_PROJECT " WORKTREES", repo().worktrees, ICON_FA_COMPUTER,
"Add worktree", "Worktree creation is not wired yet", SidebarItemKind::worktree, 2,
section_heights[2], maximum_heights[2]);
if (!g_sidebar_workspace_view) {
branch_section(ICON_FA_HOUSE " LOCAL", repo().local_branches, ICON_FA_CODE_BRANCH, ICON_FA_FOLDER,
"Create local branch", "Create local branch", false, 0, section_heights[0], maximum_heights[0]);
branch_section(ICON_FA_CLOUD " REMOTE", repo().remote_branches, ICON_FA_CODE_BRANCH, ICON_FA_GLOBE,
"Add remote", "Add remote", true, 1, section_heights[1], maximum_heights[1]);
} else {
section(ICON_FA_DIAGRAM_PROJECT " WORKTREES", repo().worktrees, ICON_FA_COMPUTER,
"Add worktree", "Add worktree", SidebarItemKind::worktree, 2,
section_heights[2], maximum_heights[2]);
}
const float submodules_body_height = section_open[3] && section_heights[3] >= ui(1.0f)
? section_heights[3] + ui(5.0f)
@@ -502,9 +549,11 @@ void draw_sidebar(float width) {
const float submodules_block_height = ui(22.0f) + submodules_body_height;
ImGui::SetCursorPosY(std::max(ImGui::GetCursorPosY(),
ImGui::GetContentRegionMax().y - submodules_block_height));
section(ICON_FA_CUBES " SUBMODULES", repo().submodules, ICON_FA_CUBES,
"Add submodule", "Submodule creation is not wired yet", SidebarItemKind::submodule, 3,
section_heights[3], maximum_heights[3]);
if (g_sidebar_workspace_view) {
section(ICON_FA_CUBES " SUBMODULES", repo().submodules, ICON_FA_CUBES,
"Add submodule", "Add submodule", SidebarItemKind::submodule, 3,
section_heights[3], maximum_heights[3]);
}
ImGui::PopStyleVar();
ImGui::EndChild();
ImGui::EndChild();
@@ -680,8 +729,18 @@ void draw_commit_table() {
if (ImGui::MenuItem(ICON_FA_COPY " Copy commit message"))
ImGui::SetClipboardText(commit.summary.c_str());
ImGui::Separator();
if (ImGui::MenuItem(ICON_FA_TAG " Create tag here")) g_notice = "Tag creation is not wired yet";
if (ImGui::MenuItem(ICON_FA_CODE_BRANCH " Create branch here")) g_notice = "Branch creation is not wired yet";
if (ImGui::MenuItem(ICON_FA_TAG " Create tag here")) {
char target[GIT_OID_SHA1_HEXSIZE + 1]{};
git_oid_tostr(target, sizeof(target), &commit.oid);
g_git_target = target;
g_tag_create_popup = true;
}
if (ImGui::MenuItem(ICON_FA_CODE_BRANCH " Create branch here")) {
char target[GIT_OID_SHA1_HEXSIZE + 1]{};
git_oid_tostr(target, sizeof(target), &commit.oid);
g_git_target = target;
g_branch_create_popup = true;
}
ImGui::EndPopup();
}
float chip_x = reference_origin.x + ui(3.0f);
@@ -722,7 +781,8 @@ ImVec4 change_color(FileChangeKind kind) {
return {0.94f, 0.66f, 0.25f, 1};
}
void draw_file_row(const std::string& path, FileChangeKind kind, int id) {
void draw_file_row(const std::string& path, FileChangeKind kind, int id,
bool working_file = false, bool staged = false, const std::string& action_path = {}) {
ImGui::PushID(id);
ImGui::InvisibleButton("##file", {-1, ui(25.0f)});
const ImVec2 minimum = ImGui::GetItemRectMin();
@@ -732,7 +792,15 @@ void draw_file_row(const std::string& path, FileChangeKind kind, int id) {
const float y = minimum.y + (maximum.y - minimum.y - ImGui::GetFontSize()) * 0.5f;
draw->AddText({minimum.x + ui(4.0f), y}, ImGui::ColorConvertFloat4ToU32(change_color(kind)), change_icon(kind));
draw->AddText({minimum.x + ui(20.0f), y}, IM_COL32(174, 179, 187, 255), path.c_str());
const std::string& git_path = action_path.empty() ? path : action_path;
if (ImGui::BeginPopupContextItem()) {
if (working_file && !staged && ImGui::MenuItem(ICON_FA_PLUS " Stage file"))
g_git_manager->stageFile(repo(), git_path, g_notice);
if (working_file && staged && ImGui::MenuItem(ICON_FA_MINUS " Unstage file"))
g_git_manager->unstageFile(repo(), git_path, g_notice);
if (working_file && !staged && ImGui::MenuItem(ICON_FA_TRASH_CAN " Discard changes"))
g_git_manager->discardFile(repo(), git_path, g_notice);
if (working_file) ImGui::Separator();
if (ImGui::MenuItem(ICON_FA_COPY " Copy path")) ImGui::SetClipboardText(path.c_str());
ImGui::EndPopup();
}
@@ -760,16 +828,19 @@ void draw_file_toolbar(bool show_view_all) {
template <typename File>
void draw_files(const std::vector<File>& files, bool staged_filter = false, bool filter_by_stage = false) {
// Git actions reload repository data immediately, so render from a stable snapshot.
const std::vector<File> snapshot = files;
if (g_file_view_mode == FileViewMode::path) {
int id = 0;
for (const auto& file : files) {
for (const auto& file : snapshot) {
if constexpr (requires { file.staged; }) if (filter_by_stage && file.staged != staged_filter) continue;
draw_file_row(file.path, file.kind, id++);
if constexpr (requires { file.staged; }) draw_file_row(file.path, file.kind, id++, true, file.staged);
else draw_file_row(file.path, file.kind, id++);
}
return;
}
std::map<std::string, std::vector<const File*>> groups;
for (const auto& file : files) {
for (const auto& file : snapshot) {
if constexpr (requires { file.staged; }) if (filter_by_stage && file.staged != staged_filter) continue;
const size_t slash = file.path.find('/');
const std::string group = slash == std::string::npos ? "Files" : file.path.substr(0, slash);
@@ -780,7 +851,10 @@ void draw_files(const std::vector<File>& files, bool staged_filter = false, bool
if (ImGui::TreeNodeEx((std::string(ICON_FA_FOLDER) + " " + group).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
for (const File* file : entries) {
const size_t slash = file->path.find('/');
draw_file_row(slash == std::string::npos ? file->path : file->path.substr(slash + 1), file->kind, id++);
const std::string display = slash == std::string::npos ? file->path : file->path.substr(slash + 1);
if constexpr (requires { file->staged; })
draw_file_row(display, file->kind, id++, true, file->staged, file->path);
else draw_file_row(display, file->kind, id++);
}
ImGui::TreePop();
}
@@ -799,7 +873,7 @@ void draw_working_details() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.38f, 0.13f, 0.14f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.88f, 0.36f, 0.36f, 1.0f));
if (ImGui::Button(ICON_FA_TRASH_CAN "##discard_all", {ui(23.0f), ui(23.0f)}))
g_notice = "Discard all is not wired yet";
g_discard_all_popup = true;
ImGui::PopStyleColor(3);
const std::string changes_label = std::to_string(repo().working_files.size()) + " file changes on";
@@ -813,15 +887,18 @@ void draw_working_details() {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.06f, 0.37f, 0.43f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.08f, 0.44f, 0.51f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(5.0f), ui(1.0f)});
ImGui::Button((repo().branch + "##working_branch").c_str());
if (ImGui::Button((repo().branch + "##working_branch").c_str()))
g_request_branch_selector = true;
ImGui::PopStyleVar();
ImGui::PopStyleColor(2);
ImGui::SameLine(ImGui::GetWindowWidth() - ui(27.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.19f, 0.08f, 0.38f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.48f, 0.14f, 0.86f, 1.0f));
if (ImGui::Button(ICON_FA_WAND_MAGIC_SPARKLES "##working_assist", {ui(23.0f), ui(23.0f)}))
g_notice = "Commit assistance is not wired yet";
if (ImGui::Button(ICON_FA_WAND_MAGIC_SPARKLES "##working_assist", {ui(23.0f), ui(23.0f)})) {
const std::string suggestion = "Update " + std::to_string(repo().working_files.size()) + " files";
std::snprintf(g_commit_summary.data(), g_commit_summary.size(), "%s", suggestion.c_str());
}
ImGui::PopStyleColor(2);
ImGui::EndChild();
ImGui::Separator();
@@ -844,7 +921,7 @@ void draw_working_details() {
ImGui::SameLine(std::max(ui(145.0f), ImGui::GetWindowWidth() - ui(119.0f)));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12f, 0.27f, 0.18f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.24f, 0.72f, 0.35f, 1.0f));
if (ImGui::SmallButton("Stage All Changes")) g_notice = "Staging is not wired yet";
if (ImGui::SmallButton("Stage All Changes")) g_git_manager->stageAll(repo(), g_notice);
ImGui::PopStyleColor(2);
ImGui::Separator();
if (unstaged_open) {
@@ -894,8 +971,10 @@ void draw_working_details() {
ImGui::SameLine(0, ui(4.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12f, 0.12f, 0.29f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.35f, 0.16f, 0.62f, 1.0f));
if (ImGui::SmallButton(ICON_FA_WAND_MAGIC_SPARKLES "##summary_assist"))
g_notice = "Summary assistance is not wired yet";
if (ImGui::SmallButton(ICON_FA_WAND_MAGIC_SPARKLES "##summary_assist")) {
const std::string suggestion = "Update " + std::to_string(repo().working_files.size()) + " files";
std::snprintf(g_commit_summary.data(), g_commit_summary.size(), "%s", suggestion.c_str());
}
ImGui::PopStyleColor(2);
ImGui::SetNextItemWidth(-1);
ImGui::InputTextMultiline("##commit_description", g_commit_description.data(), g_commit_description.size(),
@@ -913,15 +992,25 @@ void draw_working_details() {
ImGui::SameLine(std::max(ui(130.0f), ImGui::GetWindowWidth() - ui(181.0f)));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20f, 0.06f, 0.40f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.48f, 0.12f, 0.82f, 1.0f));
if (ImGui::SmallButton(ICON_FA_WAND_MAGIC_SPARKLES " Compose commits with AI"))
g_notice = "Commit composition is not wired yet";
if (ImGui::SmallButton(ICON_FA_WAND_MAGIC_SPARKLES " Compose commits with AI")) {
const std::string suggestion = "Update " + std::to_string(repo().working_files.size()) + " files";
std::snprintf(g_commit_summary.data(), g_commit_summary.size(), "%s", suggestion.c_str());
if (g_commit_description[0] == '\0')
std::snprintf(g_commit_description.data(), g_commit_description.size(),
"Update the current working tree changes.");
}
ImGui::PopStyleColor(2);
ImGui::BeginDisabled(staged == 0 || g_commit_summary[0] == '\0');
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12f, 0.24f, 0.18f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.24f, 0.54f, 0.32f, 1.0f));
if (ImGui::Button(ICON_FA_CIRCLE_DOT " Stage Changes to Commit", {-1, ui(40.0f)}))
g_notice = "Commit creation is not wired yet";
if (ImGui::Button(ICON_FA_CIRCLE_DOT " Stage Changes to Commit", {-1, ui(40.0f)})) {
if (g_git_manager->commit(repo(), g_commit_summary.data(), g_commit_description.data(), amend, g_notice)) {
g_commit_summary.fill('\0');
g_commit_description.fill('\0');
amend = false;
}
}
ImGui::PopStyleColor(2);
ImGui::EndDisabled();
ImGui::EndChild();
@@ -1179,6 +1268,150 @@ void draw_licenses_popup() {
ImGui::EndPopup();
}
void draw_git_action_popups() {
if (g_branch_create_popup) {
g_git_name.fill('\0');
g_branch_create_popup = false;
ImGui::OpenPopup("Create branch");
}
if (ImGui::BeginPopupModal("Create branch", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
static bool checkout_branch = true;
ImGui::TextUnformatted("Branch name");
ImGui::SetNextItemWidth(ui(360.0f));
ImGui::InputText("##branch_name", g_git_name.data(), g_git_name.size());
ImGui::TextDisabled("Start point: %s", g_git_target.empty() ? "HEAD" : g_git_target.c_str());
ImGui::Checkbox("Check out new branch", &checkout_branch);
const bool valid = g_git_name[0] != '\0';
ImGui::BeginDisabled(!valid);
if (ImGui::Button("Create", {ui(90.0f), 0}) &&
g_git_manager->createBranch(repo(), g_git_name.data(), g_git_target,
checkout_branch, g_notice)) {
g_git_target.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) {
g_git_target.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (g_tag_create_popup) {
g_git_name.fill('\0');
g_tag_create_popup = false;
ImGui::OpenPopup("Create tag");
}
if (ImGui::BeginPopupModal("Create tag", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted("Tag name");
ImGui::SetNextItemWidth(ui(360.0f));
ImGui::InputText("##tag_name", g_git_name.data(), g_git_name.size());
ImGui::TextDisabled("Target: %s", g_git_target.empty() ? "HEAD" : g_git_target.c_str());
ImGui::BeginDisabled(g_git_name[0] == '\0');
if (ImGui::Button("Create", {ui(90.0f), 0}) &&
g_git_manager->createTag(repo(), g_git_name.data(), g_git_target, g_notice)) {
g_git_target.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) {
g_git_target.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (g_remote_add_popup) {
g_git_name.fill('\0');
g_git_value.fill('\0');
std::snprintf(g_git_name.data(), g_git_name.size(), "%s",
std::find(repo().remotes.begin(), repo().remotes.end(), "origin") == repo().remotes.end()
? "origin" : "upstream");
g_remote_add_popup = false;
ImGui::OpenPopup("Add remote");
}
if (ImGui::BeginPopupModal("Add remote", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted("Remote name");
ImGui::SetNextItemWidth(ui(420.0f));
ImGui::InputText("##remote_name", g_git_name.data(), g_git_name.size());
ImGui::TextUnformatted("Remote URL");
ImGui::SetNextItemWidth(ui(420.0f));
ImGui::InputText("##remote_url", g_git_value.data(), g_git_value.size());
ImGui::BeginDisabled(g_git_name[0] == '\0' || g_git_value[0] == '\0');
if (ImGui::Button("Add remote", {ui(110.0f), 0}) &&
g_git_manager->addRemote(repo(), g_git_name.data(), g_git_value.data(), g_notice))
ImGui::CloseCurrentPopup();
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
if (g_worktree_add_popup) {
g_git_path.fill('\0');
g_git_value.fill('\0');
g_worktree_add_popup = false;
ImGui::OpenPopup("Add worktree");
}
if (ImGui::BeginPopupModal("Add worktree", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted("Worktree folder");
ImGui::SetNextItemWidth(ui(460.0f));
ImGui::InputText("##worktree_path", g_git_path.data(), g_git_path.size());
ImGui::TextUnformatted("Branch or commit (optional)");
ImGui::SetNextItemWidth(ui(460.0f));
ImGui::InputText("##worktree_branch", g_git_value.data(), g_git_value.size());
ImGui::BeginDisabled(g_git_path[0] == '\0');
if (ImGui::Button("Add worktree", {ui(120.0f), 0}) &&
g_git_manager->addWorktree(repo(), g_git_path.data(), g_git_value.data(), g_notice))
ImGui::CloseCurrentPopup();
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
if (g_submodule_add_popup) {
g_git_value.fill('\0');
g_git_path.fill('\0');
g_submodule_add_popup = false;
ImGui::OpenPopup("Add submodule");
}
if (ImGui::BeginPopupModal("Add submodule", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted("Repository URL");
ImGui::SetNextItemWidth(ui(460.0f));
ImGui::InputText("##submodule_url", g_git_value.data(), g_git_value.size());
ImGui::TextUnformatted("Local path (optional)");
ImGui::SetNextItemWidth(ui(460.0f));
ImGui::InputText("##submodule_path", g_git_path.data(), g_git_path.size());
ImGui::BeginDisabled(g_git_value[0] == '\0');
if (ImGui::Button("Add submodule", {ui(125.0f), 0}) &&
g_git_manager->addSubmodule(repo(), g_git_value.data(), g_git_path.data(), g_notice))
ImGui::CloseCurrentPopup();
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
if (g_discard_all_popup) {
g_discard_all_popup = false;
ImGui::OpenPopup("Discard all changes?");
}
if (ImGui::BeginPopupModal("Discard all changes?", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextWrapped("This permanently discards tracked changes and removes untracked files and folders.");
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.42f, 0.12f, 0.13f, 1.0f));
if (ImGui::Button("Discard all", {ui(110.0f), 0})) {
if (g_git_manager->discardAll(repo(), g_notice)) ImGui::CloseCurrentPopup();
}
ImGui::PopStyleColor();
ImGui::SameLine();
if (ImGui::Button("Cancel", {ui(90.0f), 0})) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
void draw_popups() {
if (g_init_popup) { ImGui::OpenPopup("Create repository"); g_init_popup = false; }
if (ImGui::BeginPopupModal("Create repository", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
@@ -1195,6 +1428,7 @@ void draw_popups() {
}
draw_about_popup();
draw_licenses_popup();
draw_git_action_popups();
}
void draw_footer() {
@@ -1272,7 +1506,10 @@ void draw_app() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit")) { ImGui::MenuItem("Preferences", nullptr, false, false); ImGui::EndMenu(); }
if (ImGui::BeginMenu("View")) { ImGui::MenuItem("Refresh", "F5"); ImGui::EndMenu(); }
if (ImGui::BeginMenu("View")) {
if (ImGui::MenuItem("Refresh", "F5")) g_git_manager->reload(repo(), g_notice);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help")) {
if (ImGui::MenuItem("About Gitree")) g_about_popup = true;
ImGui::EndMenu();
@@ -1377,12 +1614,26 @@ void draw_app() {
const float repository_width = std::clamp(
ImGui::CalcTextSize(repo().name.c_str()).x / g_ui_scale + 42.0f, 120.0f, 220.0f);
if (toolbar_selector("repository", "repository", repo().name, repository_width))
g_notice = "Use repository tabs to switch repositories";
ImGui::OpenPopup("repository_selector");
if (ImGui::BeginPopup("repository_selector")) {
for (size_t index = 0; index < g_tabs.size(); ++index) {
if (ImGui::MenuItem(g_tabs[index]->name.c_str(), nullptr, index == g_active_tab)) {
g_active_tab = index;
g_tab_to_select = g_tabs[index].get();
persist_repository_session();
}
}
ImGui::EndPopup();
}
ImGui::SameLine(0, ui(3));
ImGui::SetCursorPosY(ui(19));
ImGui::TextDisabled(ICON_FA_ANGLE_RIGHT);
ImGui::SameLine(0, ui(3));
ImGui::SetCursorPosY(ui(1));
if (g_request_branch_selector) {
ImGui::OpenPopup("branch_selector");
g_request_branch_selector = false;
}
if (toolbar_selector("branch", "branch", repo().branch, 150.0f)) ImGui::OpenPopup("branch_selector");
const float selectors_right = ImGui::GetItemRectMax().x - ImGui::GetWindowPos().x;
ImGui::SetNextWindowSize({ui(320.0f), ui(370.0f)}, ImGuiCond_Appearing);
@@ -1413,26 +1664,31 @@ void draw_app() {
ImGui::SetCursorPos({std::max(selectors_right + ui(10.0f), centered_x), ui(1.0f)});
if (toolbar_action("undo", "Undo", ICON_FA_ROTATE_LEFT, "Undo last Git action", true, false, 54))
g_notice = "Undo is not wired yet";
g_git_manager->undoCommit(repo(), g_notice);
ImGui::SameLine(0, action_spacing);
toolbar_action("redo", "Redo", ICON_FA_ROTATE_RIGHT, "Redo last Git action", false, false, 54);
if (toolbar_action("redo", "Redo", ICON_FA_ROTATE_RIGHT, "Redo last Git action", true, false, 54))
g_git_manager->redoCommit(repo(), g_notice);
ImGui::SameLine(0, action_spacing);
bool pull_dropdown_clicked = false;
if (toolbar_action("pull", "Pull", ICON_FA_DOWNLOAD, pull_mode_name(g_user_data->pullMode()),
true, true, 58, &pull_dropdown_clicked))
g_notice = std::string(pull_mode_name(g_user_data->pullMode())) + " is not wired yet";
g_git_manager->pull(repo(), g_user_data->pullMode(), g_notice);
if (pull_dropdown_clicked) ImGui::OpenPopup("pull_options");
draw_pull_options();
ImGui::SameLine(0, action_spacing);
if (toolbar_action("push", "Push", ICON_FA_UPLOAD, "Push to remote", true, false, 58))
g_notice = "Push is not wired yet";
g_git_manager->push(repo(), g_notice);
ImGui::SameLine(0, action_spacing);
if (toolbar_action("branch_action", "Branch", ICON_FA_CODE_BRANCH, "Create branch", true, false, 64))
g_notice = "Branch creation is not wired yet";
if (toolbar_action("branch_action", "Branch", ICON_FA_CODE_BRANCH, "Create branch", true, false, 64)) {
g_git_target.clear();
g_branch_create_popup = true;
}
ImGui::SameLine(0, action_spacing);
toolbar_action("stash", "Stash", ICON_FA_BOX_ARCHIVE, "Stash changes", false, false, 58);
if (toolbar_action("stash", "Stash", ICON_FA_BOX_ARCHIVE, "Stash changes", true, false, 58))
g_git_manager->stash(repo(), g_notice);
ImGui::SameLine(0, action_spacing);
toolbar_action("pop", "Pop", ICON_FA_BOX_OPEN, "Pop latest stash", false, false, 54);
if (toolbar_action("pop", "Pop", ICON_FA_BOX_OPEN, "Pop latest stash", true, false, 54))
g_git_manager->popStash(repo(), g_notice);
ImGui::EndChild();
ImGui::PopStyleColor();