feat(sidebar): persist resizable section layout
This commit is contained in:
@@ -32,9 +32,16 @@ void UserData::load() {
|
|||||||
while (settings >> key) {
|
while (settings >> key) {
|
||||||
if (key == "sidebar_width") settings >> sidebar_width_;
|
if (key == "sidebar_width") settings >> sidebar_width_;
|
||||||
else if (key == "pull_mode") settings >> pull_mode_;
|
else if (key == "pull_mode") settings >> pull_mode_;
|
||||||
|
else if (key.rfind("sidebar_section_", 0) == 0) {
|
||||||
|
const size_t index = static_cast<size_t>(std::stoul(key.substr(16)));
|
||||||
|
float height = 0.0f;
|
||||||
|
settings >> height;
|
||||||
|
if (index < sidebar_section_heights_.size()) sidebar_section_heights_[index] = height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sidebar_width_ = std::clamp(sidebar_width_, 180.0f, 520.0f);
|
sidebar_width_ = std::clamp(sidebar_width_, 180.0f, 520.0f);
|
||||||
pull_mode_ = std::clamp(pull_mode_, 0, 3);
|
pull_mode_ = std::clamp(pull_mode_, 0, 3);
|
||||||
|
for (float& height : sidebar_section_heights_) height = std::clamp(height, 42.0f, 500.0f);
|
||||||
|
|
||||||
std::ifstream history(directory_ / "history.txt");
|
std::ifstream history(directory_ / "history.txt");
|
||||||
std::string path;
|
std::string path;
|
||||||
@@ -57,6 +64,8 @@ void UserData::save() const {
|
|||||||
std::ofstream settings(directory_ / "settings.ini", std::ios::trunc);
|
std::ofstream settings(directory_ / "settings.ini", std::ios::trunc);
|
||||||
settings << "sidebar_width " << sidebar_width_ << '\n';
|
settings << "sidebar_width " << sidebar_width_ << '\n';
|
||||||
settings << "pull_mode " << pull_mode_ << '\n';
|
settings << "pull_mode " << pull_mode_ << '\n';
|
||||||
|
for (size_t index = 0; index < sidebar_section_heights_.size(); ++index)
|
||||||
|
settings << "sidebar_section_" << index << ' ' << sidebar_section_heights_[index] << '\n';
|
||||||
|
|
||||||
std::ofstream history(directory_ / "history.txt", std::ios::trunc);
|
std::ofstream history(directory_ / "history.txt", std::ios::trunc);
|
||||||
for (const auto& path : recently_closed_) history << std::quoted(path) << '\n';
|
for (const auto& path : recently_closed_) history << std::quoted(path) << '\n';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -13,9 +14,11 @@ public:
|
|||||||
[[nodiscard]] const std::string& imguiIniPath() const { return imgui_ini_path_; }
|
[[nodiscard]] const std::string& imguiIniPath() const { return imgui_ini_path_; }
|
||||||
[[nodiscard]] const std::vector<std::string>& recentlyClosed() const { return recently_closed_; }
|
[[nodiscard]] const std::vector<std::string>& recentlyClosed() const { return recently_closed_; }
|
||||||
[[nodiscard]] float sidebarWidth() const { return sidebar_width_; }
|
[[nodiscard]] float sidebarWidth() const { return sidebar_width_; }
|
||||||
|
[[nodiscard]] float sidebarSectionHeight(size_t index) const { return sidebar_section_heights_.at(index); }
|
||||||
[[nodiscard]] int pullMode() const { return pull_mode_; }
|
[[nodiscard]] int pullMode() const { return pull_mode_; }
|
||||||
|
|
||||||
void setSidebarWidth(float width) { sidebar_width_ = width; }
|
void setSidebarWidth(float width) { sidebar_width_ = width; }
|
||||||
|
void setSidebarSectionHeight(size_t index, float height) { sidebar_section_heights_.at(index) = height; }
|
||||||
void setPullMode(int mode) { pull_mode_ = mode; save(); }
|
void setPullMode(int mode) { pull_mode_ = mode; save(); }
|
||||||
void addRecentlyClosed(const std::string& path);
|
void addRecentlyClosed(const std::string& path);
|
||||||
void save() const;
|
void save() const;
|
||||||
@@ -27,5 +30,6 @@ private:
|
|||||||
std::string imgui_ini_path_;
|
std::string imgui_ini_path_;
|
||||||
std::vector<std::string> recently_closed_;
|
std::vector<std::string> recently_closed_;
|
||||||
float sidebar_width_ = 230.0f;
|
float sidebar_width_ = 230.0f;
|
||||||
|
std::array<float, 4> sidebar_section_heights_ = {110.0f, 220.0f, 90.0f, 150.0f};
|
||||||
int pull_mode_ = 1;
|
int pull_mode_ = 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -173,25 +173,54 @@ bool sidebar_collapse_row(const char* id, const std::string& label, bool default
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool sidebar_section_header(const char* label, int count, const char* add_tooltip, const char* add_notice) {
|
bool sidebar_section_header(const char* label, int count, const char* add_tooltip, const char* add_notice) {
|
||||||
|
const ImVec2 header_min = ImGui::GetCursorScreenPos();
|
||||||
|
const float header_width = ImGui::GetContentRegionAvail().x;
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.60f, 0.67f, 0.76f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.60f, 0.67f, 0.76f, 1.0f));
|
||||||
const bool open = sidebar_collapse_row(label, label, true, ui(62.0f));
|
const bool open = sidebar_collapse_row(label, label, true, ui(62.0f));
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
ImGui::SameLine(0, ui(5.0f));
|
ImGui::SameLine(0, ui(5.0f));
|
||||||
ImGui::TextDisabled("%d", count);
|
ImGui::TextDisabled("%d", count);
|
||||||
ImGui::SameLine(0, ui(7.0f));
|
const ImVec2 mouse = ImGui::GetIO().MousePos;
|
||||||
ImGui::PushID(label);
|
const bool header_hovered = ImGui::IsWindowHovered() && mouse.x >= header_min.x &&
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.43f, 0.90f, 0.51f, 1.0f));
|
mouse.x <= header_min.x + header_width && mouse.y >= header_min.y &&
|
||||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.25f, 0.72f, 0.35f, 1.0f));
|
mouse.y <= ImGui::GetItemRectMax().y;
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(5.0f), ui(1.0f)});
|
if (header_hovered) {
|
||||||
if (ImGui::SmallButton(ICON_FA_PLUS "##add")) g_notice = add_notice;
|
ImGui::SameLine(0, ui(7.0f));
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) ImGui::SetTooltip("%s", add_tooltip);
|
ImGui::PushID(label);
|
||||||
ImGui::PopStyleVar();
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.43f, 0.90f, 0.51f, 1.0f));
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.25f, 0.72f, 0.35f, 1.0f));
|
||||||
ImGui::PopID();
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(5.0f), ui(1.0f)});
|
||||||
|
if (ImGui::SmallButton(ICON_FA_PLUS "##add")) g_notice = add_notice;
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) ImGui::SetTooltip("%s", add_tooltip);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
return open;
|
return open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sidebar_section_splitter(const char* id, size_t section_index) {
|
||||||
|
ImGui::PushID(id);
|
||||||
|
ImGui::InvisibleButton("##vertical_resize", {-1, ui(5.0f)});
|
||||||
|
const bool active = ImGui::IsItemActive();
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
if (active || hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
|
||||||
|
if (active) {
|
||||||
|
const float height = std::clamp(g_user_data->sidebarSectionHeight(section_index) +
|
||||||
|
ImGui::GetIO().MouseDelta.y / g_ui_scale, 42.0f, 500.0f);
|
||||||
|
g_user_data->setSidebarSectionHeight(section_index, height);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemDeactivated()) g_user_data->save();
|
||||||
|
const ImVec2 minimum = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 maximum = ImGui::GetItemRectMax();
|
||||||
|
ImGui::GetWindowDrawList()->AddLine(
|
||||||
|
{minimum.x, (minimum.y + maximum.y) * 0.5f},
|
||||||
|
{maximum.x, (minimum.y + maximum.y) * 0.5f},
|
||||||
|
active || hovered ? IM_COL32(36, 178, 205, 255) : IM_COL32(62, 72, 88, 255));
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
enum class SidebarItemKind { worktree, submodule };
|
enum class SidebarItemKind { worktree, submodule };
|
||||||
|
|
||||||
void sidebar_item_context(const std::string& item, SidebarItemKind kind) {
|
void sidebar_item_context(const std::string& item, SidebarItemKind kind) {
|
||||||
@@ -211,16 +240,19 @@ void sidebar_item_context(const std::string& item, SidebarItemKind kind) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void section(const char* label, const std::vector<std::string>& items, const char* item_icon,
|
void section(const char* label, const std::vector<std::string>& items, const char* item_icon,
|
||||||
const char* add_tooltip, const char* add_notice, SidebarItemKind kind) {
|
const char* add_tooltip, const char* add_notice, SidebarItemKind kind, size_t section_index) {
|
||||||
const bool open = sidebar_section_header(label, static_cast<int>(items.size()), add_tooltip, add_notice);
|
const bool open = sidebar_section_header(label, static_cast<int>(items.size()), add_tooltip, add_notice);
|
||||||
if (open) {
|
if (open) {
|
||||||
|
ImGui::BeginChild((std::string(label) + "##body").c_str(),
|
||||||
|
{-1, ui(g_user_data->sidebarSectionHeight(section_index))});
|
||||||
for (const auto& item : items) {
|
for (const auto& item : items) {
|
||||||
ImGui::Selectable((std::string(" ") + item_icon + " " + item).c_str());
|
ImGui::Selectable((std::string(" ") + item_icon + " " + item).c_str());
|
||||||
sidebar_item_context(item, kind);
|
sidebar_item_context(item, kind);
|
||||||
}
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
sidebar_section_splitter(label, section_index);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Dummy({0, ui(2.0f)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BranchNode {
|
struct BranchNode {
|
||||||
@@ -277,9 +309,12 @@ void draw_branch_nodes(const BranchNode& parent, const char* branch_icon, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void branch_section(const char* label, const std::vector<std::string>& branches, const char* branch_icon,
|
void branch_section(const char* label, const std::vector<std::string>& branches, const char* branch_icon,
|
||||||
const char* group_icon, const char* add_tooltip, const char* add_notice, bool remote) {
|
const char* group_icon, const char* add_tooltip, const char* add_notice, bool remote,
|
||||||
|
size_t section_index) {
|
||||||
const bool open = sidebar_section_header(label, static_cast<int>(branches.size()), add_tooltip, add_notice);
|
const bool open = sidebar_section_header(label, static_cast<int>(branches.size()), add_tooltip, add_notice);
|
||||||
if (open) {
|
if (open) {
|
||||||
|
ImGui::BeginChild((std::string(label) + "##body").c_str(),
|
||||||
|
{-1, ui(g_user_data->sidebarSectionHeight(section_index))});
|
||||||
BranchNode root;
|
BranchNode root;
|
||||||
for (const auto& branch : branches) {
|
for (const auto& branch : branches) {
|
||||||
if (g_filter[0] && branch.find(g_filter.data()) == std::string::npos) continue;
|
if (g_filter[0] && branch.find(g_filter.data()) == std::string::npos) continue;
|
||||||
@@ -288,9 +323,10 @@ void branch_section(const char* label, const std::vector<std::string>& branches,
|
|||||||
ImGui::Indent(ui(17.0f));
|
ImGui::Indent(ui(17.0f));
|
||||||
draw_branch_nodes(root, branch_icon, group_icon, remote, label);
|
draw_branch_nodes(root, branch_icon, group_icon, remote, label);
|
||||||
ImGui::Unindent(ui(17.0f));
|
ImGui::Unindent(ui(17.0f));
|
||||||
|
ImGui::EndChild();
|
||||||
|
sidebar_section_splitter(label, section_index);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Dummy({0, ui(2.0f)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_sidebar(float width) {
|
void draw_sidebar(float width) {
|
||||||
@@ -303,13 +339,13 @@ void draw_sidebar(float width) {
|
|||||||
ImGui::InputTextWithHint("##filter", ICON_FA_MAGNIFYING_GLASS " Search branches...", g_filter.data(), g_filter.size());
|
ImGui::InputTextWithHint("##filter", ICON_FA_MAGNIFYING_GLASS " Search branches...", g_filter.data(), g_filter.size());
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
branch_section(ICON_FA_HOUSE " LOCAL", repo().local_branches, ICON_FA_CODE_BRANCH, ICON_FA_FOLDER,
|
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);
|
"Create local branch", "Branch creation is not wired yet", false, 0);
|
||||||
branch_section(ICON_FA_CLOUD " REMOTE", repo().remote_branches, ICON_FA_CODE_BRANCH, ICON_FA_GLOBE,
|
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);
|
"Add remote", "Remote creation is not wired yet", true, 1);
|
||||||
section(ICON_FA_DIAGRAM_PROJECT " WORKTREES", repo().worktrees, ICON_FA_COMPUTER,
|
section(ICON_FA_DIAGRAM_PROJECT " WORKTREES", repo().worktrees, ICON_FA_COMPUTER,
|
||||||
"Add worktree", "Worktree creation is not wired yet", SidebarItemKind::worktree);
|
"Add worktree", "Worktree creation is not wired yet", SidebarItemKind::worktree, 2);
|
||||||
section(ICON_FA_CUBES " SUBMODULES", repo().submodules, ICON_FA_CUBES,
|
section(ICON_FA_CUBES " SUBMODULES", repo().submodules, ICON_FA_CUBES,
|
||||||
"Add submodule", "Submodule creation is not wired yet", SidebarItemKind::submodule);
|
"Add submodule", "Submodule creation is not wired yet", SidebarItemKind::submodule, 3);
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user