|
|
@ -1,6 +1,9 @@ |
|
|
|
module; |
|
|
|
#include "macros.hpp" |
|
|
|
|
|
|
|
#include <algorithm> |
|
|
|
#include <cmath> |
|
|
|
#include <deque> |
|
|
|
#include <memory> |
|
|
|
#include <print> |
|
|
|
#include <ranges> |
|
|
@ -9,7 +12,6 @@ module; |
|
|
|
#include <unordered_map> |
|
|
|
#include <unordered_set> |
|
|
|
#include <vector> |
|
|
|
#include <deque> |
|
|
|
export module Application; |
|
|
|
import Node; |
|
|
|
import Base; |
|
|
@ -30,7 +32,7 @@ protected: |
|
|
|
void OnLeftClick(); |
|
|
|
void OnKeyPressed(i32 key); |
|
|
|
void Render(); |
|
|
|
void ExecuteGraph(); |
|
|
|
Result<> ExecuteGraph(); |
|
|
|
std::deque<std::string> message_queue; |
|
|
|
std::string title; |
|
|
|
bool should_close; |
|
|
@ -38,6 +40,14 @@ protected: |
|
|
|
Node *selected_node = nullptr; |
|
|
|
OutputPort *selected_port = nullptr; |
|
|
|
Button run_button; |
|
|
|
enum class State { |
|
|
|
DEFAULT, |
|
|
|
CONNECTING, |
|
|
|
EDITING, |
|
|
|
ADDING, |
|
|
|
} current_state = State::DEFAULT; |
|
|
|
|
|
|
|
std::vector<Button> add_buttons; |
|
|
|
u64 framecounter = 0; |
|
|
|
}; |
|
|
|
} // namespace ffmpegraph |
|
|
@ -50,19 +60,16 @@ Application::Application(std::string _title) |
|
|
|
InitWindow(800, 600, this->title.c_str()); |
|
|
|
SetTargetFPS(60); |
|
|
|
SetExitKey(KEY_NULL); |
|
|
|
add_buttons.emplace_back(10, 10, 100, 20, "String Constant"); |
|
|
|
add_buttons.emplace_back(10, 35, 100, 20, "Input File"); |
|
|
|
add_buttons.emplace_back(10, 60, 100, 20, "Output File"); |
|
|
|
add_buttons.emplace_back(10, 85 , 100, 20, "Stream Spec Constant"); |
|
|
|
|
|
|
|
nodes.push_back(std::make_unique<InputNode>()); |
|
|
|
nodes.push_back(std::make_unique<StringConstantNode>()); |
|
|
|
nodes.push_back(std::make_unique<InputNode>()); |
|
|
|
nodes[0]->pos_x = 400; |
|
|
|
nodes[0]->pos_y = 10; |
|
|
|
nodes[1]->pos_x = 200; |
|
|
|
nodes[1]->pos_y = 100; |
|
|
|
run_button.width = 10 + MeasureText("Run", 10); |
|
|
|
} |
|
|
|
|
|
|
|
void Application::Tick() { |
|
|
|
if (selected_node) { |
|
|
|
if (current_state == State::EDITING) { |
|
|
|
selected_node->pos_x = GetMouseX(); |
|
|
|
selected_node->pos_y = GetMouseY(); |
|
|
|
} |
|
|
@ -75,6 +82,20 @@ void Application::ProcessEvents() { |
|
|
|
} |
|
|
|
|
|
|
|
void Application::OnLeftClick() { |
|
|
|
if (current_state == State::ADDING) { |
|
|
|
if (add_buttons[0].IsHovered()) { |
|
|
|
nodes.push_back(std::make_unique<StringConstantNode>()); |
|
|
|
} else if (add_buttons[1].IsHovered()) { |
|
|
|
nodes.push_back(std::make_unique<InputNode>()); |
|
|
|
} else if (add_buttons[2].IsHovered()) { |
|
|
|
nodes.push_back(std::make_unique<OutputNode>()); |
|
|
|
} else if (add_buttons[3].IsHovered()) { |
|
|
|
nodes.push_back(std::make_unique<StreamSpecConstantNode>()); |
|
|
|
} |
|
|
|
current_state = State::DEFAULT; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
auto GetPort = [&] -> Port * { |
|
|
|
for (auto &node : nodes) { |
|
|
|
for (auto *port : node->ports) { |
|
|
@ -85,15 +106,19 @@ void Application::OnLeftClick() { |
|
|
|
} |
|
|
|
return nullptr; |
|
|
|
}; |
|
|
|
if (selected_node) { |
|
|
|
if (current_state == State::EDITING) { |
|
|
|
selected_node = nullptr; |
|
|
|
} else if (selected_port) { |
|
|
|
current_state = State::DEFAULT; |
|
|
|
} else if (current_state == State::CONNECTING) { |
|
|
|
auto port = GetPort(); |
|
|
|
auto iport = dynamic_cast<InputPort *>(port); |
|
|
|
if (iport) { selected_port->TryConnect(*iport); } |
|
|
|
selected_port = nullptr; |
|
|
|
current_state = State::DEFAULT; |
|
|
|
} else if (run_button.IsHovered()) { |
|
|
|
ExecuteGraph(); |
|
|
|
if (auto res = ExecuteGraph(); not res) { |
|
|
|
message_queue.push_front(res.error()); |
|
|
|
} |
|
|
|
} else { |
|
|
|
i32 x = GetMouseX(); |
|
|
|
i32 y = GetMouseY(); |
|
|
@ -103,6 +128,7 @@ void Application::OnLeftClick() { |
|
|
|
if (oport->GetConnected()) { |
|
|
|
oport->Disconnect(); |
|
|
|
} else { |
|
|
|
current_state = State::CONNECTING; |
|
|
|
selected_port = oport; |
|
|
|
} |
|
|
|
} else { |
|
|
@ -110,6 +136,7 @@ void Application::OnLeftClick() { |
|
|
|
if (node->pos_x < x and x < node->pos_x + node->Width() and node->pos_y < y |
|
|
|
and y < node->pos_y + node->Height()) { |
|
|
|
selected_node = node.get(); |
|
|
|
current_state = State::EDITING; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
@ -117,7 +144,7 @@ void Application::OnLeftClick() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void Application::ExecuteGraph() { |
|
|
|
Result<> Application::ExecuteGraph() { |
|
|
|
std::unordered_map<Node *, isz> index_map; |
|
|
|
std::unordered_map<isz, std::unordered_set<isz>> adj_list; |
|
|
|
for (auto [i, node] : vws::enumerate(nodes)) { |
|
|
@ -132,41 +159,87 @@ void Application::ExecuteGraph() { |
|
|
|
if (auto n = dynamic_cast<StringConstantNode *>(raw_node)) { |
|
|
|
if (auto c = n->out.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } |
|
|
|
} |
|
|
|
// TODO add cases for all new types of nodes |
|
|
|
if (auto _ = dynamic_cast<OutputNode *>(raw_node)) {} |
|
|
|
// OutputNode doesn’t have output ports. |
|
|
|
} |
|
|
|
|
|
|
|
// TODO add cases for all new types of nodes |
|
|
|
|
|
|
|
std::vector<Node *> sorted_nodes; |
|
|
|
while (not adj_list.empty()) { |
|
|
|
bool changed = false; |
|
|
|
for (auto &[i, incoming] : adj_list) { |
|
|
|
if (incoming.empty()) { |
|
|
|
sorted_nodes.push_back(nodes[usz(i)].get()); |
|
|
|
for (auto &[_, thing] : adj_list) { thing.erase(i); } |
|
|
|
for (auto &thing : adj_list | vws::values) { thing.erase(i); } |
|
|
|
adj_list.erase(i); |
|
|
|
changed = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if (not changed) { |
|
|
|
message_queue.push_back("ERROR: Graph has cycle"); |
|
|
|
return; |
|
|
|
} |
|
|
|
return Error("Graph has cycle"); |
|
|
|
} |
|
|
|
for (auto node : sorted_nodes) { |
|
|
|
node->Run(); |
|
|
|
} |
|
|
|
for (auto node : sorted_nodes) { Try(node->Run()); } |
|
|
|
return{}; |
|
|
|
} |
|
|
|
|
|
|
|
void Application::OnKeyPressed(i32 key) { |
|
|
|
if ((key == KEY_EQUAL or key == KEY_KP_ADD) and current_state == State::DEFAULT) { |
|
|
|
current_state = State::ADDING; |
|
|
|
} |
|
|
|
if (current_state != State::EDITING) return; |
|
|
|
if (key == KEY_DELETE) { |
|
|
|
for(auto& node : nodes) { |
|
|
|
for (auto raw_port: node->ports) { |
|
|
|
if (auto port = dynamic_cast<OutputPort*>(raw_port)) { |
|
|
|
if (auto a = port->GetConnected(); a and a->owner == selected_node) { |
|
|
|
port->Disconnect(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
std::erase_if(nodes, [&](auto const &it) { return it.get() == selected_node; }); |
|
|
|
selected_node = nullptr; |
|
|
|
current_state = State::DEFAULT; |
|
|
|
return; |
|
|
|
} |
|
|
|
auto selected = dynamic_cast<StringConstantNode *>(selected_node); |
|
|
|
if (not selected) return; |
|
|
|
if (key == KEY_ENTER) { |
|
|
|
if (key == KEY_ENTER or key == KEY_KP_ENTER) { |
|
|
|
selected_node = nullptr; |
|
|
|
current_state = State::DEFAULT; |
|
|
|
} else if (key == KEY_BACKSPACE) { |
|
|
|
if (not selected->in.data.empty()) selected->in.data.pop_back(); |
|
|
|
} else if (0x20 <= key and key <= 0x7E) { |
|
|
|
if (not IsKeyDown(KEY_LEFT_SHIFT) and not IsKeyDown(KEY_RIGHT_SHIFT) and 'A' <= key and key <= 'Z') { |
|
|
|
key += 32; |
|
|
|
} else if (IsKeyDown(KEY_LEFT_SHIFT) or IsKeyDown(KEY_RIGHT_SHIFT)) { |
|
|
|
switch (key) { |
|
|
|
case KEY_GRAVE: key = '~'; break; |
|
|
|
case KEY_ONE: key = '!'; break; |
|
|
|
case KEY_TWO: key = '"'; break; |
|
|
|
case KEY_THREE: key = '#'; break; |
|
|
|
case KEY_FOUR: key = '$'; break; |
|
|
|
case KEY_FIVE: key = '%'; break; |
|
|
|
case KEY_SIX: key = '^'; break; |
|
|
|
case KEY_SEVEN: key = '&'; break; |
|
|
|
case KEY_EIGHT: key = '*'; break; |
|
|
|
case KEY_NINE: key = '('; break; |
|
|
|
case KEY_ZERO: key = ')'; break; |
|
|
|
case KEY_MINUS: key = '_'; break; |
|
|
|
case KEY_EQUAL: key = '+'; break; |
|
|
|
case KEY_LEFT_BRACKET: key = '{'; break; |
|
|
|
case KEY_RIGHT_BRACKET: key = '}'; break; |
|
|
|
case KEY_SEMICOLON: key = ':'; break; |
|
|
|
case KEY_APOSTROPHE: key = '"'; break; |
|
|
|
case KEY_BACKSLASH: key = '|'; break; |
|
|
|
case KEY_COMMA: key = '<'; break; |
|
|
|
case KEY_PERIOD: key = '>'; break; |
|
|
|
case KEY_SLASH: key = '?'; break; |
|
|
|
default:; |
|
|
|
} |
|
|
|
} |
|
|
|
selected->in.data += char(key); |
|
|
|
} |
|
|
@ -174,9 +247,7 @@ void Application::OnKeyPressed(i32 key) { |
|
|
|
|
|
|
|
void Application::Render() { |
|
|
|
framecounter++; |
|
|
|
if (not (framecounter%300) and not message_queue.empty()) { |
|
|
|
message_queue.pop_back(); |
|
|
|
} |
|
|
|
if (not(framecounter % 300) and not message_queue.empty()) { message_queue.pop_back(); } |
|
|
|
BeginDrawing(); |
|
|
|
ClearBackground(RAYWHITE); |
|
|
|
message_queue.resize(std::min(message_queue.size(), usz(10))); |
|
|
@ -185,11 +256,16 @@ void Application::Render() { |
|
|
|
DrawText(message.c_str(), 5, GetScreenHeight() - offset, 10, RED); |
|
|
|
offset += 20; |
|
|
|
} |
|
|
|
if (current_state == State::ADDING) { |
|
|
|
for (auto b : add_buttons) { |
|
|
|
b.Render(); |
|
|
|
} |
|
|
|
} else { |
|
|
|
if (selected_port) { DrawLine(selected_port->pos_x, selected_port->pos_y, GetMouseX(), GetMouseY(), BLACK); } |
|
|
|
for (auto &node : nodes) { node->Render(); } |
|
|
|
run_button.Render(); |
|
|
|
} |
|
|
|
EndDrawing(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void Application::Run() { |
|
|
|