Merge pull request 'Made console command registry a better usage pattern' (#4) from dev into main
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
+2
-1
@@ -39,5 +39,6 @@ endif()
|
||||
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(editor)
|
||||
add_subdirectory(testbed)
|
||||
add_subdirectory(examples)
|
||||
add_subdirectory(examples)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
COMMAND_TO_METHOD = {
|
||||
"branch-from": "branch_from",
|
||||
"bash-completions": "bash_completions",
|
||||
}
|
||||
METHOD_TO_COMMAND = {method: command for command, method in COMMAND_TO_METHOD.items()}
|
||||
@@ -36,6 +39,28 @@ class DevHelper:
|
||||
command = f"rm -rf build && cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug"
|
||||
self._exec_in_root(command, replace=True)
|
||||
|
||||
##
|
||||
# Checks out a branch named after a Gitea issue URL.
|
||||
# If the issue is from this project's repo, the branch name will be:
|
||||
# issue-<issue_number>/<issue_title>
|
||||
# If the issue is from another repo (presumably upstream), the branch name will be:
|
||||
# <issue_owner>-issue-<issue_number>/<issue_title>
|
||||
def branch_from(self, *args, **kwargs):
|
||||
issue_url = args[0] if args else None
|
||||
if issue_url is None:
|
||||
raise ValueError("Must provide a Gitea issue URL.")
|
||||
|
||||
base_url, issue_repo, issue_number = self._parse_issue_url(issue_url)
|
||||
issue_title = self._fetch_issue_title(base_url, issue_repo, issue_number)
|
||||
branch_name = self._format_branch_name(issue_repo, issue_number, issue_title)
|
||||
|
||||
exists = subprocess.run(
|
||||
["git", "-C", self._project_root(), "rev-parse", "--verify", "--quiet", f"refs/heads/{branch_name}"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
).returncode == 0
|
||||
flag = "" if exists else "-b "
|
||||
self._exec_in_root(f"git checkout {flag}{shlex.quote(branch_name)}", replace=True)
|
||||
|
||||
def renderdoc(self, *args, **kwargs):
|
||||
qrenderdoc = self._find_qrenderdoc()
|
||||
self._exec_in_root(qrenderdoc, replace=True)
|
||||
@@ -84,6 +109,39 @@ class DevHelper:
|
||||
print(" ".join(completions))
|
||||
|
||||
# Private helpers
|
||||
def _parse_issue_url(self, url):
|
||||
match = re.match(r"^(https?://[^/]+)/([^/]+)/([^/]+)/issues/(\d+)", url)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a Gitea issue URL: {url}")
|
||||
return match[1], f"{match[2]}/{match[3]}", match[4]
|
||||
|
||||
def _fetch_issue_title(self, base_url, repo, issue_number):
|
||||
if shutil.which("tea") is None:
|
||||
raise RuntimeError("tea not found on PATH (install gitea tea and run `tea login add`)")
|
||||
|
||||
endpoint = f"{base_url}/api/v1/repos/{repo}/issues/{issue_number}"
|
||||
print(f"Running: tea api {endpoint}")
|
||||
result = subprocess.run(["tea", "api", endpoint], stdout=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"failed to fetch issue title from {endpoint}")
|
||||
return json.loads(result.stdout)["title"]
|
||||
|
||||
def _format_branch_name(self, issue_repo, issue_number, issue_title):
|
||||
formatted_title = re.sub(r"-+", "-", re.sub(r"[^a-z0-9\-]", "", re.sub(r"\s+", "-", issue_title.lower().strip())))
|
||||
if issue_repo == self._origin_repo():
|
||||
return f"issue-{issue_number}/{formatted_title}"
|
||||
issue_owner = issue_repo.split("/")[0]
|
||||
return f"{issue_owner}-issue-{issue_number}/{formatted_title}"
|
||||
|
||||
def _origin_repo(self):
|
||||
result = subprocess.run(
|
||||
["git", "-C", self._project_root(), "remote", "get-url", "origin"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
match = re.search(r"[:/]([^/:]+/[^/]+?)(?:\.git)?$", result.stdout.strip())
|
||||
return match[1] if match else None
|
||||
|
||||
def _find_glslc(self):
|
||||
glslc = shutil.which("glslc")
|
||||
if glslc is None:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
set(NAME editor)
|
||||
set(ENGINE_NAME ${PROJECT_NAME})
|
||||
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp src/*.cpp)
|
||||
|
||||
add_library(${NAME}_deps INTERFACE)
|
||||
|
||||
target_link_libraries(${NAME}_deps INTERFACE
|
||||
${ENGINE_NAME}
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
|
||||
|
||||
add_executable(editor ${SOURCES})
|
||||
|
||||
target_link_libraries(editor PRIVATE ${NAME}_deps)
|
||||
|
||||
target_include_directories(editor PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/engine/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/imgui"
|
||||
)
|
||||
|
||||
target_compile_definitions(editor PRIVATE
|
||||
EDITOR_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders"
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <iostream>
|
||||
#include <sophia/application.hpp>
|
||||
|
||||
#include "panels/asset_browser_panel.hpp"
|
||||
#include "panels/inspector_panel.hpp"
|
||||
#include "panels/scene_hierarchy_panel.hpp"
|
||||
|
||||
class Editor : public sophia::Application {
|
||||
public:
|
||||
Editor() : Application{{.width = 750, .height = 1000, .name = "Editor"}} {}
|
||||
|
||||
void onCreate() override {
|
||||
std::cout << "Editor created" << std::endl;
|
||||
|
||||
debugUI().show();
|
||||
|
||||
panels().emplace<SceneHierarchyPanel>();
|
||||
panels().emplace<InspectorPanel>();
|
||||
panels().emplace<AssetBrowserPanel>();
|
||||
}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {}
|
||||
};
|
||||
|
||||
SOPHIA_MAIN(Editor)
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "asset_browser_panel.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
void AssetBrowserPanel::onUpdate(const sophia::FrameInfo&) {
|
||||
ImGui::Begin("Asset Browser");
|
||||
|
||||
ImGui::BeginChild("Folders", ImVec2(150, 0), true);
|
||||
for (int i = 0; i < static_cast<int>(folders.size()); i++) {
|
||||
if (ImGui::Selectable(folders[static_cast<size_t>(i)].name.c_str(),
|
||||
selectedFolder == i)) {
|
||||
selectedFolder = i;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("Files", ImVec2(0, 0), true);
|
||||
const Folder& folder = folders[static_cast<size_t>(selectedFolder)];
|
||||
for (const std::string& file : folder.files) {
|
||||
ImGui::Selectable(file.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class AssetBrowserPanel : public sophia::Panel {
|
||||
public:
|
||||
AssetBrowserPanel() : Panel{"Asset Browser"} {}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
struct Folder {
|
||||
std::string name;
|
||||
std::vector<std::string> files;
|
||||
};
|
||||
|
||||
std::vector<Folder> folders = {
|
||||
{"Models", {"player_capsule.fbx", "ground_plane.fbx", "enemy_grunt.fbx"}},
|
||||
{"Textures",
|
||||
{"brick_diffuse.png", "grass_albedo.png", "player_diffuse.png"}},
|
||||
{"Materials", {"player_mat.mat", "ground_mat.mat"}},
|
||||
{"Scripts", {"PlayerController.lua", "EnemyAI.lua"}},
|
||||
{"Scenes", {"MainMenu.scene", "Level_01.scene"}},
|
||||
};
|
||||
|
||||
int selectedFolder = 0;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "inspector_panel.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
void InspectorPanel::onUpdate(const sophia::FrameInfo&) {
|
||||
ImGui::Begin("Inspector");
|
||||
|
||||
char nameBuf[128];
|
||||
std::snprintf(nameBuf, sizeof(nameBuf), "%s", name.c_str());
|
||||
if (ImGui::InputText("Name", nameBuf, sizeof(nameBuf))) {
|
||||
name = nameBuf;
|
||||
}
|
||||
ImGui::Text("Tag: %s", tag.c_str());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::DragFloat3("Position", &position.x, 0.1f);
|
||||
ImGui::DragFloat3("Rotation", &rotation.x, 0.1f);
|
||||
ImGui::DragFloat3("Scale", &scale.x, 0.1f);
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Mesh Renderer",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Text("Mesh: %s", meshName.c_str());
|
||||
ImGui::Text("Material: %s", materialName.c_str());
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Text("%s", scriptName.c_str());
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <string>
|
||||
|
||||
class InspectorPanel : public sophia::Panel {
|
||||
public:
|
||||
InspectorPanel() : Panel{"Inspector"} {}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
std::string name = "Player";
|
||||
std::string tag = "Player";
|
||||
|
||||
glm::vec3 position{0.0f, 1.0f, 0.0f};
|
||||
glm::vec3 rotation{0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 scale{1.0f, 1.0f, 1.0f};
|
||||
|
||||
std::string meshName = "player_capsule.fbx";
|
||||
std::string materialName = "player_mat";
|
||||
std::string scriptName = "PlayerController.lua";
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "scene_hierarchy_panel.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
void SceneHierarchyPanel::drawNode(const Node& node) {
|
||||
if (node.children.empty()) {
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||
if (node.name == selected) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
ImGui::TreeNodeEx(node.name.c_str(), flags);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
selected = node.name;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
||||
for (const Node& child : node.children) {
|
||||
drawNode(child);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) {
|
||||
ImGui::Begin("Scene Hierarchy");
|
||||
for (const Node& node : nodes) {
|
||||
drawNode(node);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class SceneHierarchyPanel : public sophia::Panel {
|
||||
public:
|
||||
SceneHierarchyPanel() : Panel{"Scene Hierarchy"} {}
|
||||
|
||||
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
std::string name;
|
||||
std::vector<Node> children;
|
||||
};
|
||||
|
||||
void drawNode(const Node& node);
|
||||
|
||||
std::vector<Node> nodes = {
|
||||
{"Main Camera", {}},
|
||||
{"Directional Light", {}},
|
||||
{"Ground", {}},
|
||||
{"Player", {}},
|
||||
{"Enemies", {{"Enemy_01", {}}, {"Enemy_02", {}}, {"Enemy_03", {}}}},
|
||||
{"Props", {{"Crate_01", {}}, {"Crate_02", {}}}},
|
||||
};
|
||||
|
||||
std::string selected;
|
||||
};
|
||||
@@ -19,6 +19,7 @@ target_include_directories(${ENGINE_NAME} PUBLIC
|
||||
|
||||
target_link_libraries(${ENGINE_NAME} PUBLIC
|
||||
vulkan
|
||||
shaderc
|
||||
glfw
|
||||
glm
|
||||
imgui
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <sophia/asset_registry.hpp>
|
||||
#include <sophia/camera_2d.hpp>
|
||||
#include <sophia/canvas.hpp>
|
||||
#include <sophia/console/console_command_registry.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/types.hpp>
|
||||
#include <sophia/ui/debug_ui.hpp>
|
||||
@@ -52,6 +53,8 @@ class Application {
|
||||
Camera2D camera;
|
||||
std::vector<std::string> args;
|
||||
|
||||
ConsoleCommandRegistry consoleCommandRegistry;
|
||||
|
||||
void drawMesh(MeshHandle handle, const glm::mat4& transform);
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class ConsoleCommandRegistry {
|
||||
public:
|
||||
using Handler = std::function<std::string(const std::vector<std::string>&)>;
|
||||
|
||||
struct CommandInfo {
|
||||
std::string name;
|
||||
std::string help;
|
||||
};
|
||||
|
||||
void registerCommand(std::string name, std::string help, Handler handler);
|
||||
|
||||
bool dispatch(const std::string& name, const std::vector<std::string>& args,
|
||||
std::string& outResult) const;
|
||||
|
||||
std::vector<CommandInfo> list() const;
|
||||
|
||||
private:
|
||||
struct Command {
|
||||
std::string help;
|
||||
Handler handler;
|
||||
};
|
||||
|
||||
std::map<std::string, Command> commands;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/console/console_command_registry.hpp>
|
||||
#include <sophia/handles.hpp>
|
||||
#include <sophia/ui/panel_registry.hpp>
|
||||
|
||||
@@ -10,7 +11,7 @@ class DebugUI {
|
||||
DebugUI(const DebugUI&) = delete;
|
||||
DebugUI& operator=(const DebugUI&) = delete;
|
||||
|
||||
explicit DebugUI(PanelRegistry& registry);
|
||||
DebugUI(PanelRegistry& registry, ConsoleCommandRegistry& commands);
|
||||
~DebugUI() = default;
|
||||
|
||||
void show();
|
||||
|
||||
@@ -206,7 +206,7 @@ Application::Application(const Config& config)
|
||||
canvas{this},
|
||||
args{std::move(commandLineArgs)},
|
||||
assetRegistry{impl->getDevice()},
|
||||
_debugUI{this->panelRegistry} {}
|
||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||
|
||||
Application::~Application() = default;
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <sophia/console/console_command_registry.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
void ConsoleCommandRegistry::registerCommand(std::string name, std::string help,
|
||||
Handler handler) {
|
||||
commands[std::move(name)] = Command{std::move(help), std::move(handler)};
|
||||
}
|
||||
|
||||
bool ConsoleCommandRegistry::dispatch(const std::string& name,
|
||||
const std::vector<std::string>& args,
|
||||
std::string& outResult) const {
|
||||
auto it = commands.find(name);
|
||||
if (it == commands.end()) {
|
||||
return false;
|
||||
}
|
||||
outResult = it->second.handler(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ConsoleCommandRegistry::CommandInfo> ConsoleCommandRegistry::list()
|
||||
const {
|
||||
std::vector<CommandInfo> result;
|
||||
result.reserve(commands.size());
|
||||
for (const auto& [name, command] : commands) {
|
||||
result.push_back(CommandInfo{name, command.help});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DebugUI::DebugUI(PanelRegistry& registry) : registry{registry} {
|
||||
DebugUI::DebugUI(PanelRegistry& registry, ConsoleCommandRegistry& commands)
|
||||
: registry{registry} {
|
||||
// #ifndef NDEBUG
|
||||
this->panelRegistryHandle = registry.emplace<PanelRegistryPanel>(registry);
|
||||
this->frameStatsHandle = registry.emplace<FrameStatsPanel>();
|
||||
this->consoleHandle = registry.emplace<ConsolePanel>();
|
||||
this->consoleHandle = registry.emplace<ConsolePanel>(commands);
|
||||
// #endif // !NDEBUG
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "console_panel.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
@@ -11,10 +12,6 @@ constexpr ImVec4 COLOR_ERROR = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
|
||||
constexpr ImVec4 COLOR_INFO = ImVec4(0.4f, 0.7f, 1.0f, 1.0f);
|
||||
} // namespace
|
||||
|
||||
void ConsolePanel::registerDefaultCommands() {
|
||||
log("Console ready. Type 'help' for a list of commands.");
|
||||
}
|
||||
|
||||
void ConsolePanel::log(const std::string& message) {
|
||||
entries.push_back(Entry{message, COLOR_DEFAULT});
|
||||
scrollToBottom = true;
|
||||
@@ -22,6 +19,16 @@ void ConsolePanel::log(const std::string& message) {
|
||||
|
||||
void ConsolePanel::clear() { entries.clear(); }
|
||||
|
||||
std::vector<std::string> ConsolePanel::tokenize(const std::string& command) {
|
||||
std::istringstream stream(command);
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
while (stream >> token) {
|
||||
tokens.push_back(std::move(token));
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
void ConsolePanel::execute(const std::string& command) {
|
||||
entries.push_back(Entry{"> " + command, COLOR_COMMAND});
|
||||
scrollToBottom = true;
|
||||
@@ -31,19 +38,47 @@ void ConsolePanel::execute(const std::string& command) {
|
||||
history.end());
|
||||
history.push_back(command);
|
||||
|
||||
if (command == "help") {
|
||||
entries.push_back(
|
||||
Entry{"Available commands: help, clear, history", COLOR_INFO});
|
||||
} else if (command == "clear") {
|
||||
std::vector<std::string> tokens = tokenize(command);
|
||||
if (tokens.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& name = tokens.front();
|
||||
const std::vector<std::string> args(tokens.begin() + 1, tokens.end());
|
||||
|
||||
if (name == "help") {
|
||||
entries.push_back(Entry{"help - List available commands", COLOR_INFO});
|
||||
entries.push_back(Entry{"clear - Clear the console output", COLOR_INFO});
|
||||
entries.push_back(Entry{"history - Show command history", COLOR_INFO});
|
||||
for (const auto& info : consoleCommandRegistry.list()) {
|
||||
entries.push_back(Entry{info.name + " - " + info.help, COLOR_INFO});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == "clear") {
|
||||
clear();
|
||||
} else if (command == "history") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == "history") {
|
||||
for (const std::string& past : history) {
|
||||
entries.push_back(Entry{past, COLOR_DEFAULT});
|
||||
}
|
||||
} else if (commandCallback) {
|
||||
commandCallback(*this, command);
|
||||
} else {
|
||||
entries.push_back(Entry{"Unknown command: '" + command + "'", COLOR_ERROR});
|
||||
return;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
try {
|
||||
if (consoleCommandRegistry.dispatch(name, args, result)) {
|
||||
if (!result.empty()) {
|
||||
entries.push_back(Entry{result, COLOR_INFO});
|
||||
}
|
||||
} else {
|
||||
entries.push_back(Entry{"Unknown command: '" + name + "'", COLOR_ERROR});
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
entries.push_back(Entry{e.what(), COLOR_ERROR});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +116,6 @@ void ConsolePanel::onUpdate(const FrameInfo&) {
|
||||
if (ImGui::SmallButton("Clear")) {
|
||||
clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Auto-scroll", &autoScroll);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -97,8 +130,7 @@ void ConsolePanel::onUpdate(const FrameInfo&) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (scrollToBottom ||
|
||||
(autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
|
||||
if (scrollToBottom) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
scrollToBottom = false;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <functional>
|
||||
#include <sophia/console/console_command_registry.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <string>
|
||||
@@ -12,39 +12,34 @@ namespace sophia {
|
||||
|
||||
class ConsolePanel : public Panel {
|
||||
public:
|
||||
using CommandCallback =
|
||||
std::function<void(ConsolePanel&, const std::string&)>;
|
||||
|
||||
ConsolePanel() : Panel{"Console"} { registerDefaultCommands(); }
|
||||
explicit ConsolePanel(ConsoleCommandRegistry& consoleCommandRegistry)
|
||||
: Panel{"Console"}, consoleCommandRegistry{consoleCommandRegistry} {
|
||||
log("Console ready. Type 'help' for a list of commands.");
|
||||
}
|
||||
|
||||
void onUpdate(const FrameInfo& frameInfo) override;
|
||||
|
||||
void log(const std::string& message);
|
||||
void clear();
|
||||
|
||||
void setCommandCallback(CommandCallback callback) {
|
||||
this->commandCallback = std::move(callback);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string text;
|
||||
ImVec4 color;
|
||||
};
|
||||
|
||||
void registerDefaultCommands();
|
||||
void execute(const std::string& command);
|
||||
int textEditCallback(struct ImGuiInputTextCallbackData* data);
|
||||
static std::vector<std::string> tokenize(const std::string& command);
|
||||
|
||||
std::vector<Entry> entries;
|
||||
std::vector<std::string> history;
|
||||
int historyPos = -1;
|
||||
|
||||
char inputBuffer[256] = {};
|
||||
bool autoScroll = true;
|
||||
bool scrollToBottom = false;
|
||||
|
||||
CommandCallback commandCallback;
|
||||
ConsoleCommandRegistry& consoleCommandRegistry;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
|
||||
@@ -3,15 +3,10 @@ set(ENGINE_NAME ${PROJECT_NAME})
|
||||
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp src/*.cpp)
|
||||
|
||||
add_library(deps INTERFACE)
|
||||
add_library(${NAME}_deps INTERFACE)
|
||||
|
||||
target_link_libraries(deps INTERFACE
|
||||
target_link_libraries(${NAME}_deps INTERFACE
|
||||
${ENGINE_NAME}
|
||||
vulkan
|
||||
shaderc
|
||||
glfw
|
||||
glm
|
||||
imgui
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
@@ -19,7 +14,7 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CM
|
||||
|
||||
add_executable(testbed main.cpp)
|
||||
|
||||
target_link_libraries(testbed PRIVATE deps)
|
||||
target_link_libraries(testbed PRIVATE ${NAME}_deps)
|
||||
|
||||
target_include_directories(testbed PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/engine/include"
|
||||
|
||||
Reference in New Issue
Block a user