Browse Source

More work

main
Annwan 3 months ago
parent
commit
61c6bab505
  1. 27
      CMakeLists.txt
  2. 136
      src/Application.cppm
  3. 45
      src/Base.cppm
  4. 78
      src/Node.cppm
  5. 21
      src/Nodes/InputNode.cpp
  6. 20
      src/Nodes/OutputNode.cpp
  7. 34
      src/Nodes/StreamSpecConstantNode.cpp
  8. 13
      src/Nodes/StringConstantNode.cpp
  9. 83
      src/Port.cppm
  10. 21
      src/Ports/InputPort.cpp
  11. 32
      src/Ports/OutputPort.cpp
  12. 15
      src/Ports/StringUserInput.cpp
  13. 20
      src/macros.hpp
  14. 2
      src/main.cpp

27
CMakeLists.txt

@ -31,22 +31,18 @@ FetchContent_Declare(clopts
) )
FetchContent_MakeAvailable(clopts) FetchContent_MakeAvailable(clopts)
# Find all the modules
message(STATUS "Looking for files")
file(GLOB_RECURSE modules src/*.cppm) file(GLOB_RECURSE modules src/*.cppm)
# Create the executable target
add_executable(ffmpegraph)
# Main file
target_sources(ffmpegraph
PUBLIC
src/main.cpp
)
# The modules
target_sources(ffmpegraph
PUBLIC
FILE_SET CXX_MODULES FILES
${modules}
file(GLOB_RECURSE headers src/*.hpp)
file(GLOB_RECURSE impls src/*.cpp)
message(STATUS "Create target")
add_executable(ffmpegraph ${impls})
target_sources(ffmpegraph PUBLIC
FILE_SET CXX_MODULES FILES ${modules}
FILE_SET HEADERS FILES ${headers}
) )
#Compile flags
target_compile_options(ffmpegraph PRIVATE target_compile_options(ffmpegraph PRIVATE
-Wall -Wextra # Standard screaming -Wall -Wextra # Standard screaming
-Wconversion -Wsign-conversion # Scream about accidentally converting stuff -Wconversion -Wsign-conversion # Scream about accidentally converting stuff
@ -55,13 +51,12 @@ target_compile_options(ffmpegraph PRIVATE
$<$<CONFIG:DEBUG>:-O0 -g3 -glldb> $<$<CONFIG:DEBUG>:-O0 -g3 -glldb>
$<$<CONFIG:RELEASE>:-O3 -march=native> $<$<CONFIG:RELEASE>:-O3 -march=native>
) )
# color shit out
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(ffmpegraph PRIVATE -fdiagnostics-color=always) target_compile_options(ffmpegraph PRIVATE -fdiagnostics-color=always)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(ffmpegraph PRIVATE -fcolor-diagnostics) target_compile_options(ffmpegraph PRIVATE -fcolor-diagnostics)
endif() endif()
# Link against exernal libraries
target_link_libraries(ffmpegraph PRIVATE raylib m) target_link_libraries(ffmpegraph PRIVATE raylib m)
target_include_directories(ffmpegraph PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/libs/clopts/include") target_include_directories(ffmpegraph PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/libs/clopts/include")

136
src/Application.cppm

@ -1,6 +1,9 @@
module; module;
#include "macros.hpp"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <deque>
#include <memory> #include <memory>
#include <print> #include <print>
#include <ranges> #include <ranges>
@ -9,7 +12,6 @@ module;
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include <deque>
export module Application; export module Application;
import Node; import Node;
import Base; import Base;
@ -30,7 +32,7 @@ protected:
void OnLeftClick(); void OnLeftClick();
void OnKeyPressed(i32 key); void OnKeyPressed(i32 key);
void Render(); void Render();
void ExecuteGraph();
Result<> ExecuteGraph();
std::deque<std::string> message_queue; std::deque<std::string> message_queue;
std::string title; std::string title;
bool should_close; bool should_close;
@ -38,6 +40,14 @@ protected:
Node *selected_node = nullptr; Node *selected_node = nullptr;
OutputPort *selected_port = nullptr; OutputPort *selected_port = nullptr;
Button run_button; Button run_button;
enum class State {
DEFAULT,
CONNECTING,
EDITING,
ADDING,
} current_state = State::DEFAULT;
std::vector<Button> add_buttons;
u64 framecounter = 0; u64 framecounter = 0;
}; };
} // namespace ffmpegraph } // namespace ffmpegraph
@ -50,19 +60,16 @@ Application::Application(std::string _title)
InitWindow(800, 600, this->title.c_str()); InitWindow(800, 600, this->title.c_str());
SetTargetFPS(60); SetTargetFPS(60);
SetExitKey(KEY_NULL); 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); run_button.width = 10 + MeasureText("Run", 10);
} }
void Application::Tick() { void Application::Tick() {
if (selected_node) {
if (current_state == State::EDITING) {
selected_node->pos_x = GetMouseX(); selected_node->pos_x = GetMouseX();
selected_node->pos_y = GetMouseY(); selected_node->pos_y = GetMouseY();
} }
@ -75,6 +82,20 @@ void Application::ProcessEvents() {
} }
void Application::OnLeftClick() { 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 * { auto GetPort = [&] -> Port * {
for (auto &node : nodes) { for (auto &node : nodes) {
for (auto *port : node->ports) { for (auto *port : node->ports) {
@ -85,15 +106,19 @@ void Application::OnLeftClick() {
} }
return nullptr; return nullptr;
}; };
if (selected_node) {
if (current_state == State::EDITING) {
selected_node = nullptr; selected_node = nullptr;
} else if (selected_port) {
current_state = State::DEFAULT;
} else if (current_state == State::CONNECTING) {
auto port = GetPort(); auto port = GetPort();
auto iport = dynamic_cast<InputPort *>(port); auto iport = dynamic_cast<InputPort *>(port);
if (iport) { selected_port->TryConnect(*iport); } if (iport) { selected_port->TryConnect(*iport); }
selected_port = nullptr; selected_port = nullptr;
current_state = State::DEFAULT;
} else if (run_button.IsHovered()) { } else if (run_button.IsHovered()) {
ExecuteGraph();
if (auto res = ExecuteGraph(); not res) {
message_queue.push_front(res.error());
}
} else { } else {
i32 x = GetMouseX(); i32 x = GetMouseX();
i32 y = GetMouseY(); i32 y = GetMouseY();
@ -103,6 +128,7 @@ void Application::OnLeftClick() {
if (oport->GetConnected()) { if (oport->GetConnected()) {
oport->Disconnect(); oport->Disconnect();
} else { } else {
current_state = State::CONNECTING;
selected_port = oport; selected_port = oport;
} }
} else { } 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 if (node->pos_x < x and x < node->pos_x + node->Width() and node->pos_y < y
and y < node->pos_y + node->Height()) { and y < node->pos_y + node->Height()) {
selected_node = node.get(); selected_node = node.get();
current_state = State::EDITING;
break; break;
} }
} }
@ -117,7 +144,7 @@ void Application::OnLeftClick() {
} }
} }
void Application::ExecuteGraph() {
Result<> Application::ExecuteGraph() {
std::unordered_map<Node *, isz> index_map; std::unordered_map<Node *, isz> index_map;
std::unordered_map<isz, std::unordered_set<isz>> adj_list; std::unordered_map<isz, std::unordered_set<isz>> adj_list;
for (auto [i, node] : vws::enumerate(nodes)) { for (auto [i, node] : vws::enumerate(nodes)) {
@ -132,41 +159,87 @@ void Application::ExecuteGraph() {
if (auto n = dynamic_cast<StringConstantNode *>(raw_node)) { 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]); } 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; std::vector<Node *> sorted_nodes;
while (not adj_list.empty()) { while (not adj_list.empty()) {
bool changed = false; bool changed = false;
for (auto &[i, incoming] : adj_list) { for (auto &[i, incoming] : adj_list) {
if (incoming.empty()) { if (incoming.empty()) {
sorted_nodes.push_back(nodes[usz(i)].get()); 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); adj_list.erase(i);
changed = true; changed = true;
break; break;
} }
} }
if (not changed) { 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) { 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); auto selected = dynamic_cast<StringConstantNode *>(selected_node);
if (not selected) return; if (not selected) return;
if (key == KEY_ENTER) {
if (key == KEY_ENTER or key == KEY_KP_ENTER) {
selected_node = nullptr; selected_node = nullptr;
current_state = State::DEFAULT;
} else if (key == KEY_BACKSPACE) { } else if (key == KEY_BACKSPACE) {
if (not selected->in.data.empty()) selected->in.data.pop_back(); if (not selected->in.data.empty()) selected->in.data.pop_back();
} else if (0x20 <= key and key <= 0x7E) { } 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') { if (not IsKeyDown(KEY_LEFT_SHIFT) and not IsKeyDown(KEY_RIGHT_SHIFT) and 'A' <= key and key <= 'Z') {
key += 32; 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); selected->in.data += char(key);
} }
@ -174,22 +247,25 @@ void Application::OnKeyPressed(i32 key) {
void Application::Render() { void Application::Render() {
framecounter++; 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(); BeginDrawing();
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
message_queue.resize(std::min(message_queue.size(), usz(10))); message_queue.resize(std::min(message_queue.size(), usz(10)));
i32 offset = 15; i32 offset = 15;
for (auto& message: message_queue) {
for (auto &message : message_queue) {
DrawText(message.c_str(), 5, GetScreenHeight() - offset, 10, RED); DrawText(message.c_str(), 5, GetScreenHeight() - offset, 10, RED);
offset += 20; offset += 20;
} }
if (selected_port) { DrawLine(selected_port->pos_x, selected_port->pos_y, GetMouseX(), GetMouseY(), BLACK); }
for (auto &node : nodes) { node->Render(); }
run_button.Render();
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(); EndDrawing();
} }
void Application::Run() { void Application::Run() {

45
src/Base.cppm

@ -1,5 +1,6 @@
module; module;
#include <cstdint> #include <cstdint>
#include <expected>
#include <format> #include <format>
#include <ranges> #include <ranges>
#include <string> #include <string>
@ -8,40 +9,48 @@ module;
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
export module Base; export module Base;
export {
namespace rgs = std::ranges;
namespace vws = std::views;
export namespace rgs = std::ranges;
export namespace vws = std::views;
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using usz = std::size_t;
using uptr = std::uintptr_t;
export using u8 = std::uint8_t;
export using u16 = std::uint16_t;
export using u32 = std::uint32_t;
export using u64 = std::uint64_t;
export using usz = std::size_t;
export using uptr = std::uintptr_t;
using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using isz = std::make_signed_t<std::size_t>;
using iptr = std::intptr_t;
export using i8 = std::int8_t;
export using i16 = std::int16_t;
export using i32 = std::int32_t;
export using i64 = std::int64_t;
export using isz = std::make_signed_t<std::size_t>;
export using iptr = std::intptr_t;
using f32 = float;
using f64 = double;
export using f32 = float;
export using f64 = double;
using Unit = std::monostate;
export namespace utils {
template <class... Args>
[[nodiscard]] auto Error(std::format_string<Args...> fmt, Args&&... args) -> std::unexpected<std::string> {
return std::unexpected(std::format(fmt, std::forward<Args>(args)...));
}
template <class T = void> using Result = std::expected<T, std::string>;
namespace utils {
template <typename... Types> struct Overloaded : Types... { template <typename... Types> struct Overloaded : Types... {
using Types::operator()...; using Types::operator()...;
}; };
template <typename... Types> Overloaded(Types...) -> Overloaded<Types...>; template <typename... Types> Overloaded(Types...) -> Overloaded<Types...>;
} }
}
template <class T> struct std::formatter<std::vector<T>> : std::formatter<std::string> { template <class T> struct std::formatter<std::vector<T>> : std::formatter<std::string> {
template <class FormatContext> auto format(std::vector<T> const& data, FormatContext& ctx) const { template <class FormatContext> auto format(std::vector<T> const& data, FormatContext& ctx) const {
std::string repr = "["; std::string repr = "[";
bool first = true; bool first = true;
for (auto& el : data) { for (auto& el : data) {
repr += std::format("{}{}",first ? "" : ", ", el);
repr += std::format("{}{}", first ? "" : ", ", el);
first = false; first = false;
} }
repr += "]"; repr += "]";

78
src/Node.cppm

@ -1,4 +1,5 @@
module; module;
#include <print>
#include <raylib.h> #include <raylib.h>
#include <string> #include <string>
#include <vector> #include <vector>
@ -10,12 +11,22 @@ export namespace ffmpegraph {
struct Node { struct Node {
virtual ~Node() = default; virtual ~Node() = default;
void Render();
i32 Width() const;
i32 Height() const;
void Render() {
i32 port_y = pos_y;
DrawRectangle(pos_x, pos_y, Width(), Height(), WHITE);
i32 new_width = min_port_width;
for (auto* port : ports) {
new_width = std::max(port->Render(pos_x, port_y, width), new_width);
port_y += 20;
}
width = new_width;
}
i32 Width() const { return width; }
i32 Height() const { return 20 * i32(ports.size()); }
i32 pos_x{}, pos_y{}; i32 pos_x{}, pos_y{};
std::vector<Port*> ports; std::vector<Port*> ports;
virtual void Run() = 0;
virtual Result<> Run() = 0;
virtual void OnDisconnect() {}
protected: protected:
i32 width = min_port_width; i32 width = min_port_width;
}; };
@ -24,7 +35,15 @@ struct InputNode : Node {
Label label; Label label;
InputPort filename_port; InputPort filename_port;
OutputPort output_port; OutputPort output_port;
void Run() override;
Result<> Run() override;
};
struct OutputNode : Node {
OutputNode();
Result<> Run() override;
Label label;
InputPort in_data;
InputPort in_filename;
}; };
struct StringConstantNode : Node { struct StringConstantNode : Node {
@ -32,48 +51,19 @@ struct StringConstantNode : Node {
Label label; Label label;
StringUserInput in; StringUserInput in;
OutputPort out; OutputPort out;
void Run() override;
Result<> Run() override;
}; };
}
module :private;
namespace ffmpegraph {
i32 Node::Width() const { return width; }
i32 Node::Height() const { return 20 * i32(ports.size()); }
InputNode::InputNode()
: label("Input", this), filename_port("filename", this, PortType::STRING), output_port("output", this, PortType::STRING) {
ports.emplace_back(&label);
ports.emplace_back(&filename_port);
ports.emplace_back(&output_port);
}
void InputNode::Run() {
output_port.SetValue(filename_port.GetValue());
}
StringConstantNode::StringConstantNode() : label("String Constant", this), in("Text", this), out("Output", this, PortType::STRING) {
ports.emplace_back(&label);
ports.emplace_back(&in);
ports.emplace_back(&out);
}
struct StreamSpecConstantNode : Node {
StreamSpecConstantNode();
Result<> Run() override;
Label label;
StringUserInput in;
OutputPort out;
};
void StringConstantNode::Run() {
out.SetValue(in.data);
}
struct SplitterNode : Node {
};
void Node::Render() {
i32 port_y = pos_y;
DrawRectangle(pos_x, pos_y, Width(), Height(), WHITE);
i32 new_width = min_port_width;
for (auto* port : ports) {
new_width = std::max(port->Render(pos_x, port_y, width), new_width);
port_y += 20;
}
width = new_width;
} }
} // namespace ffmpegraph

21
src/Nodes/InputNode.cpp

@ -0,0 +1,21 @@
module;
#include <format>
#include <variant>
module Node;
using namespace ffmpegraph;
InputNode::InputNode()
: label("Input File", this)
, filename_port("Filename", this, PortTypeE::STRING)
, output_port("Out", this, PortTypeE::STRING) {
ports.push_back(&label);
ports.push_back(&filename_port);
ports.push_back(&output_port);
}
Result<> InputNode::Run() {
auto data = std::get_if<std::string>(&filename_port.GetValue());
if (not data or data->empty()) return Error("File Input needs a filename ");
output_port.SetValue(std::format("-i {}", *data));
return {};
}

20
src/Nodes/OutputNode.cpp

@ -0,0 +1,20 @@
module;
#include <variant>
module Node;
using namespace ffmpegraph;
OutputNode::OutputNode()
: label("File Output", this)
, in_data("Data", this, PortTypeE::STRING)
, in_filename("File", this, PortTypeE::STRING) {
ports.push_back(&label);
ports.push_back(&in_data);
ports.push_back(&in_filename);
}
Result<> OutputNode::Run() {
auto data = std::get_if<std::string>(&in_data.GetValue());
if (not data) return {};
auto fname = std::get_if<std::string>(&in_filename.GetValue());
auto path = fname and not fname->empty() ? std::string_view{*fname} : "/tmp/ffmpegraph_out";
return Error("ffmpeg {} {}", *data, path);
}

34
src/Nodes/StreamSpecConstantNode.cpp

@ -0,0 +1,34 @@
module Node;
using namespace ffmpegraph;
StreamSpecConstantNode::StreamSpecConstantNode()
: label("Stream Specifier Constant", this), in("Stream Specifier", this), out("Out", this, PortTypeE::STREAM_SPEC) {
ports.push_back(&label);
ports.push_back(&in);
ports.push_back(&out);
}
Result<> StreamSpecConstantNode::Run() {
// TODO validate that
// <stream_specifier> →
// <index>
// [vVasdt](:<stream_specifier>)?
// g:<group_specifier>(:<stream_specifier>)?
// p:<id>(:<stream_specifier>)?
// #<id>
// i:<id>
// m:<id>(:<id>)?
// disp:<dispositions>(:<stream_specifier>)?
// u
// <index> → [1-9][0-9]*
// <group_specifier> →
// <index>
// #<id>
// i:<id>
// <id> → [a-zA-Z0-9_-]+
// <dispositions> →
// <id>
// <dispositions>+<id>
out.SetValue(in.data);
return {};
}

13
src/Nodes/StringConstantNode.cpp

@ -0,0 +1,13 @@
module Node;
using namespace ffmpegraph;
StringConstantNode::StringConstantNode()
: label("String Constant", this), in("Text", this), out("Out", this, PortTypeE::STRING) {
ports.push_back(&label);
ports.push_back(&in);
ports.push_back(&out);
}
Result<> StringConstantNode::Run() {
out.SetValue(in.data);
return {};
}

83
src/Port.cppm

@ -1,21 +1,45 @@
module; module;
#include <format> #include <format>
#include <limits> #include <limits>
#include <raylib.h>
#include <string> #include <string>
#include <variant> #include <variant>
export module Node:Port; export module Node:Port;
import Base; import Base;
using namespace std::literals; using namespace std::literals;
export namespace ffmpegraph { export namespace ffmpegraph {
struct Node; struct Node;
enum struct PortType {
enum struct PortTypeE {
GENERIC,
INT, INT,
STRING, STRING,
STREAM_SPEC
};
struct PortType {
PortType(PortTypeE t): t(t) {}
friend bool operator==(PortType a, PortType b) = default;
friend struct std::formatter<PortType>;
protected:
PortTypeE t;
}; };
}
template <> struct std::formatter<ffmpegraph::PortType> : std::formatter<std::string_view> {
template <class FormatContext> auto format(ffmpegraph::PortType data, FormatContext& ctx) const {
auto s = [data] -> string_view {
switch (data.t) {
case ffmpegraph::PortTypeE::INT: return "integer";
case ffmpegraph::PortTypeE::STREAM_SPEC: return "stream spec";
case ffmpegraph::PortTypeE::STRING: return "string";
case ffmpegraph::PortTypeE::GENERIC: return "any";
default: return "unknown_type";
}
}();
return formatter<std::string_view>::format(s, ctx);
}
};
export namespace ffmpegraph {
constexpr i32 min_port_width = 100; constexpr i32 min_port_width = 100;
using PortData = std::variant<i32, std::string, std::monostate>; using PortData = std::variant<i32, std::string, std::monostate>;
} }
@ -75,7 +99,7 @@ public:
struct Label : Port { struct Label : Port {
i32 Render(i32 x, i32 y, i32 width) override; i32 Render(i32 x, i32 y, i32 width) override;
explicit Label(std::string name, Node* owner) : Port(std::move(name), owner) {
Label(std::string name, Node* owner) : Port(std::move(name), owner) {
pos_x = pos_y = std::numeric_limits<i32>::min(); pos_x = pos_y = std::numeric_limits<i32>::min();
} }
}; };
@ -87,55 +111,10 @@ struct StringUserInput : Port {
i32 Render(i32 x, i32 y, i32 width) override; i32 Render(i32 x, i32 y, i32 width) override;
std::string data; std::string data;
}; };
};
PortData const& InputPort::GetValue() const { return value; }
namespace ffmpegraph{
void OutputPort::SetValue(PortData value) {
if (connected) { connected->value = std::move(value); }
}
bool OutputPort::TryConnect(InputPort& ip) {
if (ip.type == type) {
connected = &ip;
return true;
}
return false;
} }
void OutputPort::Disconnect() { connected = nullptr; }
i32 OutputPort::Render(i32 x, i32 y, i32 width) {
pos_x = x + width;
pos_y = y + 10;
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(name.c_str(), x + 5, y + 5, 10, RED);
DrawCircle(x + width, y + 10, 2.5f, BLACK);
if (connected) { DrawLine(pos_x, pos_y, connected->pos_x, connected->pos_y, BLUE); }
return 10 + MeasureText(name.c_str(), 10);
}
i32 InputPort::Render(i32 x, i32 y, i32 width) {
pos_x = x;
pos_y = y + 10;
auto formatted = std::format("{}: {}", name, value);
auto new_width = 10 + MeasureText(formatted.c_str(), 10);
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(formatted.c_str(), x + 5, y + 5, 10, BLUE);
DrawCircle(x, y + 10, 2.5f, BLACK);
return new_width;
}
i32 Label::Render(i32 x, i32 y, i32 width) {
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(name.c_str(), x + 5, y + 5, 10, BLACK);
return 10 + MeasureText(name.c_str(), 10);
}
i32 StringUserInput::Render(i32 x, i32 y, i32 width) {
DrawRectangleLines(x, y, width, 20, BLACK);
if (data.empty()) {
DrawText(name.c_str(), x + 5, y + 5, 10, GRAY);
return 10 + MeasureText(name.c_str(), 10);
} else {
DrawText(data.c_str(), x + 5, y + 5, 10, BLACK);
return 10 + MeasureText(data.c_str(), 10);
}
}
} // namespace ffmpegraph

21
src/Ports/InputPort.cpp

@ -0,0 +1,21 @@
module;
#include <raylib.h>
#include <format>
module Node;
using namespace ffmpegraph;
PortData const& InputPort::GetValue() const { return value; }
i32 InputPort::Render(i32 x, i32 y, i32 width) {
pos_x = x;
pos_y = y + 10;
auto formatted = std::format("{}: {}", name, type);
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(formatted.c_str(), x + 5, y + 5, 10, BLUE);
DrawCircle(x, y + 10, 2.5f, BLACK);
return 10 + MeasureText(formatted.c_str(), 10);
}
i32 Label::Render(i32 x, i32 y, i32 width) {
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(name.c_str(), x + 5, y + 5, 10, BLACK);
return 10 + MeasureText(name.c_str(), 10);
}

32
src/Ports/OutputPort.cpp

@ -0,0 +1,32 @@
module;
#include <raylib.h>
#include <format>
module Node;
using namespace ffmpegraph;
void OutputPort::SetValue(PortData value) {
if (connected) { connected->value = std::move(value); }
}
bool OutputPort::TryConnect(InputPort& ip) {
if (ip.type == type) {
connected = &ip;
return true;
}
return false;
}
void OutputPort::Disconnect() {
if (connected) {
connected->owner->OnDisconnect();
}
connected = nullptr;
}
i32 OutputPort::Render(i32 x, i32 y, i32 width) {
pos_x = x + width;
pos_y = y + 10;
auto formatted = std::format("{}: {}", name, type);
DrawRectangleLines(x, y, width, 20, BLACK);
DrawText(formatted.c_str(), x + 5, y + 5, 10, RED);
if (type != PortTypeE::GENERIC) DrawCircle(x + width, y + 10, 2.5f, BLACK);
if (connected) { DrawLine(pos_x, pos_y, connected->pos_x, connected->pos_y, BLUE); }
return 10 + MeasureText(formatted.c_str(), 10);
}

15
src/Ports/StringUserInput.cpp

@ -0,0 +1,15 @@
module;
#include <raylib.h>
module Node;
using namespace ffmpegraph;
i32 StringUserInput::Render(i32 x, i32 y, i32 width) {
DrawRectangleLines(x, y, width, 20, BLACK);
if (data.empty()) {
DrawText(name.c_str(), x + 5, y + 5, 10, GRAY);
return 10 + MeasureText(name.c_str(), 10);
} else {
DrawText(data.c_str(), x + 5, y + 5, 10, BLACK);
return 10 + MeasureText(data.c_str(), 10);
}
}

20
src/macros.hpp

@ -0,0 +1,20 @@
#ifndef MACROS_HPP
#define MACROS_HPP
#include <string>
#include <expected>
#include <type_traits>
#include <utility>
#define Try(x, ...) \
({ \
auto _res = x; \
if (not _res) { \
return std::unexpected(__VA_OPT__([&]([[maybe_unused]] std::string $) { return __VA_ARGS__; } \
) __VA_OPT__(LIBBASE_LPAREN_) std::move(_res.error()) __VA_OPT__(LIBBASE_RPAREN_)); \
} \
using NonRef = std::remove_reference_t<decltype(*_res)>; \
static_cast<std::add_rvalue_reference_t<NonRef>>(*_res); \
})
#endif // MACROS_HPP

2
src/main.cpp

@ -1,5 +1,4 @@
#include <clopts.hh> #include <clopts.hh>
#include <print>
import Base; import Base;
import Application; import Application;
using namespace ffmpegraph; using namespace ffmpegraph;
@ -15,6 +14,7 @@ namespace detail {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
auto opts = detail::options::parse(argc, argv); auto opts = detail::options::parse(argc, argv);
auto ffmpeg_path = opts.get_or<"--ffmpeg-path">("ffmpeg");
Application app("FFMpeGraph"); Application app("FFMpeGraph");
app.Run(); app.Run();
return 0; return 0;

Loading…
Cancel
Save