|
|
|
|
@@ -37,6 +37,11 @@ std::string g_notice;
|
|
|
|
|
bool g_init_popup = false;
|
|
|
|
|
bool g_about_popup = false;
|
|
|
|
|
bool g_licenses_popup = false;
|
|
|
|
|
enum class FileViewMode { path, tree };
|
|
|
|
|
FileViewMode g_file_view_mode = FileViewMode::path;
|
|
|
|
|
bool g_view_all_files = false;
|
|
|
|
|
std::array<char, 128> g_commit_summary{};
|
|
|
|
|
std::array<char, 1024> g_commit_description{};
|
|
|
|
|
float g_ui_scale = 1.0f;
|
|
|
|
|
float g_sidebar_width = 230.0f;
|
|
|
|
|
WindowManager* g_window_manager = nullptr;
|
|
|
|
|
@@ -354,6 +359,24 @@ void draw_commit_table() {
|
|
|
|
|
ImGui::TableSetupColumn("COMMIT MESSAGE", ImGuiTableColumnFlags_WidthStretch);
|
|
|
|
|
ImGui::TableSetupColumn("COMMIT DATE / TIME", ImGuiTableColumnFlags_WidthFixed, ui(180.0f));
|
|
|
|
|
ImGui::TableHeadersRow();
|
|
|
|
|
if (!repo().working_files.empty()) {
|
|
|
|
|
ImGui::TableNextRow(0, ui(31.0f));
|
|
|
|
|
ImGui::TableSetColumnIndex(0);
|
|
|
|
|
if (ImGui::Selectable("##working_tree", repo().selected_commit == -1,
|
|
|
|
|
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap))
|
|
|
|
|
repo().selected_commit = -1;
|
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
|
|
|
|
const ImVec2 position = ImGui::GetCursorScreenPos();
|
|
|
|
|
ImDrawList* draw = ImGui::GetWindowDrawList();
|
|
|
|
|
const ImVec2 center{position.x + ui(15), position.y + ui(15.5f)};
|
|
|
|
|
draw->AddCircle(center, ui(7), IM_COL32(90, 180, 195, 210), 12, ui(1.5f));
|
|
|
|
|
ImGui::Dummy({0, ui(31)});
|
|
|
|
|
ImGui::TableSetColumnIndex(2);
|
|
|
|
|
ImGui::TextDisabled("// WIP");
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::TextColored(ImVec4(0.94f, 0.66f, 0.25f, 1), ICON_FA_PEN " %d",
|
|
|
|
|
static_cast<int>(repo().working_files.size()));
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < static_cast<int>(repo().commits.size()); ++i) {
|
|
|
|
|
const auto& commit = repo().commits[i];
|
|
|
|
|
if (g_filter[0]) {
|
|
|
|
|
@@ -396,26 +419,138 @@ void draw_commit_table() {
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_details() {
|
|
|
|
|
ImGui::BeginChild("details", {ui(360.0f), -ui(28.0f)}, ImGuiChildFlags_Borders);
|
|
|
|
|
if (repo().commits.empty()) {
|
|
|
|
|
ImGui::TextDisabled("Select a repository with commits");
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
const char* change_icon(FileChangeKind kind) {
|
|
|
|
|
if (kind == FileChangeKind::added) return ICON_FA_PLUS;
|
|
|
|
|
if (kind == FileChangeKind::deleted) return ICON_FA_MINUS;
|
|
|
|
|
if (kind == FileChangeKind::renamed) return ICON_FA_ARROW_RIGHT_ARROW_LEFT;
|
|
|
|
|
return ICON_FA_PEN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImVec4 change_color(FileChangeKind kind) {
|
|
|
|
|
if (kind == FileChangeKind::added) return {0.31f, 0.82f, 0.43f, 1};
|
|
|
|
|
if (kind == FileChangeKind::deleted) return {0.91f, 0.35f, 0.35f, 1};
|
|
|
|
|
if (kind == FileChangeKind::renamed) return {0.38f, 0.67f, 0.92f, 1};
|
|
|
|
|
return {0.94f, 0.66f, 0.25f, 1};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_file_row(const std::string& path, FileChangeKind kind, int id) {
|
|
|
|
|
ImGui::PushID(id);
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, change_color(kind));
|
|
|
|
|
ImGui::Selectable((std::string(change_icon(kind)) + " " + path).c_str());
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
if (ImGui::BeginPopupContextItem()) {
|
|
|
|
|
if (ImGui::MenuItem(ICON_FA_COPY " Copy path")) ImGui::SetClipboardText(path.c_str());
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_file_toolbar() {
|
|
|
|
|
ImGui::TextDisabled(ICON_FA_ARROW_DOWN_A_Z);
|
|
|
|
|
const float controls_width = ui(225.0f);
|
|
|
|
|
ImGui::SameLine(std::max(ImGui::GetCursorPosX() + ui(8), ImGui::GetWindowWidth() - controls_width));
|
|
|
|
|
const bool path_selected = g_file_view_mode == FileViewMode::path;
|
|
|
|
|
if (path_selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.28f, 0.46f, 1));
|
|
|
|
|
if (ImGui::Button(ICON_FA_BARS " Path")) g_file_view_mode = FileViewMode::path;
|
|
|
|
|
if (path_selected) ImGui::PopStyleColor();
|
|
|
|
|
ImGui::SameLine(0, 0);
|
|
|
|
|
const bool tree_selected = g_file_view_mode == FileViewMode::tree;
|
|
|
|
|
if (tree_selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.28f, 0.46f, 1));
|
|
|
|
|
if (ImGui::Button(ICON_FA_FOLDER_TREE " Tree")) g_file_view_mode = FileViewMode::tree;
|
|
|
|
|
if (tree_selected) ImGui::PopStyleColor();
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::Checkbox("View all files", &g_view_all_files);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename File>
|
|
|
|
|
void draw_files(const std::vector<File>& files, bool staged_filter = false, bool filter_by_stage = false) {
|
|
|
|
|
if (g_file_view_mode == FileViewMode::path) {
|
|
|
|
|
int id = 0;
|
|
|
|
|
for (const auto& file : files) {
|
|
|
|
|
if constexpr (requires { file.staged; }) if (filter_by_stage && file.staged != staged_filter) continue;
|
|
|
|
|
draw_file_row(file.path, file.kind, id++);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::map<std::string, std::vector<const File*>> groups;
|
|
|
|
|
for (const auto& file : files) {
|
|
|
|
|
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);
|
|
|
|
|
groups[group].push_back(&file);
|
|
|
|
|
}
|
|
|
|
|
int id = 0;
|
|
|
|
|
for (const auto& [group, entries] : groups) {
|
|
|
|
|
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++);
|
|
|
|
|
}
|
|
|
|
|
ImGui::TreePop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_working_details() {
|
|
|
|
|
int staged = 0;
|
|
|
|
|
for (const auto& file : repo().working_files) if (file.staged) ++staged;
|
|
|
|
|
const int unstaged = static_cast<int>(repo().working_files.size()) - staged;
|
|
|
|
|
ImGui::SetCursorPosX(std::max(ui(8), (ImGui::GetWindowWidth() - ui(210)) * 0.5f));
|
|
|
|
|
ImGui::Text("%d file changes on", static_cast<int>(repo().working_files.size()));
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::TextColored(ImVec4(0.25f, 0.80f, 0.86f, 1), "%s", repo().branch.c_str());
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
draw_file_toolbar();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::BeginChild("working_files", {-1, ui(260)}, false);
|
|
|
|
|
if (ImGui::TreeNodeEx((std::string("Unstaged Files (") + std::to_string(unstaged) + ")").c_str(),
|
|
|
|
|
ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
|
|
|
|
ImGui::SameLine(std::max(ui(150), ImGui::GetWindowWidth() - ui(135)));
|
|
|
|
|
if (ImGui::SmallButton("Stage All Changes")) g_notice = "Staging is not wired yet";
|
|
|
|
|
draw_files(repo().working_files, false, true);
|
|
|
|
|
ImGui::TreePop();
|
|
|
|
|
}
|
|
|
|
|
if (ImGui::TreeNodeEx((std::string("Staged Files (") + std::to_string(staged) + ")").c_str(),
|
|
|
|
|
ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
|
|
|
|
draw_files(repo().working_files, true, true);
|
|
|
|
|
ImGui::TreePop();
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::TextUnformatted(ICON_FA_CIRCLE_DOT " Commit");
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
static bool amend = false;
|
|
|
|
|
ImGui::Checkbox("Amend previous commit", &amend);
|
|
|
|
|
ImGui::InputTextWithHint("##commit_summary", "Commit summary", g_commit_summary.data(), g_commit_summary.size());
|
|
|
|
|
ImGui::InputTextMultiline("##commit_description", g_commit_description.data(), g_commit_description.size(),
|
|
|
|
|
{-1, ui(115)});
|
|
|
|
|
ImGui::BeginDisabled(staged == 0 || g_commit_summary[0] == '\0');
|
|
|
|
|
if (ImGui::Button("Stage Changes to Commit", {-1, ui(40)})) g_notice = "Commit creation is not wired yet";
|
|
|
|
|
ImGui::EndDisabled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_commit_details() {
|
|
|
|
|
const int selected = std::clamp(repo().selected_commit, 0, static_cast<int>(repo().commits.size() - 1));
|
|
|
|
|
g_git_manager->loadCommitChanges(repo(), selected, g_notice);
|
|
|
|
|
const auto& commit = repo().commits[selected];
|
|
|
|
|
ImGui::TextDisabled("COMMIT");
|
|
|
|
|
ImGui::TextDisabled("commit:");
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::Text("%s", commit.short_id.c_str());
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::Dummy({0, 6});
|
|
|
|
|
ImGui::BeginChild("message_card", {-1, ui(125)}, ImGuiChildFlags_Borders);
|
|
|
|
|
ImGui::PushTextWrapPos();
|
|
|
|
|
ImGui::Text("%s", commit.summary.c_str());
|
|
|
|
|
ImGui::PopTextWrapPos();
|
|
|
|
|
ImGui::Dummy({0, 26});
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ui(80)) * 0.5f);
|
|
|
|
|
const ImVec2 handle = ImGui::GetCursorScreenPos();
|
|
|
|
|
ImGui::GetWindowDrawList()->AddLine(
|
|
|
|
|
handle,
|
|
|
|
|
{handle.x + ui(80), handle.y},
|
|
|
|
|
IM_COL32(72, 80, 94, 255),
|
|
|
|
|
ui(3));
|
|
|
|
|
ImGui::Dummy({ui(80), ui(6)});
|
|
|
|
|
|
|
|
|
|
const unsigned int author_texture = g_avatar_cache ? g_avatar_cache->textureFor(commit.email) : 0;
|
|
|
|
|
if (author_texture) {
|
|
|
|
|
ImGui::Image(ImTextureRef(static_cast<ImTextureID>(author_texture)), {ui(40), ui(40)});
|
|
|
|
|
@@ -423,48 +558,37 @@ void draw_details() {
|
|
|
|
|
}
|
|
|
|
|
ImGui::BeginGroup();
|
|
|
|
|
ImGui::TextColored(ImVec4(0.25f, 0.80f, 0.86f, 1), "%s", commit.author.c_str());
|
|
|
|
|
ImGui::TextDisabled("%s", commit.email.c_str());
|
|
|
|
|
ImGui::TextDisabled("authored %s", commit.date.c_str());
|
|
|
|
|
ImGui::EndGroup();
|
|
|
|
|
ImGui::Dummy({0, 24});
|
|
|
|
|
ImGui::TextColored(ImVec4(0.95f, 0.68f, 0.22f, 1), "%d parent%s", commit.parents, commit.parents == 1 ? "" : "s");
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
int added = 0;
|
|
|
|
|
int modified = 0;
|
|
|
|
|
int deleted = 0;
|
|
|
|
|
if (!commit.parent_ids.empty()) {
|
|
|
|
|
char parent[GIT_OID_SHA1_HEXSIZE + 1]{};
|
|
|
|
|
git_oid_tostr(parent, sizeof(parent), &commit.parent_ids.front());
|
|
|
|
|
ImGui::SameLine(std::max(ui(180), ImGui::GetWindowWidth() - ui(95)));
|
|
|
|
|
ImGui::TextDisabled("parent: %.7s", parent);
|
|
|
|
|
}
|
|
|
|
|
ImGui::Dummy({0, ui(14)});
|
|
|
|
|
int added = 0, modified = 0, deleted = 0;
|
|
|
|
|
for (const auto& file : commit.changed_files) {
|
|
|
|
|
if (file.kind == FileChangeKind::added) ++added;
|
|
|
|
|
else if (file.kind == FileChangeKind::deleted) ++deleted;
|
|
|
|
|
else ++modified;
|
|
|
|
|
}
|
|
|
|
|
if (modified) ImGui::TextColored(ImVec4(0.94f, 0.66f, 0.25f, 1), ICON_FA_PEN " %d modified", modified);
|
|
|
|
|
if (added) {
|
|
|
|
|
if (modified) ImGui::SameLine();
|
|
|
|
|
ImGui::TextColored(ImVec4(0.31f, 0.82f, 0.43f, 1), ICON_FA_PLUS " %d added", added);
|
|
|
|
|
}
|
|
|
|
|
if (deleted) {
|
|
|
|
|
if (modified || added) ImGui::SameLine();
|
|
|
|
|
ImGui::TextColored(ImVec4(0.91f, 0.35f, 0.35f, 1), ICON_FA_MINUS " %d deleted", deleted);
|
|
|
|
|
}
|
|
|
|
|
if (modified) ImGui::TextColored(change_color(FileChangeKind::modified), ICON_FA_PEN " %d modified", modified);
|
|
|
|
|
if (added) { if (modified) ImGui::SameLine(); ImGui::TextColored(change_color(FileChangeKind::added), ICON_FA_PLUS " %d added", added); }
|
|
|
|
|
if (deleted) { if (modified || added) ImGui::SameLine(); ImGui::TextColored(change_color(FileChangeKind::deleted), ICON_FA_MINUS " %d deleted", deleted); }
|
|
|
|
|
draw_file_toolbar();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
if (g_file_view_mode == FileViewMode::tree) ImGui::TextUnformatted("Collapse All");
|
|
|
|
|
ImGui::BeginChild("changed_files", {-1, -1});
|
|
|
|
|
for (int index = 0; index < static_cast<int>(commit.changed_files.size()); ++index) {
|
|
|
|
|
const auto& file = commit.changed_files[index];
|
|
|
|
|
const char* icon = file.kind == FileChangeKind::added ? ICON_FA_PLUS
|
|
|
|
|
: file.kind == FileChangeKind::deleted ? ICON_FA_MINUS : ICON_FA_PEN;
|
|
|
|
|
const ImVec4 color = file.kind == FileChangeKind::added ? ImVec4(0.31f, 0.82f, 0.43f, 1)
|
|
|
|
|
: file.kind == FileChangeKind::deleted ? ImVec4(0.91f, 0.35f, 0.35f, 1)
|
|
|
|
|
: ImVec4(0.94f, 0.66f, 0.25f, 1);
|
|
|
|
|
ImGui::PushID(index);
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
|
|
|
|
ImGui::Selectable((std::string(icon) + " " + file.path).c_str());
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
if (ImGui::BeginPopupContextItem()) {
|
|
|
|
|
if (ImGui::MenuItem(ICON_FA_COPY " Copy path")) ImGui::SetClipboardText(file.path.c_str());
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
}
|
|
|
|
|
draw_files(commit.changed_files);
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_details() {
|
|
|
|
|
ImGui::BeginChild("details", {ui(368.0f), -ui(28.0f)}, ImGuiChildFlags_Borders);
|
|
|
|
|
if (repo().selected_commit == -1) draw_working_details();
|
|
|
|
|
else if (!repo().commits.empty()) draw_commit_details();
|
|
|
|
|
else ImGui::TextDisabled("Select a repository with commits");
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|