simplify code of config UI
This commit is contained in:
parent
69690f2e29
commit
245884fae7
@ -91,8 +91,6 @@ target_sources(nf7
|
||||
common/gui_file.hh
|
||||
common/gui_file.cc
|
||||
common/gui_node.hh
|
||||
common/gui_popup.hh
|
||||
common/gui_popup.cc
|
||||
common/gui_timeline.hh
|
||||
common/gui_timeline.cc
|
||||
common/gui_value.hh
|
||||
@ -127,7 +125,6 @@ target_sources(nf7
|
||||
common/thread.hh
|
||||
common/timed_queue.hh
|
||||
common/util_algorithm.hh
|
||||
common/util_string.hh
|
||||
common/value.hh
|
||||
common/yas_enum.hh
|
||||
common/yas_imgui.hh
|
||||
|
@ -1,48 +0,0 @@
|
||||
#include "common/gui_popup.hh"
|
||||
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/util_algorithm.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
void IOSocketListPopup::Update() noexcept {
|
||||
if (Popup::Begin()) {
|
||||
ImGui::InputTextMultiline("inputs", &is_);
|
||||
ImGui::InputTextMultiline("outputs", &os_);
|
||||
|
||||
const auto iterm = nf7::util::SplitAndValidate(is_, nf7::File::Path::ValidateTerm);
|
||||
const auto oterm = nf7::util::SplitAndValidate(os_, nf7::File::Path::ValidateTerm);
|
||||
|
||||
if (iterm) {
|
||||
ImGui::Bullet();
|
||||
ImGui::Text("invalid input name: %.*s", (int) iterm->size(), iterm->data());
|
||||
}
|
||||
if (oterm) {
|
||||
ImGui::Bullet();
|
||||
ImGui::Text("invalid output name: %.*s", (int) oterm->size(), oterm->data());
|
||||
}
|
||||
ImGui::Bullet();
|
||||
ImGui::TextDisabled("duplicated names are removed automatically");
|
||||
|
||||
if (!iterm && !oterm && ImGui::Button("ok")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
std::vector<std::string> iv, ov;
|
||||
|
||||
nf7::util::SplitAndAppend(iv, is_);
|
||||
nf7::util::Uniq(iv);
|
||||
|
||||
nf7::util::SplitAndAppend(ov, os_);
|
||||
nf7::util::Uniq(ov);
|
||||
|
||||
onSubmit(std::move(iv), std::move(ov));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,67 +0,0 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "common/file_base.hh"
|
||||
#include "common/util_string.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
class Popup {
|
||||
public:
|
||||
Popup(const char* name, ImGuiWindowFlags flags = 0) noexcept :
|
||||
name_(name), flags_(flags) {
|
||||
}
|
||||
|
||||
void Open(ImGuiPopupFlags flags = 0) noexcept {
|
||||
open_flags_ = flags;
|
||||
}
|
||||
|
||||
bool Begin() noexcept {
|
||||
if (auto flags = std::exchange(open_flags_, std::nullopt)) {
|
||||
ImGui::OpenPopup(name_, *flags);
|
||||
}
|
||||
return ImGui::BeginPopup(name_, flags_);
|
||||
}
|
||||
|
||||
const char* name() const noexcept { return name_; }
|
||||
|
||||
private:
|
||||
const char* name_;
|
||||
ImGuiWindowFlags flags_;
|
||||
|
||||
std::optional<ImGuiPopupFlags> open_flags_;
|
||||
};
|
||||
|
||||
|
||||
class IOSocketListPopup final :
|
||||
public nf7::FileBase::Feature, private Popup {
|
||||
public:
|
||||
IOSocketListPopup(const char* name = "IOSocketListPopup",
|
||||
ImGuiWindowFlags flags = 0) noexcept :
|
||||
Popup(name, flags) {
|
||||
}
|
||||
|
||||
void Open(std::span<const std::string> iv,
|
||||
std::span<const std::string> ov) noexcept {
|
||||
is_ = "";
|
||||
nf7::util::JoinAndAppend(is_, iv);
|
||||
os_ = "";
|
||||
nf7::util::JoinAndAppend(os_, ov);
|
||||
Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
std::function<void(std::vector<std::string>&&, std::vector<std::string>&&)> onSubmit =
|
||||
[](auto&&, auto&&){};
|
||||
|
||||
private:
|
||||
std::string is_, os_;
|
||||
};
|
||||
|
||||
} // namespace nf7::gui
|
@ -6,12 +6,15 @@
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Memento : public File::Interface {
|
||||
public:
|
||||
class Tag;
|
||||
class RestoreCommand;
|
||||
class CorruptException;
|
||||
|
||||
Memento() = default;
|
||||
@ -43,6 +46,33 @@ class Memento::Tag {
|
||||
Id id_;
|
||||
};
|
||||
|
||||
class Memento::RestoreCommand final : public nf7::History::Command {
|
||||
public:
|
||||
RestoreCommand() = delete;
|
||||
RestoreCommand(Memento& mem,
|
||||
const std::shared_ptr<Tag>& prev,
|
||||
const std::shared_ptr<Tag>& next) noexcept :
|
||||
mem_(mem), prev_(prev), next_(next) {
|
||||
}
|
||||
RestoreCommand(const RestoreCommand&) = delete;
|
||||
RestoreCommand(RestoreCommand&&) = delete;
|
||||
RestoreCommand& operator=(const RestoreCommand&) = delete;
|
||||
RestoreCommand& operator=(RestoreCommand&&) = delete;
|
||||
|
||||
void Apply() override { Exec(); }
|
||||
void Revert() override { Exec(); }
|
||||
|
||||
private:
|
||||
Memento& mem_;
|
||||
std::shared_ptr<Tag> prev_;
|
||||
std::shared_ptr<Tag> next_;
|
||||
|
||||
void Exec() noexcept {
|
||||
mem_.Restore(next_);
|
||||
std::swap(prev_, next_);
|
||||
}
|
||||
};
|
||||
|
||||
class Memento::CorruptException : public Exception {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
|
@ -29,6 +29,17 @@ class Node : public File::Interface {
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
static void ValidateSockets(std::span<const std::string> v) {
|
||||
for (auto itr = v.begin(); itr < v.end(); ++itr) {
|
||||
if (v.end() != std::find(itr+1, v.end(), *itr)) {
|
||||
throw nf7::Exception {"name duplication: "+*itr};
|
||||
}
|
||||
}
|
||||
for (auto& s : v) {
|
||||
nf7::File::Path::ValidateTerm(s);
|
||||
}
|
||||
}
|
||||
|
||||
Node(Flags f) noexcept : flags_(f) { }
|
||||
Node(const Node&) = default;
|
||||
Node(Node&&) = default;
|
||||
|
@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace nf7::util {
|
||||
|
||||
inline std::string_view Trim(
|
||||
std::string_view str,
|
||||
const std::function<bool(char)>& func = [](auto c) { return std::isspace(c); }) noexcept {
|
||||
while (!str.empty() && func(str.front())) {
|
||||
str.remove_prefix(1);
|
||||
}
|
||||
while (!str.empty() && func(str.back())) {
|
||||
str.remove_suffix(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
|
||||
std::string_view ret;
|
||||
while (ret.empty() && i < str.size()) {
|
||||
auto j = str.find(c, i);
|
||||
if (j == std::string::npos) j = str.size();
|
||||
|
||||
ret = str.substr(i, j-i);
|
||||
i = j+1;
|
||||
}
|
||||
if (ret.empty()) return std::nullopt;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void SplitAndAppend(std::vector<std::string>& dst, std::string_view src, char c = '\n') noexcept {
|
||||
size_t itr = 0;
|
||||
while (auto term = IterateTerms(src, c, itr)) {
|
||||
dst.emplace_back(*term);
|
||||
}
|
||||
}
|
||||
inline void JoinAndAppend(std::string& dst, std::span<const std::string> src, char c = '\n') noexcept {
|
||||
for (auto& str : src) {
|
||||
dst += str;
|
||||
dst += c;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::optional<std::string_view> SplitAndValidate(
|
||||
std::string_view v,
|
||||
const std::function<bool(std::string_view)> validator, char c = '\n') noexcept {
|
||||
size_t itr = 0;
|
||||
while (auto term = IterateTerms(v, c, itr)) {
|
||||
if (validator(*term)) {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
inline std::optional<std::string_view> SplitAndValidate(
|
||||
std::string_view v,
|
||||
const std::function<void(std::string_view)> validator, char c = '\n') noexcept {
|
||||
size_t itr = 0;
|
||||
while (auto term = IterateTerms(v, c, itr)) {
|
||||
try {
|
||||
validator(*term);
|
||||
} catch (nf7::Exception&) {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace nf7::util
|
@ -60,8 +60,6 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
|
||||
|
||||
private:
|
||||
std::shared_ptr<Queue> q_;
|
||||
|
||||
const char* popup_ = nullptr;
|
||||
};
|
||||
|
||||
class AudioContext::Queue final : public nf7::audio::Queue,
|
||||
|
@ -33,7 +33,6 @@
|
||||
#include "common/memento.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/util_algorithm.hh"
|
||||
|
||||
|
||||
using namespace std::literals;
|
||||
@ -79,8 +78,8 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
|
||||
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
nf7::util::Uniq(mem_->inputs);
|
||||
nf7::util::Uniq(mem_->outputs);
|
||||
nf7::Node::ValidateSockets(mem_->inputs);
|
||||
nf7::Node::ValidateSockets(mem_->outputs);
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
@ -295,20 +294,16 @@ std::string Node::Data::Stringify() const noexcept {
|
||||
void Node::Data::Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
auto new_inputs = yaml["inputs"] .as<std::vector<std::string>>();
|
||||
auto new_outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||
auto new_script = yaml["script"].as<std::string>();
|
||||
|
||||
if (nf7::util::Uniq(new_inputs) > 0) {
|
||||
throw nf7::Exception {"duplicated inputs"};
|
||||
}
|
||||
if (nf7::util::Uniq(new_outputs) > 0) {
|
||||
throw nf7::Exception {"duplicated outputs"};
|
||||
}
|
||||
Data d;
|
||||
d.inputs = yaml["inputs"].as<std::vector<std::string>>();
|
||||
d.outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||
d.script = yaml["script"].as<std::string>();
|
||||
|
||||
inputs = std::move(new_inputs);
|
||||
outputs = std::move(new_outputs);
|
||||
script = std::move(new_script);
|
||||
nf7::Node::ValidateSockets(d.inputs);
|
||||
nf7::Node::ValidateSockets(d.outputs);
|
||||
|
||||
*this = std::move(d);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
|
@ -11,7 +11,11 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <ImNodes.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
#include <yas/types/std/vector.hpp>
|
||||
@ -24,10 +28,10 @@
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_config.hh"
|
||||
#include "common/gui_context.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_node.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/memento.hh"
|
||||
@ -36,7 +40,6 @@
|
||||
#include "common/node_link_store.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/squashed_history.hh"
|
||||
#include "common/util_algorithm.hh"
|
||||
#include "common/yas_imgui.hh"
|
||||
#include "common/yas_imnodes.hh"
|
||||
#include "common/yas_nf7.hh"
|
||||
@ -64,8 +67,6 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
class Lambda;
|
||||
class Editor;
|
||||
|
||||
class SocketSwapCommand;
|
||||
|
||||
// special Node types
|
||||
class Initiator;
|
||||
class Terminal;
|
||||
@ -73,22 +74,54 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
using ItemId = uint64_t;
|
||||
using ItemList = std::vector<std::unique_ptr<Item>>;
|
||||
|
||||
struct Data {
|
||||
std::vector<std::string> inputs, outputs;
|
||||
|
||||
void serialize(auto& ar) {
|
||||
ar(inputs, outputs);
|
||||
}
|
||||
|
||||
std::string Stringify() noexcept {
|
||||
YAML::Emitter st;
|
||||
st << YAML::BeginMap;
|
||||
st << YAML::Key << "inputs";
|
||||
st << YAML::Value << inputs;
|
||||
st << YAML::Key << "outputs";
|
||||
st << YAML::Value << outputs;
|
||||
st << YAML::EndMap;
|
||||
return {st.c_str(), st.size()};
|
||||
}
|
||||
void Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
|
||||
Data d;
|
||||
d.inputs = yaml["inputs"].as<std::vector<std::string>>();
|
||||
d.outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||
|
||||
nf7::Node::ValidateSockets(d.inputs);
|
||||
nf7::Node::ValidateSockets(d.outputs);
|
||||
|
||||
*this = std::move(d);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
};
|
||||
|
||||
Network(nf7::Env& env,
|
||||
const gui::Window* win = nullptr,
|
||||
ItemList&& items = {},
|
||||
nf7::NodeLinkStore&& links = {}) :
|
||||
nf7::FileBase(kType, env, {&add_popup_, &socket_popup_}),
|
||||
nf7::NodeLinkStore&& links = {},
|
||||
Data&& d = {}) :
|
||||
nf7::FileBase(kType, env),
|
||||
nf7::DirItem(nf7::DirItem::kMenu |
|
||||
nf7::DirItem::kTooltip |
|
||||
nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kCustomNode),
|
||||
nf7::Node(nf7::Node::kNone),
|
||||
life_(*this),
|
||||
win_(*this, "Editor Node/Network", win),
|
||||
items_(std::move(items)), links_(std::move(links)),
|
||||
add_popup_(*this) {
|
||||
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
|
||||
ExecSwapSocket(std::move(i), std::move(o));
|
||||
};
|
||||
mem_(std::move(d), *this) {
|
||||
Sanitize();
|
||||
}
|
||||
~Network() noexcept {
|
||||
@ -96,11 +129,11 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
}
|
||||
|
||||
Network(nf7::Deserializer& ar) : Network(ar.env()) {
|
||||
ar(win_, links_, canvas_, inputs_, outputs_, items_);
|
||||
ar(win_, links_, canvas_, mem_.data(), items_);
|
||||
Sanitize();
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(win_, links_, canvas_, inputs_, outputs_, items_);
|
||||
ar(win_, links_, canvas_, mem_.data(), items_);
|
||||
}
|
||||
std::unique_ptr<File> Clone(nf7::Env& env) const noexcept override {
|
||||
ItemList items;
|
||||
@ -121,16 +154,15 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
void UpdateTooltip() noexcept override;
|
||||
void UpdateWidget() noexcept override;
|
||||
void UpdateMenu(nf7::Node::Editor&) noexcept override { UpdateMenu(); }
|
||||
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
return inputs_;
|
||||
return mem_->inputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return outputs_;
|
||||
return mem_->outputs;
|
||||
}
|
||||
|
||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
@ -150,37 +182,15 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
std::shared_ptr<Network::Lambda> lambda_;
|
||||
std::vector<std::weak_ptr<Network::Lambda>> lambdas_running_;
|
||||
|
||||
ImVec2 canvas_pos_;
|
||||
|
||||
// persistent params
|
||||
gui::Window win_;
|
||||
std::vector<std::unique_ptr<Item>> items_;
|
||||
NodeLinkStore links_;
|
||||
ImNodes::CanvasState canvas_;
|
||||
|
||||
std::vector<std::string> inputs_, outputs_;
|
||||
|
||||
// GUI popup
|
||||
nf7::gui::IOSocketListPopup socket_popup_;
|
||||
|
||||
class AddPopup final : public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
static bool TypeFilter(const nf7::File::TypeInfo& t) noexcept {
|
||||
return
|
||||
t.flags().contains("nf7::Node") ||
|
||||
t.name().find("Node/Network/") == 0;
|
||||
}
|
||||
|
||||
AddPopup(Network& owner) noexcept :
|
||||
nf7::gui::Popup("AddPopup"), owner_(&owner), factory_(owner, TypeFilter) {
|
||||
}
|
||||
|
||||
void Open(const ImVec2& pos) noexcept;
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
Network* const owner_;
|
||||
nf7::gui::FileFactory factory_;
|
||||
ImVec2 pos_;
|
||||
} add_popup_;
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
|
||||
// initialization
|
||||
@ -199,11 +209,7 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
[this]() { history_.ReDo(); Touch(); });
|
||||
}
|
||||
|
||||
// IO socket operation
|
||||
void ExecSwapSocket(std::vector<std::string>&&, std::vector<std::string>&&) noexcept;
|
||||
|
||||
// item operation
|
||||
void ExecAddItem(std::unique_ptr<Item>&&) noexcept;
|
||||
void ExecAddItem(std::unique_ptr<Item>&& item, const ImVec2& pos) noexcept;
|
||||
void ExecRemoveItem(ItemId) noexcept;
|
||||
|
||||
@ -235,6 +241,14 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
|
||||
}
|
||||
return *itr->second;
|
||||
}
|
||||
|
||||
// gui
|
||||
void ItemAdder(const ImVec2&) noexcept;
|
||||
void Config() noexcept;
|
||||
|
||||
ImVec2 GetCanvasPosFromScreenPos(const ImVec2& pos) noexcept {
|
||||
return pos - canvas_pos_ - canvas_.Offset/canvas_.Zoom;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -564,36 +578,6 @@ class Network::Editor final : public nf7::Node::Editor {
|
||||
};
|
||||
|
||||
|
||||
// A command that add or remove a Node socket.
|
||||
class Network::SocketSwapCommand final : public nf7::History::Command {
|
||||
public:
|
||||
struct Pair {
|
||||
std::vector<std::string> in, out;
|
||||
};
|
||||
SocketSwapCommand(Network& owner, Pair&& p) noexcept :
|
||||
owner_(&owner), pair_(std::move(p)) {
|
||||
}
|
||||
|
||||
void Apply() noexcept override { Exec(); }
|
||||
void Revert() noexcept override { Exec(); }
|
||||
|
||||
private:
|
||||
Network* const owner_;
|
||||
Pair pair_;
|
||||
|
||||
void Exec() noexcept {
|
||||
std::swap(owner_->inputs_, pair_.in);
|
||||
std::swap(owner_->outputs_, pair_.out);
|
||||
}
|
||||
};
|
||||
void Network::ExecSwapSocket(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept {
|
||||
auto cmd = std::make_unique<SocketSwapCommand>(
|
||||
*this, SocketSwapCommand::Pair {std::move(i), std::move(o)});
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this);
|
||||
history_.Add(std::move(cmd)).ExecApply(ctx);
|
||||
}
|
||||
|
||||
|
||||
// A command that add or remove a Node.
|
||||
class Network::Item::SwapCommand final : public nf7::History::Command {
|
||||
public:
|
||||
@ -636,11 +620,6 @@ class Network::Item::SwapCommand final : public nf7::History::Command {
|
||||
}
|
||||
}
|
||||
};
|
||||
void Network::ExecAddItem(std::unique_ptr<Network::Item>&& item) noexcept {
|
||||
history_.
|
||||
Add(std::make_unique<Network::Item::SwapCommand>(*this, std::move(item))).
|
||||
ExecApply(std::make_shared<nf7::GenericContext>(*this, "adding new item"));
|
||||
}
|
||||
void Network::ExecRemoveItem(Network::ItemId id) noexcept {
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "removing items");
|
||||
|
||||
@ -874,22 +853,8 @@ void Network::Sanitize() {
|
||||
}
|
||||
|
||||
// sanitize IO sockets
|
||||
nf7::util::Uniq(inputs_);
|
||||
for (auto itr = inputs_.begin(); itr < inputs_.end(); ++itr) {
|
||||
try {
|
||||
nf7::File::Path::ValidateTerm(*itr);
|
||||
} catch (nf7::Exception&) {
|
||||
inputs_.erase(itr);
|
||||
}
|
||||
}
|
||||
nf7::util::Uniq(outputs_);
|
||||
for (auto itr = outputs_.begin(); itr < outputs_.end(); ++itr) {
|
||||
try {
|
||||
nf7::File::Path::ValidateTerm(*itr);
|
||||
} catch (nf7::Exception&) {
|
||||
outputs_.erase(itr);
|
||||
}
|
||||
}
|
||||
nf7::Node::ValidateSockets(mem_->inputs);
|
||||
nf7::Node::ValidateSockets(mem_->outputs);
|
||||
|
||||
// remove expired links
|
||||
for (const auto& item : items_) {
|
||||
@ -1060,7 +1025,7 @@ void Network::Update() noexcept {
|
||||
|
||||
// ---- editor window / canvas
|
||||
if (ImGui::BeginChild("canvas", {0, 0}, false, ImGuiWindowFlags_NoMove)) {
|
||||
const auto canvas_pos = ImGui::GetCursorScreenPos();
|
||||
canvas_pos_ = ImGui::GetCursorScreenPos();
|
||||
ImNodes::BeginCanvas(&canvas_);
|
||||
|
||||
// update child nodes
|
||||
@ -1100,10 +1065,11 @@ void Network::Update() noexcept {
|
||||
ImGuiPopupFlags_MouseButtonRight |
|
||||
ImGuiPopupFlags_NoOpenOverExistingPopup;
|
||||
if (ImGui::BeginPopupContextWindow(nullptr, kFlags)) {
|
||||
const auto mouse = ImGui::GetMousePosOnOpeningCurrentPopup();
|
||||
if (ImGui::MenuItem("add")) {
|
||||
const auto pos = mouse - canvas_pos - canvas_.Offset/canvas_.Zoom;
|
||||
add_popup_.Open(pos);
|
||||
const auto pos =
|
||||
GetCanvasPosFromScreenPos(ImGui::GetMousePosOnOpeningCurrentPopup());
|
||||
if (ImGui::BeginMenu("add")) {
|
||||
ItemAdder(pos);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
|
||||
@ -1117,8 +1083,9 @@ void Network::Update() noexcept {
|
||||
canvas_.Zoom = 1.f;
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("I/O socket list")) {
|
||||
socket_popup_.Open(inputs_, outputs_);
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
Config();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@ -1136,8 +1103,9 @@ void Network::UpdateMenu() noexcept {
|
||||
if (ImGui::MenuItem("Editor", nullptr, &win_.shown()) && win_.shown()) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::MenuItem("I/O sockets")) {
|
||||
socket_popup_.Open(inputs_, outputs_);
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
Config();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
void Network::UpdateTooltip() noexcept {
|
||||
@ -1148,29 +1116,7 @@ void Network::UpdateWidget() noexcept {
|
||||
if (ImGui::Button("open editor")) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::Button("I/O sockets")) {
|
||||
socket_popup_.Open(inputs_, outputs_);
|
||||
}
|
||||
|
||||
socket_popup_.Update();
|
||||
}
|
||||
void Network::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
ImGui::TextUnformatted("Node/Network");
|
||||
|
||||
ImGui::BeginGroup();
|
||||
nf7::gui::NodeInputSockets(inputs_);
|
||||
ImGui::SameLine();
|
||||
nf7::gui::NodeOutputSockets(outputs_);
|
||||
ImGui::EndGroup();
|
||||
|
||||
if (ImGui::Button("open editor")) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::Button("I/O sockets")) {
|
||||
socket_popup_.Open(inputs_, outputs_);
|
||||
}
|
||||
|
||||
socket_popup_.Update();
|
||||
Config();
|
||||
}
|
||||
|
||||
void Network::Item::UpdateNode(Node::Editor& ed) noexcept {
|
||||
@ -1201,12 +1147,15 @@ void Network::Item::UpdateNode(Node::Editor& ed) noexcept {
|
||||
ImGuiPopupFlags_MouseButtonRight |
|
||||
ImGuiPopupFlags_NoOpenOverExistingPopup;
|
||||
if (ImGui::BeginPopupContextItem(nullptr, kFlags)) {
|
||||
const auto pos =
|
||||
owner_->GetCanvasPosFromScreenPos(
|
||||
ImGui::GetMousePosOnOpeningCurrentPopup());
|
||||
if (ImGui::MenuItem("remove")) {
|
||||
owner_->ExecRemoveItem(id_);
|
||||
}
|
||||
if (ImGui::MenuItem("clone")) {
|
||||
owner_->ExecAddItem(
|
||||
std::make_unique<Item>(owner_->next_++, file_->Clone(env())));
|
||||
std::make_unique<Item>(owner_->next_++, file_->Clone(env())), pos);
|
||||
}
|
||||
if (node_->flags() & nf7::Node::kMenu_DirItem) {
|
||||
ImGui::Separator();
|
||||
@ -1225,20 +1174,69 @@ void Network::Item::UpdateNode(Node::Editor& ed) noexcept {
|
||||
}
|
||||
|
||||
|
||||
void Network::AddPopup::Open(const ImVec2& pos) noexcept {
|
||||
nf7::gui::Popup::Open();
|
||||
pos_ = pos;
|
||||
}
|
||||
void Network::AddPopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
ImGui::TextUnformatted("Node/Network: adding new Node...");
|
||||
if (factory_.Update()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
owner_->ExecAddItem(
|
||||
std::make_unique<Item>(owner_->next_++, factory_.Create(owner_->env())),
|
||||
pos_);
|
||||
void Network::ItemAdder(const ImVec2& pos) noexcept {
|
||||
static const nf7::File::TypeInfo* type;
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
type = nullptr;
|
||||
}
|
||||
ImGui::TextUnformatted("Node/Network: adding new Node...");
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
bool exec = false;
|
||||
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
|
||||
for (auto& p : nf7::File::registry()) {
|
||||
const auto& t = *p.second;
|
||||
if (!t.flags().contains("nf7::Node")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr auto kFlags =
|
||||
ImGuiSelectableFlags_SpanAllColumns |
|
||||
ImGuiSelectableFlags_AllowItemOverlap;
|
||||
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
|
||||
type = &t;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
t.UpdateTooltip();
|
||||
ImGui::EndTooltip();
|
||||
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
exec = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
if (type == nullptr) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!valid);
|
||||
if (ImGui::Button("ok")) {
|
||||
exec = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (exec && valid) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
ExecAddItem(
|
||||
std::make_unique<Item>(next_++, type->Create(env())),
|
||||
pos);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::Config() noexcept {
|
||||
auto ptag = mem_.Save();
|
||||
nf7::gui::Config(mem_);
|
||||
auto tag = mem_.Save();
|
||||
|
||||
if (ptag != tag) {
|
||||
history_.Add(std::make_unique<nf7::Memento::RestoreCommand>(mem_, tag, ptag));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1274,12 +1272,12 @@ void Network::Terminal::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
ImGui::SetNextItemWidth(12*ImGui::GetFontSize());
|
||||
if (ImGui::BeginCombo("##name", name.c_str())) {
|
||||
ImGui::PushID("input");
|
||||
if (net->inputs_.size() > 0) {
|
||||
if (net->mem_->inputs.size() > 0) {
|
||||
ImGui::TextDisabled("inputs");
|
||||
} else {
|
||||
ImGui::TextDisabled("no input");
|
||||
}
|
||||
for (const auto& sock : net->inputs_) {
|
||||
for (const auto& sock : net->mem_->inputs) {
|
||||
if (ImGui::Selectable(sock.c_str())) {
|
||||
if (data().type != kInput || name != sock) {
|
||||
data() = Data {kInput, sock};
|
||||
@ -1290,12 +1288,12 @@ void Network::Terminal::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
ImGui::PopID();
|
||||
ImGui::Separator();
|
||||
ImGui::PushID("output");
|
||||
if (net->outputs_.size() > 0) {
|
||||
if (net->mem_->outputs.size() > 0) {
|
||||
ImGui::TextDisabled("outputs");
|
||||
} else {
|
||||
ImGui::TextDisabled("no output");
|
||||
}
|
||||
for (const auto& sock : net->outputs_) {
|
||||
for (const auto& sock : net->mem_->outputs) {
|
||||
if (ImGui::Selectable(sock.c_str())) {
|
||||
if (data().type != kOutput || name != sock) {
|
||||
data() = Data {kOutput, sock};
|
||||
@ -1333,7 +1331,8 @@ void Network::Terminal::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
}
|
||||
|
||||
if (auto net = owner()) {
|
||||
const auto& socks = data().type == kInput? net->inputs_: net->outputs_;
|
||||
const auto& socks =
|
||||
data().type == kInput? net->mem_->inputs: net->mem_->outputs;
|
||||
if (socks.end() == std::find(socks.begin(), socks.end(), data().name)) {
|
||||
ImGui::TextUnformatted("SOCKET MISSING X(");
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "common/generic_watcher.hh"
|
||||
#include "common/gui_dnd.hh"
|
||||
#include "common/gui_node.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/memento.hh"
|
||||
@ -58,34 +57,33 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
||||
};
|
||||
|
||||
Ref(nf7::Env& env, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {&config_popup_}),
|
||||
nf7::FileBase(kType, env),
|
||||
nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu),
|
||||
life_(*this),
|
||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||
mem_(std::move(data), *this),
|
||||
config_popup_(*this) {
|
||||
mem_(std::move(data), *this) {
|
||||
nf7::FileBase::Install(*log_);
|
||||
|
||||
mem_.onRestore = mem_.onCommit = [this]() { SetUpWatcher(); };
|
||||
}
|
||||
|
||||
Ref(nf7::Deserializer& ar) : Ref(ar.env()) {
|
||||
ar(data().target, data().inputs, data().outputs);
|
||||
ar(mem_->target, mem_->inputs, mem_->outputs);
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(data().target, data().inputs, data().outputs);
|
||||
ar(mem_->target, mem_->inputs, mem_->outputs);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<Ref>(env, Data {data()});
|
||||
return std::make_unique<Ref>(env, Data {mem_.data()});
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
return data().inputs;
|
||||
return mem_->inputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return data().outputs;
|
||||
return mem_->outputs;
|
||||
}
|
||||
|
||||
void Handle(const nf7::File::Event& ev) noexcept {
|
||||
@ -116,38 +114,19 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
||||
std::optional<nf7::GenericWatcher> watcher_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
const Data& data() const noexcept { return mem_.data(); }
|
||||
Data& data() noexcept { return mem_.data(); }
|
||||
|
||||
|
||||
// GUI popup
|
||||
class ConfigPopup final : public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
ConfigPopup(Ref& f) noexcept : nf7::gui::Popup("ConfigPopup"), f_(&f) {
|
||||
}
|
||||
|
||||
void Open() noexcept {
|
||||
path_ = f_->data().target.Stringify();
|
||||
nf7::gui::Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
Ref* const f_;
|
||||
std::string path_;
|
||||
} config_popup_;
|
||||
|
||||
// accessors
|
||||
nf7::File& target() const {
|
||||
auto& f = ResolveOrThrow(data().target);
|
||||
auto& f = ResolveOrThrow(mem_->target);
|
||||
if (&f == this) throw nf7::Exception("self reference");
|
||||
return f;
|
||||
}
|
||||
|
||||
// socket synchronization
|
||||
bool SyncQuiet() noexcept {
|
||||
auto& dsti = data().inputs;
|
||||
auto& dsto = data().outputs;
|
||||
auto& dsti = mem_->inputs;
|
||||
auto& dsto = mem_->outputs;
|
||||
|
||||
bool mod = false;
|
||||
try {
|
||||
@ -181,7 +160,7 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
||||
|
||||
// referencee operation
|
||||
void ExecChangeTarget(Path&& p) noexcept {
|
||||
auto& target = mem_.data().target;
|
||||
auto& target = mem_->target;
|
||||
if (p == target) return;
|
||||
|
||||
env().ExecMain(
|
||||
@ -277,7 +256,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
||||
ExecSync();
|
||||
}
|
||||
|
||||
const auto pathstr = mem_.data().target.Stringify();
|
||||
const auto pathstr = mem_->target.Stringify();
|
||||
|
||||
auto w = 6*em;
|
||||
{
|
||||
@ -285,18 +264,17 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
||||
w = std::max(w, std::min(pw, 8*em));
|
||||
|
||||
auto iw = 3*em;
|
||||
for (const auto& v : data().inputs) {
|
||||
for (const auto& v : mem_->inputs) {
|
||||
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
|
||||
}
|
||||
auto ow = 3*em;
|
||||
for (const auto& v : data().outputs) {
|
||||
for (const auto& v : mem_->outputs) {
|
||||
ow = std::max(ow, ImGui::CalcTextSize(v.c_str()).x);
|
||||
}
|
||||
w = std::max(w, 1*em+style.ItemSpacing.x+iw +1*em+ ow+style.ItemSpacing.x+1*em);
|
||||
}
|
||||
|
||||
if (ImGui::Button(pathstr.c_str(), {w, 0})) {
|
||||
config_popup_.Open();
|
||||
}
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
|
||||
@ -307,7 +285,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
||||
|
||||
const auto right = ImGui::GetCursorPosX() + w;
|
||||
ImGui::BeginGroup();
|
||||
for (const auto& name : data().inputs) {
|
||||
for (const auto& name : mem_->inputs) {
|
||||
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
|
||||
gui::NodeSocket();
|
||||
ImGui::SameLine();
|
||||
@ -318,7 +296,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginGroup();
|
||||
for (const auto& name : data().outputs) {
|
||||
for (const auto& name : mem_->outputs) {
|
||||
const auto tw = ImGui::CalcTextSize(name.c_str()).x;
|
||||
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
|
||||
|
||||
@ -330,16 +308,11 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
config_popup_.Update();
|
||||
}
|
||||
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
|
||||
if (ImGui::MenuItem("sync")) {
|
||||
ExecSync();
|
||||
}
|
||||
if (ImGui::MenuItem("replace target")) {
|
||||
config_popup_.Open();
|
||||
}
|
||||
try {
|
||||
auto& f = target();
|
||||
auto& n = f.interfaceOrThrow<nf7::Node>();
|
||||
@ -364,36 +337,5 @@ void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void Ref::ConfigPopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
ImGui::TextUnformatted("Node/Ref: config");
|
||||
const bool submit = ImGui::InputText(
|
||||
"path", &path_, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
|
||||
bool err = false;
|
||||
|
||||
Path path;
|
||||
try {
|
||||
path = Path::Parse(path_);
|
||||
} catch (nf7::Exception& e) {
|
||||
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
|
||||
err = true;
|
||||
}
|
||||
try {
|
||||
f_->ResolveOrThrow(path).interfaceOrThrow<nf7::Node>();
|
||||
} catch (nf7::File::NotFoundException&) {
|
||||
ImGui::Bullet(); ImGui::Text("target seems to be missing");
|
||||
} catch (nf7::File::NotImplementedException&) {
|
||||
ImGui::Bullet(); ImGui::Text("target doesn't seem to have Node interface");
|
||||
}
|
||||
|
||||
if (!err && (ImGui::Button("ok") || submit)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
f_->ExecChangeTarget(std::move(path));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nf7
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_value.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_context.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_timeline.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/life.hh"
|
||||
@ -64,48 +63,41 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
class Session;
|
||||
class Lambda;
|
||||
|
||||
class ConfigModifyCommand;
|
||||
|
||||
using ItemId = uint64_t;
|
||||
|
||||
TL(nf7::Env& env,
|
||||
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
||||
ItemId next = 1,
|
||||
const nf7::gui::Window* win = nullptr) noexcept :
|
||||
nf7::FileBase(kType, env, {&log_, &popup_socket_, &popup_add_item_}),
|
||||
nf7::FileBase(kType, env, {&log_}),
|
||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kMenu_DirItem),
|
||||
life_(*this), log_(*this),
|
||||
layers_(std::move(layers)), next_(next),
|
||||
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
||||
popup_add_item_(*this) {
|
||||
ApplySeqSocketChanges();
|
||||
|
||||
popup_socket_.onSubmit = [this](auto&& i, auto&& o) {
|
||||
ExecChangeSeqSocket(std::move(i), std::move(o));
|
||||
};
|
||||
win_(*this, "Timeline Editor", win), tl_("timeline") {
|
||||
}
|
||||
~TL() noexcept {
|
||||
history_.Clear();
|
||||
}
|
||||
|
||||
TL(nf7::Deserializer& ar) : TL(ar.env()) {
|
||||
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_);
|
||||
ar(win_, tl_, layers_);
|
||||
AssignId();
|
||||
ApplySeqSocketChanges();
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_);
|
||||
ar(win_, tl_, layers_);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override;
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
return inputs_;
|
||||
static const std::vector<std::string> kInputs = {"exec"};
|
||||
return kInputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return outputs_;
|
||||
static const std::vector<std::string> kOutputs = {"result"};
|
||||
return kOutputs;
|
||||
}
|
||||
|
||||
void Handle(const nf7::File::Event& ev) noexcept;
|
||||
@ -113,12 +105,6 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
void UpdateMenu() noexcept override;
|
||||
void UpdateWidget() noexcept override;
|
||||
|
||||
void UpdateEditorWindow() noexcept;
|
||||
void UpdateLambdaSelector() noexcept;
|
||||
void HandleTimelineAction() noexcept;
|
||||
|
||||
void UpdateParamPanelWindow() noexcept;
|
||||
|
||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
|
||||
}
|
||||
@ -132,8 +118,6 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
std::shared_ptr<TL::Lambda> lambda_;
|
||||
std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_;
|
||||
|
||||
std::vector<std::string> inputs_, outputs_; // for GetInputs/GetOutputs
|
||||
|
||||
uint64_t action_time_;
|
||||
uint64_t action_layer_;
|
||||
|
||||
@ -141,44 +125,12 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
uint64_t cursor_ = 0;
|
||||
std::vector<std::unique_ptr<Layer>> layers_;
|
||||
|
||||
std::vector<std::string> seq_inputs_;
|
||||
std::vector<std::string> seq_outputs_;
|
||||
|
||||
ItemId next_;
|
||||
|
||||
nf7::gui::Window win_;
|
||||
nf7::gui::Timeline tl_;
|
||||
|
||||
|
||||
// GUI popup
|
||||
nf7::gui::IOSocketListPopup popup_socket_;
|
||||
|
||||
struct AddItemPopup final :
|
||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
AddItemPopup(TL& f) noexcept :
|
||||
Popup("AddItemPopup"),
|
||||
owner_(&f),
|
||||
factory_(f, [](auto& t) { return t.flags().contains("nf7::Sequencer"); }) {
|
||||
}
|
||||
|
||||
void Open(uint64_t t, TL::Layer& l) noexcept {
|
||||
target_time_ = t;
|
||||
target_layer_ = &l;
|
||||
Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
TL* const owner_;
|
||||
|
||||
uint64_t target_time_ = 0;
|
||||
TL::Layer* target_layer_ = nullptr;
|
||||
|
||||
nf7::gui::FileFactory factory_;
|
||||
} popup_add_item_;
|
||||
|
||||
|
||||
// GUI temporary params
|
||||
bool param_panel_request_focus_ = false;
|
||||
TL::Item* param_panel_target_ = nullptr;
|
||||
@ -206,7 +158,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
void ExecApplyLayerOfSelected() noexcept;
|
||||
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
|
||||
|
||||
// history
|
||||
// history operation
|
||||
void ExecUnDo() noexcept {
|
||||
env().ExecMain(
|
||||
std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"),
|
||||
@ -218,19 +170,17 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
[this]() { history_.ReDo(); });
|
||||
}
|
||||
|
||||
|
||||
// instant running
|
||||
void MoveCursorTo(uint64_t t) noexcept;
|
||||
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
|
||||
|
||||
// socket operation
|
||||
void ExecChangeSeqSocket(std::vector<std::string>&&, std::vector<std::string>&&) noexcept;
|
||||
void ApplySeqSocketChanges() noexcept {
|
||||
inputs_ = seq_inputs_;
|
||||
inputs_.push_back("_exec");
|
||||
// gui
|
||||
void EditorWindow() noexcept;
|
||||
void ParamPanelWindow() noexcept;
|
||||
void LambdaSelector() noexcept;
|
||||
void ItemAdder() noexcept;
|
||||
|
||||
outputs_ = seq_outputs_;
|
||||
}
|
||||
void HandleTimelineAction() noexcept;
|
||||
};
|
||||
|
||||
|
||||
@ -633,11 +583,11 @@ class TL::Lambda final : public Node::Lambda,
|
||||
auto caller = parent();
|
||||
if (!caller) return;
|
||||
|
||||
for (const auto& name : owner_->seq_outputs_) {
|
||||
auto itr = vars.find(name);
|
||||
if (itr == vars.end()) continue;
|
||||
caller->Handle(name, itr->second, shared_from_this());
|
||||
std::vector<nf7::Value::TuplePair> tup;
|
||||
for (auto& p : vars) {
|
||||
tup.emplace_back(p.first, p.second);
|
||||
}
|
||||
caller->Handle("result", nf7::Value {std::move(tup)}, shared_from_this());
|
||||
}
|
||||
|
||||
void Abort() noexcept {
|
||||
@ -1135,65 +1085,6 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
|
||||
}
|
||||
|
||||
|
||||
class TL::ConfigModifyCommand final : public nf7::History::Command {
|
||||
public:
|
||||
struct Builder final {
|
||||
public:
|
||||
Builder(TL& f) noexcept :
|
||||
prod_(std::make_unique<ConfigModifyCommand>(f)) {
|
||||
}
|
||||
|
||||
Builder& inputs(std::vector<std::string>&& v) noexcept {
|
||||
prod_->seq_inputs_ = std::move(v);
|
||||
return *this;
|
||||
}
|
||||
Builder& outputs(std::vector<std::string>&& v) noexcept {
|
||||
prod_->seq_outputs_ = std::move(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<ConfigModifyCommand> Build() noexcept {
|
||||
return std::move(prod_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConfigModifyCommand> prod_;
|
||||
};
|
||||
|
||||
ConfigModifyCommand(TL& f) noexcept : owner_(&f) {
|
||||
}
|
||||
|
||||
void Apply() override { Exec(); }
|
||||
void Revert() override { Exec(); }
|
||||
|
||||
private:
|
||||
TL* const owner_;
|
||||
|
||||
std::optional<std::vector<std::string>> seq_inputs_, seq_outputs_;
|
||||
|
||||
void Exec() noexcept {
|
||||
if (seq_inputs_) {
|
||||
std::swap(owner_->seq_inputs_, *seq_inputs_);
|
||||
}
|
||||
if (seq_outputs_) {
|
||||
std::swap(owner_->seq_outputs_, *seq_outputs_);
|
||||
}
|
||||
|
||||
if (seq_inputs_ || seq_outputs_) {
|
||||
owner_->ApplySeqSocketChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
void TL::ExecChangeSeqSocket(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept {
|
||||
auto cmd = ConfigModifyCommand::Builder {*this}.
|
||||
inputs(std::move(i)).
|
||||
outputs(std::move(o)).
|
||||
Build();
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "updating I/O socket list");
|
||||
history_.Add(std::move(cmd)).ExecApply(ctx);
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
|
||||
std::vector<std::unique_ptr<TL::Layer>> layers;
|
||||
layers.reserve(layers_.size());
|
||||
@ -1242,8 +1133,8 @@ void TL::Update() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateEditorWindow();
|
||||
UpdateParamPanelWindow();
|
||||
EditorWindow();
|
||||
ParamPanelWindow();
|
||||
|
||||
if (history_.Squash()) {
|
||||
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
|
||||
@ -1254,30 +1145,21 @@ void TL::UpdateMenu() noexcept {
|
||||
if (ImGui::MenuItem("editor", nullptr, &win_.shown()) && win_.shown()) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::MenuItem("I/O list")) {
|
||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
||||
}
|
||||
}
|
||||
void TL::UpdateWidget() noexcept {
|
||||
ImGui::TextUnformatted("Sequencer/Timeline");
|
||||
|
||||
|
||||
if (ImGui::Button("Editor")) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::Button("I/O list")) {
|
||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
||||
}
|
||||
|
||||
popup_socket_.Update();
|
||||
}
|
||||
void TL::UpdateEditorWindow() noexcept {
|
||||
|
||||
void TL::EditorWindow() noexcept {
|
||||
if (win_.shownInCurrentFrame()) {
|
||||
const auto em = ImGui::GetFontSize();
|
||||
ImGui::SetNextWindowSizeConstraints({32*em, 16*em}, {1e8, 1e8});
|
||||
}
|
||||
if (win_.Begin()) {
|
||||
UpdateLambdaSelector();
|
||||
LambdaSelector();
|
||||
|
||||
// timeline
|
||||
if (tl_.Begin()) {
|
||||
@ -1300,9 +1182,10 @@ void TL::UpdateEditorWindow() noexcept {
|
||||
action_layer_ = layer->index();
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("add new item")) {
|
||||
if (action_layer_ < layers_.size()) {
|
||||
popup_add_item_.Open(action_time_, *layers_[action_layer_]);
|
||||
if (action_layer_ < layers_.size()) {
|
||||
if (ImGui::BeginMenu("add new item")) {
|
||||
ItemAdder();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
if (selected_.size()) {
|
||||
@ -1318,10 +1201,6 @@ void TL::UpdateEditorWindow() noexcept {
|
||||
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
|
||||
ExecReDo();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("I/O socket list")) {
|
||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
@ -1405,7 +1284,33 @@ void TL::UpdateEditorWindow() noexcept {
|
||||
}
|
||||
win_.End();
|
||||
}
|
||||
void TL::UpdateLambdaSelector() noexcept {
|
||||
void TL::ParamPanelWindow() noexcept {
|
||||
if (!win_.shown()) return;
|
||||
|
||||
const auto name = abspath().Stringify() + " | Parameter Panel";
|
||||
|
||||
if (std::exchange(param_panel_request_focus_, false)) {
|
||||
ImGui::SetNextWindowFocus();
|
||||
}
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Begin(name.c_str())) {
|
||||
if (auto item = param_panel_target_) {
|
||||
if (item->seq().flags() & Sequencer::kParamPanel) {
|
||||
TL::Editor ed {*item};
|
||||
item->seq().UpdateParamPanel(ed);
|
||||
} else {
|
||||
ImGui::TextUnformatted("item doesn't have parameter panel");
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted("no item selected");
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
void TL::LambdaSelector() noexcept {
|
||||
const auto current_lambda =
|
||||
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
|
||||
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
|
||||
@ -1435,6 +1340,84 @@ void TL::UpdateLambdaSelector() noexcept {
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
void TL::ItemAdder() noexcept {
|
||||
// parameters
|
||||
auto& layer = *layers_[action_layer_];
|
||||
auto time = action_time_;
|
||||
|
||||
uint64_t dur = static_cast<uint64_t>(4.f / tl_.zoom());
|
||||
if (auto item = layer.FindItemAfter(time)) {
|
||||
dur = std::min(dur, item->timing().begin() - time);
|
||||
}
|
||||
|
||||
// header and initialization
|
||||
static const nf7::File::TypeInfo* type;
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
type = nullptr;
|
||||
}
|
||||
ImGui::TextUnformatted("Sequencer/Timeline: adding new item...");
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
// type list
|
||||
bool exec = false;
|
||||
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
|
||||
for (auto& p : nf7::File::registry()) {
|
||||
const auto& t = *p.second;
|
||||
if (!t.flags().contains("nf7::Sequencer")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr auto kFlags =
|
||||
ImGuiSelectableFlags_SpanAllColumns |
|
||||
ImGuiSelectableFlags_AllowItemOverlap;
|
||||
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
|
||||
type = &t;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
t.UpdateTooltip();
|
||||
ImGui::EndTooltip();
|
||||
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
exec = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
// validation
|
||||
bool valid = true;
|
||||
if (type == nullptr) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
|
||||
valid = false;
|
||||
}
|
||||
if (dur == 0) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("no space to insert new item");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// ok button
|
||||
ImGui::BeginDisabled(!valid);
|
||||
if (ImGui::Button("ok")) {
|
||||
exec = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
// adding
|
||||
if (exec && valid) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
auto file = type->Create(env());
|
||||
auto timing = TL::Timing::BeginDur(time, dur);
|
||||
auto item = std::make_unique<TL::Item>(next_++, std::move(file), timing);
|
||||
auto cmd = std::make_unique<TL::Layer::ItemSwapCommand>(layer, std::move(item));
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "adding new item");
|
||||
history_.Add(std::move(cmd)).ExecApply(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void TL::HandleTimelineAction() noexcept {
|
||||
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
|
||||
const auto action_time = tl_.actionTime();
|
||||
@ -1497,32 +1480,6 @@ void TL::HandleTimelineAction() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void TL::UpdateParamPanelWindow() noexcept {
|
||||
if (!win_.shown()) return;
|
||||
|
||||
const auto name = abspath().Stringify() + " | Parameter Panel";
|
||||
|
||||
if (std::exchange(param_panel_request_focus_, false)) {
|
||||
ImGui::SetNextWindowFocus();
|
||||
}
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Begin(name.c_str())) {
|
||||
if (auto item = param_panel_target_) {
|
||||
if (item->seq().flags() & Sequencer::kParamPanel) {
|
||||
TL::Editor ed {*item};
|
||||
item->seq().UpdateParamPanel(ed);
|
||||
} else {
|
||||
ImGui::TextUnformatted("item doesn't have parameter panel");
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted("no item selected");
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void TL::Layer::UpdateHeader(size_t idx) noexcept {
|
||||
index_ = idx;
|
||||
@ -1574,6 +1531,7 @@ void TL::Layer::UpdateHeader(size_t idx) noexcept {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TL::Item::Update() noexcept {
|
||||
assert(owner_);
|
||||
assert(layer_);
|
||||
@ -1615,33 +1573,6 @@ void TL::Item::Update() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TL::AddItemPopup::Update() noexcept {
|
||||
if (Popup::Begin()) {
|
||||
ImGui::TextUnformatted("Sequencer/Timeline: adding new item...");
|
||||
if (factory_.Update()) {
|
||||
auto& layer = *target_layer_;
|
||||
auto time = target_time_;
|
||||
|
||||
uint64_t dur = static_cast<uint64_t>(4.f / owner_->tl_.zoom());
|
||||
if (auto item = layer.FindItemAfter(time)) {
|
||||
dur = std::min(dur, item->timing().begin() - time);
|
||||
}
|
||||
if (dur > 0) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
auto file = factory_.type().Create(owner_->env());
|
||||
auto timing = TL::Timing::BeginDur(time, dur);
|
||||
auto item = std::make_unique<TL::Item>(owner_->next_++, std::move(file), timing);
|
||||
auto cmd = std::make_unique<TL::Layer::ItemSwapCommand>(layer, std::move(item));
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
|
||||
owner_->history_.Add(std::move(cmd)).ExecApply(ctx);
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nf7
|
||||
|
||||
|
@ -19,8 +19,6 @@
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_dnd.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/yas_nf7.hh"
|
||||
@ -39,13 +37,12 @@ class Dir final : public nf7::FileBase,
|
||||
using ItemMap = std::map<std::string, std::unique_ptr<File>>;
|
||||
|
||||
Dir(nf7::Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept :
|
||||
nf7::FileBase(kType, env, {&widget_popup_, &add_popup_, &rename_popup_}),
|
||||
nf7::FileBase(kType, env),
|
||||
nf7::DirItem(nf7::DirItem::kTree |
|
||||
nf7::DirItem::kMenu |
|
||||
nf7::DirItem::kTooltip |
|
||||
nf7::DirItem::kDragDropTarget),
|
||||
items_(std::move(items)), win_(*this, "TreeView System/Dir", src),
|
||||
widget_popup_(*this), add_popup_(*this), rename_popup_(*this) {
|
||||
items_(std::move(items)), win_(*this, "TreeView System/Dir", src) {
|
||||
}
|
||||
|
||||
Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
|
||||
@ -148,66 +145,6 @@ class Dir final : public nf7::FileBase,
|
||||
std::unordered_set<std::string> opened_;
|
||||
|
||||
|
||||
// GUI popup
|
||||
class WidgetPopup final :
|
||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
WidgetPopup(Dir& owner) noexcept :
|
||||
nf7::gui::Popup("WidgetPopup"), owner_(&owner) {
|
||||
}
|
||||
|
||||
void Open(nf7::File& f) noexcept {
|
||||
target_ = &f;
|
||||
nf7::gui::Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
Dir* owner_;
|
||||
nf7::File* target_ = nullptr;
|
||||
} widget_popup_;
|
||||
|
||||
class AddPopup final :
|
||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
AddPopup(Dir& owner) noexcept :
|
||||
nf7::gui::Popup("AddPopup"),
|
||||
owner_(&owner),
|
||||
factory_(owner, [](auto& t) { return t.flags().contains("nf7::DirItem"); },
|
||||
nf7::gui::FileFactory::kNameInput |
|
||||
nf7::gui::FileFactory::kNameDupCheck) {
|
||||
}
|
||||
|
||||
using nf7::gui::Popup::Open;
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
Dir* owner_;
|
||||
nf7::gui::FileFactory factory_;
|
||||
} add_popup_;
|
||||
|
||||
class RenamePopup final :
|
||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
RenamePopup(Dir& owner) noexcept :
|
||||
nf7::gui::Popup("RenamePopup"),
|
||||
owner_(&owner) {
|
||||
}
|
||||
|
||||
void Open(std::string_view before) noexcept {
|
||||
before_ = before;
|
||||
after_ = "";
|
||||
nf7::gui::Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
Dir* owner_;
|
||||
std::string before_;
|
||||
std::string after_;
|
||||
} rename_popup_;
|
||||
|
||||
|
||||
std::string GetUniqueName(std::string_view name) const noexcept {
|
||||
auto ret = std::string {name};
|
||||
while (Find(ret)) {
|
||||
@ -215,6 +152,11 @@ class Dir final : public nf7::FileBase,
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// imgui widgets
|
||||
void ItemAdder() noexcept;
|
||||
void ItemRenamer(const std::string& name) noexcept;
|
||||
bool ValidateName(const std::string& name) noexcept;
|
||||
};
|
||||
|
||||
void Dir::Update() noexcept {
|
||||
@ -279,12 +221,6 @@ void Dir::UpdateTree() noexcept {
|
||||
opened_.erase(name);
|
||||
}
|
||||
|
||||
if (ditem && (ditem->flags() & DirItem::kWidget)) {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
widget_popup_.Open(file);
|
||||
}
|
||||
}
|
||||
|
||||
// tooltip
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
@ -301,11 +237,6 @@ void Dir::UpdateTree() noexcept {
|
||||
|
||||
// context menu
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ditem && (ditem->flags() & DirItem::kWidget)) {
|
||||
if (ImGui::MenuItem("open widget")) {
|
||||
widget_popup_.Open(file);
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("copy path")) {
|
||||
ImGui::SetClipboardText(file.abspath().Stringify().c_str());
|
||||
}
|
||||
@ -316,13 +247,14 @@ void Dir::UpdateTree() noexcept {
|
||||
std::make_shared<nf7::GenericContext>(*this, "removing item"),
|
||||
[this, name]() { Remove(name); });
|
||||
}
|
||||
if (ImGui::MenuItem("rename")) {
|
||||
rename_popup_.Open(name);
|
||||
if (ImGui::BeginMenu("rename")) {
|
||||
ItemRenamer(name);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("renew")) {
|
||||
env().ExecMain(
|
||||
std::make_shared<nf7::GenericContext>(*this, "removing item"),
|
||||
std::make_shared<nf7::GenericContext>(*this, "renewing item"),
|
||||
[this, name]() { Add(name, Remove(name)); });
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
@ -330,8 +262,9 @@ void Dir::UpdateTree() noexcept {
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("add new sibling")) {
|
||||
add_popup_.Open();
|
||||
if (ImGui::BeginMenu("add new sibling")) {
|
||||
ItemAdder();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ditem && (ditem->flags() & DirItem::kMenu)) {
|
||||
@ -377,8 +310,9 @@ void Dir::UpdateTree() noexcept {
|
||||
}
|
||||
}
|
||||
void Dir::UpdateMenu() noexcept {
|
||||
if (ImGui::MenuItem("add new child")) {
|
||||
add_popup_.Open();
|
||||
if (ImGui::BeginMenu("add new child")) {
|
||||
ItemAdder();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("TreeView", nullptr, &win_.shown());
|
||||
@ -413,82 +347,115 @@ try {
|
||||
} catch (nf7::Exception&) {
|
||||
}
|
||||
|
||||
void Dir::WidgetPopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
if (auto item = target_->interface<nf7::DirItem>()) {
|
||||
ImGui::PushID(item);
|
||||
item->UpdateWidget();
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
|
||||
void Dir::ItemAdder() noexcept {
|
||||
static const nf7::File::TypeInfo* type;
|
||||
static std::string name;
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
type = nullptr;
|
||||
name = GetUniqueName("new_file");
|
||||
}
|
||||
}
|
||||
void Dir::AddPopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
ImGui::TextUnformatted("System/Dir: adding new file...");
|
||||
if (factory_.Update()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
ImGui::TextUnformatted("System/Dir: adding new file...");
|
||||
|
||||
auto& env = owner_->env();
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
|
||||
auto task = [this, &env]() { owner_->Add(factory_.name(), factory_.Create(env)); };
|
||||
env.ExecMain(ctx, std::move(task));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
void Dir::RenamePopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
ImGui::TextUnformatted("System/Dir: renaming an exsting item...");
|
||||
ImGui::InputText("before", &before_);
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
bool submit = false;
|
||||
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
|
||||
if (ImGui::InputText("after", &after_, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
submit = true;
|
||||
}
|
||||
bool exec = false;
|
||||
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
|
||||
for (auto& p : nf7::File::registry()) {
|
||||
const auto& t = *p.second;
|
||||
if (!t.flags().contains("nf7::DirItem")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool err = false;
|
||||
if (!owner_->Find(before_)) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target");
|
||||
err = true;
|
||||
}
|
||||
if (owner_->Find(after_)) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("after is invalid: duplicated name");
|
||||
err = true;
|
||||
}
|
||||
try {
|
||||
Path::ValidateTerm(after_);
|
||||
} catch (Exception& e) {
|
||||
ImGui::Bullet(); ImGui::Text("after is invalid: %s", e.msg().c_str());
|
||||
err = true;
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
if (ImGui::Button("ok")) {
|
||||
submit = true;
|
||||
constexpr auto kFlags =
|
||||
ImGuiSelectableFlags_SpanAllColumns |
|
||||
ImGuiSelectableFlags_AllowItemOverlap;
|
||||
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
|
||||
type = &t;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"rename '%s' to '%s' on '%s'",
|
||||
before_.c_str(), after_.c_str(),
|
||||
owner_->abspath().Stringify().c_str());
|
||||
ImGui::BeginTooltip();
|
||||
t.UpdateTooltip();
|
||||
ImGui::EndTooltip();
|
||||
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
exec = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (submit) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "renaming item");
|
||||
auto task = [this, before = std::move(before_), after = std::move(after_)]() {
|
||||
auto f = owner_->Remove(before);
|
||||
if (!f) throw nf7::Exception {"missing target"};
|
||||
owner_->Add(after, std::move(f));
|
||||
};
|
||||
owner_->env().ExecMain(ctx, std::move(task));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(16*em);
|
||||
if (ImGui::InputText("name", &name, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
exec = true;
|
||||
}
|
||||
|
||||
bool valid = ValidateName(name);
|
||||
if (type == nullptr) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!valid);
|
||||
if (ImGui::Button("ok")) {
|
||||
exec = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (exec && valid) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
env().ExecMain(
|
||||
std::make_shared<nf7::GenericContext>(*this, "adding new item"),
|
||||
[this]() { Add(name, type->Create(env())); });
|
||||
}
|
||||
}
|
||||
|
||||
void Dir::ItemRenamer(const std::string& name) noexcept {
|
||||
static std::string editing_name;
|
||||
static std::string err;
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
editing_name = name;
|
||||
err = "";
|
||||
}
|
||||
|
||||
bool exec = ImGui::InputText("##name", &editing_name, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
ImGui::SameLine();
|
||||
const auto pos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::NewLine();
|
||||
bool valid = ValidateName(editing_name);
|
||||
|
||||
ImGui::SetCursorPos(pos);
|
||||
ImGui::BeginDisabled(!valid);
|
||||
if (ImGui::Button("apply")) {
|
||||
exec = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (exec && valid) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
env().ExecMain(
|
||||
std::make_shared<nf7::GenericContext>(*this, "renaming item"),
|
||||
[this, name]() { Add(editing_name, Remove(name)); });
|
||||
}
|
||||
}
|
||||
|
||||
bool Dir::ValidateName(const std::string& name) noexcept {
|
||||
bool ret = true;
|
||||
|
||||
if (Find(name)) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("name duplicated");
|
||||
ret = false;
|
||||
}
|
||||
|
||||
try {
|
||||
nf7::File::Path::ValidateTerm(name);
|
||||
} catch (nf7::Exception& e) {
|
||||
ImGui::Bullet(); ImGui::Text("invalid format: %s", e.msg().c_str());
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,17 +10,22 @@
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/dir_item.hh"
|
||||
#include "common/file_base.hh"
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_config.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/logger.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/yas_std_atomic.hh"
|
||||
|
||||
@ -44,8 +49,6 @@ class Logger final : public nf7::File,
|
||||
"recorded logs won't be permanentized");
|
||||
}
|
||||
|
||||
class Node;
|
||||
|
||||
struct Row final {
|
||||
public:
|
||||
File::Id file;
|
||||
@ -66,39 +69,80 @@ class Logger final : public nf7::File,
|
||||
return st.str();
|
||||
}
|
||||
};
|
||||
struct Param final {
|
||||
public:
|
||||
Param(uint32_t mr, bool p, bool f) : max_rows(mr), propagate(p), freeze(f) {
|
||||
}
|
||||
std::atomic<uint32_t> max_rows;
|
||||
std::atomic<bool> propagate;
|
||||
std::atomic<bool> freeze;
|
||||
};
|
||||
class ItemStore;
|
||||
|
||||
Logger(nf7::Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept :
|
||||
struct Data {
|
||||
uint32_t max_rows = 1024;
|
||||
bool propagate = false;
|
||||
bool freeze = false;
|
||||
|
||||
Data() noexcept { }
|
||||
void serialize(auto& ar) {
|
||||
ar(max_rows, propagate, freeze);
|
||||
if (max_rows == 0) {
|
||||
throw DeserializeException("max_rows must be 1 or more");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Stringify() const noexcept {
|
||||
YAML::Emitter st;
|
||||
st << YAML::BeginMap;
|
||||
st << YAML::Key << "max_rows";
|
||||
st << YAML::Value << max_rows;
|
||||
st << YAML::Key << "propagate";
|
||||
st << YAML::Value << propagate;
|
||||
st << YAML::Key << "freeze";
|
||||
st << YAML::Value << freeze;
|
||||
st << YAML::EndMap;
|
||||
return {st.c_str(), st.size()};
|
||||
}
|
||||
void Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
|
||||
Data d;
|
||||
d.max_rows = yaml["max_rows"].as<uint32_t>();
|
||||
d.propagate = yaml["propagate"].as<bool>();
|
||||
d.freeze = yaml["freeze"].as<bool>();
|
||||
|
||||
*this = std::move(d);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
};
|
||||
|
||||
Logger(nf7::Env& env, Data&& d = {}) noexcept :
|
||||
File(kType, env), DirItem(DirItem::kMenu),
|
||||
param_(std::make_shared<Param>(max_rows, propagate, freeze)),
|
||||
win_(*this, "LogView") {
|
||||
mem_(std::move(d), *this), win_(*this, "Log View") {
|
||||
win_.shown() = true;
|
||||
|
||||
mem_.onCommit = mem_.onRestore = [this]() {
|
||||
store_->param(mem_.data());
|
||||
};
|
||||
}
|
||||
|
||||
Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
|
||||
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
|
||||
|
||||
if (param_->max_rows == 0) {
|
||||
throw DeserializeException("max_rows must be 1 or more");
|
||||
}
|
||||
ar(win_, mem_.data());
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
|
||||
ar(win_, mem_.data());
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<Logger>(
|
||||
env, param_->max_rows, param_->propagate, param_->freeze);
|
||||
return std::make_unique<Logger>(env, Data {mem_.data()});
|
||||
}
|
||||
|
||||
void Handle(const nf7::File::Event& ev) noexcept override {
|
||||
switch (ev.type) {
|
||||
case Event::kAdd:
|
||||
store_ = std::make_shared<ItemStore>(*this);
|
||||
return;
|
||||
case Event::kRemove:
|
||||
store_ = nullptr;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Handle(const nf7::File::Event& ev) noexcept override;
|
||||
void Update() noexcept override;
|
||||
void UpdateMenu() noexcept override;
|
||||
void UpdateRowMenu(const Row&) noexcept;
|
||||
@ -109,18 +153,18 @@ class Logger final : public nf7::File,
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Param> param_;
|
||||
class ItemStore;
|
||||
std::shared_ptr<ItemStore> store_;
|
||||
std::deque<Row> rows_;
|
||||
|
||||
const char* popup_ = nullptr;
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
nf7::gui::Window win_;
|
||||
|
||||
|
||||
void DropExceededRows() noexcept {
|
||||
if (rows_.size() <= param_->max_rows) return;
|
||||
rows_.erase(rows_.begin(), rows_.end()-param_->max_rows);
|
||||
if (rows_.size() <= mem_->max_rows) return;
|
||||
rows_.erase(rows_.begin(), rows_.end()-mem_->max_rows);
|
||||
}
|
||||
|
||||
std::string GetPathString(File::Id id) const noexcept
|
||||
@ -147,197 +191,85 @@ class Logger final : public nf7::File,
|
||||
static std::string GetLocationString(const std::source_location loc) noexcept {
|
||||
return loc.file_name()+":"s+std::to_string(loc.line());
|
||||
}
|
||||
};
|
||||
class Logger::ItemStore final : public nf7::Context,
|
||||
public nf7::Logger,
|
||||
public std::enable_shared_from_this<ItemStore> {
|
||||
public:
|
||||
ItemStore() = delete;
|
||||
ItemStore(File& owner, const std::shared_ptr<Param>& param) noexcept :
|
||||
Context(owner.env(), owner.id()), param_(param) {
|
||||
}
|
||||
ItemStore(const ItemStore&) = delete;
|
||||
ItemStore(ItemStore&&) = delete;
|
||||
ItemStore& operator=(const ItemStore&) = delete;
|
||||
ItemStore& operator=(ItemStore&&) = delete;
|
||||
|
||||
void Write(nf7::Logger::Item&& item) noexcept override {
|
||||
if (param_->freeze) return;
|
||||
if (param_->propagate) {
|
||||
// TODO propagation
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (items_.size() >= param_->max_rows) items_.pop_front();
|
||||
items_.push_back(std::move(item));
|
||||
}
|
||||
bool MoveItemsTo(auto& owner) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (items_.empty()) return false;
|
||||
auto& rows = owner.rows_;
|
||||
|
||||
auto itr = items_.begin();
|
||||
if (rows.size()+items_.size() > param_->max_rows) {
|
||||
// max_rows may be changed
|
||||
if (items_.size() > param_->max_rows) {
|
||||
itr += static_cast<intmax_t>(param_->max_rows - items_.size());
|
||||
}
|
||||
const auto keep =
|
||||
static_cast<intmax_t>(param_->max_rows) - std::distance(itr, items_.end());
|
||||
rows.erase(rows.begin(), rows.end()-keep);
|
||||
}
|
||||
for (; itr < items_.end(); ++itr) {
|
||||
Row row = {
|
||||
.file = itr->file,
|
||||
.srcloc = itr->srcloc,
|
||||
.level = GetLevelString(itr->level),
|
||||
.msg = std::move(itr->msg),
|
||||
.path = owner.GetPathString(itr->file),
|
||||
.location = GetLocationString(itr->srcloc),
|
||||
.ex = itr->ex,
|
||||
};
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
items_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetDescription() const noexcept override {
|
||||
return "System/Logger shared instance";
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mtx_;
|
||||
std::deque<nf7::Logger::Item> items_;
|
||||
std::shared_ptr<Param> param_;
|
||||
};
|
||||
|
||||
|
||||
class Logger::Node final : public nf7::FileBase, public nf7::Node {
|
||||
public:
|
||||
static inline const nf7::GenericTypeInfo<Logger::Node> kType = {
|
||||
"System/Logger/Node", {"nf7::Node"}};
|
||||
static void UpdateTypeTooltip() noexcept {
|
||||
ImGui::TextUnformatted("Sends message to logger.");
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
||||
}
|
||||
|
||||
Node(nf7::Env& env) noexcept :
|
||||
nf7::FileBase(kType, env, {&logger_}),
|
||||
nf7::Node(nf7::Node::kNone),
|
||||
life_(*this), logger_(*this) {
|
||||
}
|
||||
|
||||
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
||||
}
|
||||
void Serialize(nf7::Serializer&) const noexcept override {
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<Logger::Node>(env);
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
|
||||
return std::make_shared<Logger::Node::Lambda>(*this, parent);
|
||||
}
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
static const std::vector<std::string> kInputs = {"msg"};
|
||||
return kInputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return {};
|
||||
}
|
||||
|
||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
return InterfaceSelector<nf7::Node>(t).Select(this);
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Logger::Node> life_;
|
||||
|
||||
nf7::LoggerRef logger_;
|
||||
|
||||
|
||||
class Lambda final : public nf7::Node::Lambda {
|
||||
class ItemStore final : public nf7::Context,
|
||||
public nf7::Logger,
|
||||
public std::enable_shared_from_this<ItemStore> {
|
||||
public:
|
||||
Lambda(Logger::Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||
ItemStore() = delete;
|
||||
ItemStore(File& f) noexcept : nf7::Context(f) {
|
||||
}
|
||||
ItemStore(const ItemStore&) = delete;
|
||||
ItemStore(ItemStore&&) = delete;
|
||||
ItemStore& operator=(const ItemStore&) = delete;
|
||||
ItemStore& operator=(ItemStore&&) = delete;
|
||||
|
||||
void Write(nf7::Logger::Item&& item) noexcept override {
|
||||
if (param_.freeze) return;
|
||||
if (param_.propagate) {
|
||||
// TODO propagation
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (items_.size() >= param_.max_rows) items_.pop_front();
|
||||
items_.push_back(std::move(item));
|
||||
}
|
||||
|
||||
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
|
||||
try {
|
||||
f_.EnforceAlive();
|
||||
if (in.value.isString()) {
|
||||
f_->logger_.Info(in.value.string());
|
||||
} else {
|
||||
f_->logger_.Info("["s+in.value.typeName()+"]");
|
||||
bool MoveItemsTo(auto& owner) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (items_.empty()) return false;
|
||||
auto& rows = owner.rows_;
|
||||
|
||||
auto itr = items_.begin();
|
||||
if (rows.size()+items_.size() > param_.max_rows) {
|
||||
if (items_.size() > param_.max_rows) {
|
||||
itr += static_cast<intmax_t>(param_.max_rows - items_.size());
|
||||
}
|
||||
const auto keep =
|
||||
static_cast<intmax_t>(param_.max_rows) - std::distance(itr, items_.end());
|
||||
rows.erase(rows.begin(), rows.end()-keep);
|
||||
}
|
||||
} catch (nf7::Exception&) {
|
||||
for (; itr < items_.end(); ++itr) {
|
||||
Row row = {
|
||||
.file = itr->file,
|
||||
.srcloc = itr->srcloc,
|
||||
.level = GetLevelString(itr->level),
|
||||
.msg = std::move(itr->msg),
|
||||
.path = owner.GetPathString(itr->file),
|
||||
.location = GetLocationString(itr->srcloc),
|
||||
.ex = itr->ex,
|
||||
};
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
items_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetDescription() const noexcept override {
|
||||
return "System/Logger shared instance";
|
||||
}
|
||||
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
void param(const Data& d) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
param_ = d;
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Logger::Node>::Ref f_;
|
||||
std::mutex mtx_;
|
||||
std::deque<nf7::Logger::Item> items_;
|
||||
|
||||
Data param_;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
void Logger::Handle(const Event& ev) noexcept {
|
||||
switch (ev.type) {
|
||||
case Event::kAdd:
|
||||
store_ = std::make_shared<ItemStore>(*this, param_);
|
||||
return;
|
||||
case Event::kRemove:
|
||||
store_ = nullptr;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
void Logger::Update() noexcept {
|
||||
if (const auto name = std::exchange(popup_, nullptr)) {
|
||||
ImGui::OpenPopup(name);
|
||||
}
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
// config popup
|
||||
if (ImGui::BeginPopup("ConfigPopup")) {
|
||||
ImGui::TextUnformatted("System/Logger Config");
|
||||
ImGui::Spacing();
|
||||
|
||||
static const uint32_t kMinRows = 1, kMaxRows = 1024*1024;
|
||||
uint32_t max_rows = param_->max_rows;
|
||||
if (ImGui::DragScalar("max rows", ImGuiDataType_U32, &max_rows, 1, &kMinRows, &kMaxRows)) {
|
||||
param_->max_rows = max_rows;
|
||||
DropExceededRows();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("the oldest row is dropped when exceed");
|
||||
}
|
||||
|
||||
bool propagate = param_->propagate;
|
||||
if (ImGui::Checkbox("propagate", &propagate)) {
|
||||
param_->propagate = propagate;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("after handling, passes the msg to outer logger if exists");
|
||||
}
|
||||
|
||||
bool freeze = param_->freeze;
|
||||
if (ImGui::Checkbox("freeze", &freeze)) {
|
||||
param_->freeze = freeze;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("stop handling except propagation");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// LogView
|
||||
if (win_.shownInCurrentFrame()) {
|
||||
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
|
||||
}
|
||||
@ -423,11 +355,12 @@ void Logger::Update() noexcept {
|
||||
win_.End();
|
||||
}
|
||||
void Logger::UpdateMenu() noexcept {
|
||||
ImGui::MenuItem("shown", nullptr, &win_.shown());
|
||||
|
||||
if (ImGui::MenuItem("config")) {
|
||||
popup_ = "ConfigPopup";
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Log View", nullptr, &win_.shown());
|
||||
}
|
||||
void Logger::UpdateRowMenu(const Row& row) noexcept {
|
||||
if (ImGui::MenuItem("copy as text")) {
|
||||
|
@ -9,6 +9,9 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
@ -18,12 +21,12 @@
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/gui_config.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/mutex.hh"
|
||||
#include "common/nfile.hh"
|
||||
#include "common/nfile_watcher.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/thread.hh"
|
||||
@ -83,33 +86,60 @@ class NFile final : public nf7::FileBase,
|
||||
struct Data final {
|
||||
std::filesystem::path npath;
|
||||
std::string mode;
|
||||
|
||||
std::string Stringify() noexcept {
|
||||
YAML::Emitter st;
|
||||
st << YAML::BeginMap;
|
||||
st << YAML::Key << "npath";
|
||||
st << YAML::Value << npath.generic_string();
|
||||
st << YAML::Key << "mode";
|
||||
st << YAML::Value << mode;
|
||||
st << YAML::EndMap;
|
||||
return {st.c_str(), st.size()};
|
||||
}
|
||||
void Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
|
||||
Data d;
|
||||
d.npath = yaml["npath"].as<std::string>();
|
||||
d.mode = yaml["mode"].as<std::string>();
|
||||
|
||||
*this = std::move(d);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
};
|
||||
|
||||
NFile(nf7::Env& env, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {&config_popup_}),
|
||||
nf7::FileBase(kType, env, {&nwatch_}),
|
||||
nf7::DirItem(nf7::DirItem::kMenu |
|
||||
nf7::DirItem::kTooltip |
|
||||
nf7::DirItem::kWidget),
|
||||
nf7::DirItem::kTooltip),
|
||||
nf7::Node(nf7::Node::kMenu_DirItem),
|
||||
life_(*this),
|
||||
shared_(std::make_shared<SharedData>(*this)),
|
||||
th_(std::make_shared<Thread>(*this, Runner {shared_})),
|
||||
mem_(std::move(data), *this),
|
||||
config_popup_(*this) {
|
||||
mem_(std::move(data), *this) {
|
||||
nf7::FileBase::Install(shared_->log);
|
||||
|
||||
mtx_.onLock = [this]() { SetUp(); };
|
||||
mtx_.onLock = [this]() { SetUp(); };
|
||||
mtx_.onUnlock = [this]() { shared_->nfile.reset(); };
|
||||
|
||||
mem_.onRestore = mem_.onCommit = [this]() {
|
||||
nwatch_.Clear();
|
||||
nwatch_.Watch(mem_->npath);
|
||||
};
|
||||
nwatch_.onMod = [this]() { Touch(); };
|
||||
}
|
||||
|
||||
NFile(nf7::Deserializer& ar) : NFile(ar.env()) {
|
||||
ar(data().npath, data().mode);
|
||||
ar(mem_->npath, mem_->mode);
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(data().npath, data().mode);
|
||||
ar(mem_->npath, mem_->mode);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<NFile>(env, Data {data()});
|
||||
return std::make_unique<NFile>(env, Data {mem_.data()});
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
@ -124,58 +154,26 @@ class NFile final : public nf7::FileBase,
|
||||
return kOutputs;
|
||||
}
|
||||
|
||||
void Update() noexcept override;
|
||||
void UpdateMenu() noexcept override;
|
||||
void UpdateTooltip() noexcept override;
|
||||
void UpdateWidget() noexcept override;
|
||||
|
||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
return InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<NFile> life_;
|
||||
nf7::Life<NFile> life_;
|
||||
nf7::NFileWatcher nwatch_;
|
||||
|
||||
std::shared_ptr<SharedData> shared_;
|
||||
std::shared_ptr<Thread> th_;
|
||||
nf7::Mutex mtx_;
|
||||
|
||||
std::filesystem::file_time_type lastmod_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
const Data& data() const noexcept { return mem_.data(); }
|
||||
Data& data() noexcept { return mem_.data(); }
|
||||
|
||||
|
||||
// GUI popup
|
||||
struct ConfigPopup final :
|
||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||
public:
|
||||
ConfigPopup(NFile& f) noexcept :
|
||||
nf7::gui::Popup("ConfigPopup"), f_(&f) {
|
||||
}
|
||||
|
||||
void Open() noexcept {
|
||||
npath_ = f_->data().npath.generic_string();
|
||||
|
||||
const auto& mode = f_->data().mode;
|
||||
read_ = std::string::npos != mode.find('r');
|
||||
write_ = std::string::npos != mode.find('w');
|
||||
nf7::gui::Popup::Open();
|
||||
}
|
||||
void Update() noexcept override;
|
||||
|
||||
private:
|
||||
NFile* const f_;
|
||||
|
||||
std::string npath_;
|
||||
bool read_, write_;
|
||||
} config_popup_;
|
||||
|
||||
|
||||
void SetUp() {
|
||||
const auto& mode = data().mode;
|
||||
const auto& mode = mem_->mode;
|
||||
nf7::NFile::Flags flags = 0;
|
||||
if (std::string::npos != mode.find('r')) flags |= nf7::NFile::kRead;
|
||||
if (std::string::npos != mode.find('w')) flags |= nf7::NFile::kWrite;
|
||||
@ -184,7 +182,7 @@ class NFile final : public nf7::FileBase,
|
||||
th_->Push(ctx, Runner::Task {
|
||||
.callee = nullptr,
|
||||
.caller = nullptr,
|
||||
.func = [shared = shared_, npath = data().npath, flags]() {
|
||||
.func = [shared = shared_, npath = mem_->npath, flags]() {
|
||||
shared->nfile.emplace(npath, flags);
|
||||
return nf7::Value::Pulse {};
|
||||
},
|
||||
@ -210,9 +208,11 @@ class NFile::Lambda final : public nf7::Node::Lambda,
|
||||
if (type == "lock") {
|
||||
const auto ex = v.tuple("ex").boolean();
|
||||
Push(in.sender, ex, []() { return nf7::Value::Pulse {}; });
|
||||
|
||||
} else if (type == "unlock") {
|
||||
lock_ = std::nullopt;
|
||||
in.sender->Handle("result", nf7::Value::Pulse {}, shared_from_this());
|
||||
|
||||
} else if (type == "read") {
|
||||
const auto offset = v.tuple("offset").integer<size_t>();
|
||||
const auto size = v.tuple("size").integer<size_t>();
|
||||
@ -223,6 +223,7 @@ class NFile::Lambda final : public nf7::Node::Lambda,
|
||||
buf.resize(actual);
|
||||
return nf7::Value {std::move(buf)};
|
||||
});
|
||||
|
||||
} else if (type == "write") {
|
||||
const auto offset = v.tuple("offset").integer<size_t>();
|
||||
const auto buf = v.tuple("buf").vector();
|
||||
@ -230,12 +231,14 @@ class NFile::Lambda final : public nf7::Node::Lambda,
|
||||
const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size());
|
||||
return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
|
||||
});
|
||||
|
||||
} else if (type == "truncate") {
|
||||
const auto size = v.tuple("size").integer<size_t>();
|
||||
Push(in.sender, true, [this, size]() {
|
||||
shared_->nfile->Truncate(size);
|
||||
return nf7::Value::Pulse {};
|
||||
});
|
||||
|
||||
} else {
|
||||
throw nf7::Exception {"unknown command type: "+type};
|
||||
}
|
||||
@ -271,60 +274,15 @@ std::shared_ptr<nf7::Node::Lambda> NFile::CreateLambda(
|
||||
}
|
||||
|
||||
|
||||
void NFile::Update() noexcept {
|
||||
nf7::FileBase::Update();
|
||||
|
||||
// file update check
|
||||
try {
|
||||
const auto npath = env().npath() / data().npath;
|
||||
const auto lastmod = std::filesystem::last_write_time(npath);
|
||||
if (std::exchange(lastmod_, lastmod) < lastmod) {
|
||||
Touch();
|
||||
}
|
||||
} catch (std::filesystem::filesystem_error&) {
|
||||
}
|
||||
}
|
||||
void NFile::UpdateMenu() noexcept {
|
||||
if (ImGui::MenuItem("config")) {
|
||||
config_popup_.Open();
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
void NFile::UpdateTooltip() noexcept {
|
||||
ImGui::Text("npath: %s", data().npath.generic_string().c_str());
|
||||
ImGui::Text("mode : %s", data().mode.c_str());
|
||||
}
|
||||
void NFile::UpdateWidget() noexcept {
|
||||
ImGui::TextUnformatted("System/NFile");
|
||||
|
||||
if (ImGui::Button("config")) {
|
||||
config_popup_.Open();
|
||||
}
|
||||
config_popup_.Update();
|
||||
}
|
||||
void NFile::ConfigPopup::Update() noexcept {
|
||||
if (nf7::gui::Popup::Begin()) {
|
||||
ImGui::InputText("path", &npath_);
|
||||
ImGui::Checkbox("read", &read_);
|
||||
ImGui::Checkbox("write", &write_);
|
||||
|
||||
if (ImGui::Button("ok")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
auto& d = f_->data();
|
||||
d.npath = npath_;
|
||||
|
||||
d.mode = "";
|
||||
if (read_) d.mode += "r";
|
||||
if (write_) d.mode += "w";
|
||||
|
||||
f_->mem_.Commit();
|
||||
}
|
||||
if (!std::filesystem::exists(f_->env().npath()/npath_)) {
|
||||
ImGui::Bullet();
|
||||
ImGui::TextUnformatted("file not found");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::Text("npath: %s", mem_->npath.generic_string().c_str());
|
||||
ImGui::Text("mode : %s", mem_->mode.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,6 @@
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/util_algorithm.hh"
|
||||
#include "common/util_string.hh"
|
||||
#include "common/value.hh"
|
||||
#include "common/yas_enum.hh"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user