simplify code of config UI

This commit is contained in:
falsycat 2022-11-05 13:38:46 +09:00
parent 69690f2e29
commit 245884fae7
17 changed files with 659 additions and 1094 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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()};
}

View File

@ -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(");
}

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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;
}
}

View File

@ -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")) {

View File

@ -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());
}
}

View File

@ -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"