#include "tools/jotpluggler/app.h" #include "tools/jotpluggler/common.h" #include #include #include #include #include "json11/json11.hpp" namespace fs = std::filesystem; namespace { std::string curve_color_hex(const std::array &color) { std::ostringstream hex; hex << "#" << std::hex << std::setfill('0') << std::setw(2) << static_cast(color[0]) << std::setw(2) << static_cast(color[1]) << std::setw(2) << static_cast(color[2]); return hex.str(); } json11::Json curve_to_json(const Curve &curve) { json11::Json::object obj = { {"name", curve.name}, {"color", curve_color_hex(curve.color)}, }; if (curve.derivative) { obj["transform"] = "derivative"; if (curve.derivative_dt > 0.0) { obj["derivative_dt"] = curve.derivative_dt; } } else if (std::abs(curve.value_scale - 1.0) > 1.0e-9 || std::abs(curve.value_offset) > 1.0e-9) { obj["transform"] = "scale"; obj["scale"] = curve.value_scale; obj["offset"] = curve.value_offset; } if (curve.custom_python.has_value()) { json11::Json::array additional_sources; for (const std::string &path : curve.custom_python->additional_sources) { additional_sources.push_back(path); } obj["custom_python"] = json11::Json::object{ {"linked_source", curve.custom_python->linked_source}, {"additional_sources", additional_sources}, {"globals_code", curve.custom_python->globals_code}, {"function_code", curve.custom_python->function_code}, }; } return obj; } json11::Json workspace_node_to_json(const WorkspaceNode &node, const WorkspaceTab &tab) { if (node.is_pane) { if (node.pane_index < 0 || node.pane_index >= static_cast(tab.panes.size())) { return nullptr; } const Pane &pane = tab.panes[static_cast(node.pane_index)]; json11::Json::object obj = { {"title", pane.title.empty() ? std::string("...") : pane.title}, }; if (pane.kind == PaneKind::Map) { obj["kind"] = "map"; } else if (pane.kind == PaneKind::Camera) { obj["kind"] = "camera"; obj["camera_view"] = camera_view_spec(pane.camera_view).layout_name; } if (pane.range.valid) { obj["range"] = json11::Json::object{ {"left", pane.range.left}, {"right", pane.range.right}, {"top", pane.range.top}, {"bottom", pane.range.bottom}, }; } if (pane.range.has_y_limit_min || pane.range.has_y_limit_max) { json11::Json::object limits; if (pane.range.has_y_limit_min) { limits["min"] = pane.range.y_limit_min; } if (pane.range.has_y_limit_max) { limits["max"] = pane.range.y_limit_max; } obj["y_limits"] = limits; } json11::Json::array curves; for (const Curve &curve : pane.curves) { if (!curve.runtime_only) { curves.push_back(curve_to_json(curve)); } } obj["curves"] = curves; return obj; } if (node.children.empty()) return nullptr; json11::Json::array sizes; for (size_t i = 0; i < node.children.size(); ++i) { sizes.push_back(i < node.sizes.size() ? static_cast(node.sizes[i]) : 1.0 / static_cast(node.children.size())); } json11::Json::array children; for (const WorkspaceNode &child : node.children) { children.push_back(workspace_node_to_json(child, tab)); } return json11::Json::object{ {"split", node.orientation == SplitOrientation::Horizontal ? "horizontal" : "vertical"}, {"sizes", sizes}, {"children", children}, }; } } // namespace void save_layout_json(const SketchLayout &layout, const fs::path &path) { ensure_parent_dir(path); json11::Json::array tabs; for (const WorkspaceTab &tab : layout.tabs) { tabs.push_back(json11::Json::object{ {"name", tab.tab_name}, {"root", workspace_node_to_json(tab.root, tab)}, }); } const json11::Json root = json11::Json::object{ {"current_tab_index", std::clamp(layout.current_tab_index, 0, std::max(0, static_cast(layout.tabs.size()) - 1))}, {"tabs", tabs}, }; write_file_or_throw(path, root.dump() + "\n"); }