add Sequencer/Adaptor
This commit is contained in:
parent
e01039b74e
commit
61bccc3acb
@ -115,6 +115,7 @@ target_sources(nf7
|
||||
file/node_imm.cc
|
||||
file/node_network.cc
|
||||
file/node_ref.cc
|
||||
file/sequencer_adaptor.cc
|
||||
file/sequencer_call.cc
|
||||
file/sequencer_timeline.cc
|
||||
file/system_dir.cc
|
||||
|
217
common/gui_value.hh
Normal file
217
common/gui_value.hh
Normal file
@ -0,0 +1,217 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
class Value {
|
||||
public:
|
||||
enum Type {
|
||||
kPulse,
|
||||
kInteger,
|
||||
kScalar,
|
||||
kNormalizedScalar,
|
||||
kString,
|
||||
kMultilineString,
|
||||
};
|
||||
|
||||
static const char* StringifyType(Type t) noexcept {
|
||||
switch (t) {
|
||||
case kPulse: return "Pulse";
|
||||
case kInteger: return "Integer";
|
||||
case kScalar: return "Scalar";
|
||||
case kNormalizedScalar: return "NormalizedScalar";
|
||||
case kString: return "kString";
|
||||
case kMultilineString: return "kMultilineString";
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
static Type ParseType(std::string_view v) {
|
||||
return
|
||||
v == "Pulse"? kPulse:
|
||||
v == "Integer"? kInteger:
|
||||
v == "Scalar"? kScalar:
|
||||
v == "NormalizedScalar"? kNormalizedScalar:
|
||||
v == "String"? kString:
|
||||
v == "MultilineString"? kMultilineString:
|
||||
throw nf7::DeserializeException {"unknown type: "+std::string {v}};
|
||||
}
|
||||
|
||||
Value() = default;
|
||||
Value(const Value&) = default;
|
||||
Value(Value&&) = default;
|
||||
Value& operator=(const Value&) = default;
|
||||
Value& operator=(Value&&) = default;
|
||||
|
||||
bool ReplaceType(Type t) noexcept {
|
||||
if (type_ == t) return false;
|
||||
|
||||
type_ = t;
|
||||
switch (type_) {
|
||||
case nf7::gui::Value::kPulse:
|
||||
entity_ = nf7::Value::Pulse {};
|
||||
break;
|
||||
case nf7::gui::Value::kInteger:
|
||||
entity_ = nf7::Value::Integer {0};
|
||||
break;
|
||||
case nf7::gui::Value::kScalar:
|
||||
case nf7::gui::Value::kNormalizedScalar:
|
||||
entity_ = nf7::Value::Scalar {0};
|
||||
break;
|
||||
case nf7::gui::Value::kString:
|
||||
case nf7::gui::Value::kMultilineString:
|
||||
entity_ = nf7::Value::String {};
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReplaceEntity(const nf7::Value& v) noexcept {
|
||||
entity_ = v;
|
||||
ValidateValue();
|
||||
}
|
||||
void ReplaceEntity(nf7::Value&& v) noexcept {
|
||||
entity_ = std::move(v);
|
||||
ValidateValue();
|
||||
}
|
||||
void ValidateValue() const {
|
||||
bool valid = true;
|
||||
switch (type_) {
|
||||
case nf7::gui::Value::kPulse:
|
||||
valid = entity_.isPulse();
|
||||
break;
|
||||
case nf7::gui::Value::kInteger:
|
||||
valid = entity_.isInteger();
|
||||
break;
|
||||
case nf7::gui::Value::kScalar:
|
||||
case nf7::gui::Value::kNormalizedScalar:
|
||||
valid = entity_.isScalar();
|
||||
break;
|
||||
case nf7::gui::Value::kString:
|
||||
case nf7::gui::Value::kMultilineString:
|
||||
valid = entity_.isString();
|
||||
break;
|
||||
}
|
||||
if (!valid) {
|
||||
throw nf7::DeserializeException {"invalid entity type"};
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateEditor() noexcept {
|
||||
bool ret = false;
|
||||
|
||||
ImGui::Button("T");
|
||||
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
if (ImGui::MenuItem("Pulse", nullptr, type_ == kPulse)) {
|
||||
ret |= ReplaceType(kPulse);
|
||||
}
|
||||
if (ImGui::MenuItem("Integer", nullptr, type_ == kInteger)) {
|
||||
ret |= ReplaceType(kInteger);
|
||||
}
|
||||
if (ImGui::MenuItem("Scalar", nullptr, type_ == kScalar)) {
|
||||
ret |= ReplaceType(kScalar);
|
||||
}
|
||||
if (ImGui::MenuItem("Normalized Scalar", nullptr, type_ == kNormalizedScalar)) {
|
||||
ret |= ReplaceType(kNormalizedScalar);
|
||||
}
|
||||
if (ImGui::MenuItem("String", nullptr, type_ == kString)) {
|
||||
ret |= ReplaceType(kString);
|
||||
}
|
||||
if (ImGui::MenuItem("Multiline String", nullptr, type_ == kMultilineString)) {
|
||||
ret |= ReplaceType(kMultilineString);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
const auto w = ImGui::CalcItemWidth();
|
||||
ImGui::PushItemWidth(w);
|
||||
switch (type_) {
|
||||
case kPulse:
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button("PULSE", {w, 0});
|
||||
ImGui::EndDisabled();
|
||||
break;
|
||||
case kInteger:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_S64, &entity_.integer());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kScalar:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kNormalizedScalar:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kString:
|
||||
ImGui::InputTextWithHint("##value", "string", &entity_.string());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kMultilineString:
|
||||
ImGui::InputTextMultiline("##value", &entity_.string(), {w, 2.4f*em});
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Type type() const noexcept { return type_; }
|
||||
const nf7::Value& entity() const noexcept { return entity_; }
|
||||
|
||||
private:
|
||||
Type type_ = kInteger;
|
||||
nf7::Value entity_ = nf7::Value::Integer {0};
|
||||
};
|
||||
|
||||
} // namespace nf7::gui
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::gui::Value> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const nf7::gui::Value& v) {
|
||||
ar(std::string_view {v.StringifyType(v.type())}, v.entity());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, nf7::gui::Value& v) {
|
||||
std::string type;
|
||||
nf7::Value entity;
|
||||
ar(type, entity);
|
||||
|
||||
v.ReplaceType(v.ParseType(type));
|
||||
v.ReplaceEntity(entity);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
413
file/sequencer_adaptor.cc
Normal file
413
file/sequencer_adaptor.cc
Normal file
@ -0,0 +1,413 @@
|
||||
#include "nf7.hh"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <yas/types/std/string.hpp>
|
||||
#include <yas/types/std/variant.hpp>
|
||||
#include <yas/types/std/vector.hpp>
|
||||
|
||||
#include "common/file_base.hh"
|
||||
#include "common/file_holder.hh"
|
||||
#include "common/generic_context.hh"
|
||||
#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"
|
||||
#include "common/sequencer.hh"
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
namespace {
|
||||
|
||||
class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
|
||||
public:
|
||||
static inline const nf7::GenericTypeInfo<Adaptor> kType =
|
||||
{"Sequencer/Adaptor", {"nf7::Sequencer"}};
|
||||
static void UpdateTypeTooltip() noexcept {
|
||||
ImGui::TextUnformatted("Wraps and Adapts other Sequencer.");
|
||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
||||
"implements nf7::Sequencer");
|
||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
||||
"changes will be applied to active lambdas immediately");
|
||||
}
|
||||
|
||||
class Session;
|
||||
class Lambda;
|
||||
class SessionLambda;
|
||||
|
||||
struct Var {
|
||||
std::string name;
|
||||
bool peek = false;
|
||||
|
||||
void serialize(auto& ar) {
|
||||
ar(name, peek);
|
||||
}
|
||||
};
|
||||
struct Data {
|
||||
Data(Adaptor& f, const Data* src) noexcept : target(f.target_) {
|
||||
if (src) {
|
||||
input_imm = src->input_imm;
|
||||
input_map = src->input_map;
|
||||
output_map = src->output_map;
|
||||
}
|
||||
}
|
||||
|
||||
nf7::FileHolder::Tag target;
|
||||
|
||||
std::vector<std::pair<std::string, nf7::gui::Value>> input_imm;
|
||||
std::vector<std::pair<std::string, Var>> input_map;
|
||||
std::vector<std::pair<std::string, std::string>> output_map;
|
||||
};
|
||||
|
||||
Adaptor(Env& env, const nf7::FileHolder* target = nullptr, const Data* data = nullptr) noexcept :
|
||||
nf7::FileBase(kType, env, {&target_, &target_popup_}),
|
||||
Sequencer(Sequencer::kCustomItem |
|
||||
Sequencer::kTooltip |
|
||||
Sequencer::kParamPanel),
|
||||
life_(*this),
|
||||
target_(*this, "target", target),
|
||||
target_editor_(*this,
|
||||
[](auto& t) { return t.flags().contains("nf7::Sequencer"); }),
|
||||
target_popup_("TargetEditorPopup",
|
||||
"Sequencer/Adaptor: replacing target...",
|
||||
target_editor_),
|
||||
mem_(*this, Data {*this, data}) {
|
||||
target_.onChildMementoChange = [this]() { mem_.Commit(); };
|
||||
|
||||
target_popup_.onOpen = [this]() {
|
||||
target_editor_.Reset(target_);
|
||||
};
|
||||
target_popup_.onDone = [this]() {
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "updating target");
|
||||
this->env().ExecMain(ctx, [this]() {
|
||||
target_editor_.Apply(target_);
|
||||
mem_.Commit();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Adaptor(Env& env, Deserializer& ar) : Adaptor(env) {
|
||||
ar(target_, data().input_imm, data().input_map, data().output_map);
|
||||
}
|
||||
void Serialize(Serializer& ar) const noexcept override {
|
||||
ar(target_, data().input_imm, data().input_map, data().output_map);
|
||||
}
|
||||
std::unique_ptr<File> Clone(Env& env) const noexcept override {
|
||||
return std::make_unique<Adaptor>(env, &target_, &data());
|
||||
}
|
||||
|
||||
std::shared_ptr<Sequencer::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Context>&) noexcept override;
|
||||
|
||||
void UpdateItem(Sequencer::Editor&) noexcept override;
|
||||
void UpdateParamPanel(Sequencer::Editor&) noexcept override;
|
||||
void UpdateTooltip(Sequencer::Editor&) noexcept override;
|
||||
|
||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
return InterfaceSelector<
|
||||
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Adaptor> life_;
|
||||
nf7::FileHolder target_;
|
||||
|
||||
nf7::gui::FileHolderEditor target_editor_;
|
||||
nf7::gui::PopupWrapper<nf7::gui::FileHolderEditor> target_popup_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
const Data& data() const noexcept { return mem_.data(); }
|
||||
Data& data() noexcept { return mem_.data(); }
|
||||
};
|
||||
|
||||
|
||||
class Adaptor::Session final : public nf7::Sequencer::Session {
|
||||
public:
|
||||
// ensure that Adaptor is alive
|
||||
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept {
|
||||
for (auto& p : f.data().input_imm) {
|
||||
vars_[p.first] = p.second.entity();
|
||||
}
|
||||
for (auto& p : f.data().input_map) {
|
||||
if (p.second.name.size() == 0) continue;
|
||||
if (p.second.peek) {
|
||||
if (const auto ptr = parent->Peek(p.second.name)) {
|
||||
vars_[p.first] = *ptr;
|
||||
}
|
||||
} else {
|
||||
if (auto ptr = parent->Receive(p.second.name)) {
|
||||
vars_[p.first] = std::move(*ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& p : f.data().output_map) {
|
||||
outs_[p.first] = p.second;
|
||||
}
|
||||
}
|
||||
|
||||
const nf7::Value* Peek(std::string_view name) noexcept override {
|
||||
assert(parent_);
|
||||
auto itr = vars_.find(std::string {name});
|
||||
return itr != vars_.end()? &itr->second: nullptr;
|
||||
}
|
||||
std::optional<nf7::Value> Receive(std::string_view name) noexcept override {
|
||||
assert(parent_);
|
||||
|
||||
auto itr = vars_.find(std::string {name});
|
||||
if (itr == vars_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto ret = std::move(itr->second);
|
||||
vars_.erase(itr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Send(std::string_view name, nf7::Value&& v) noexcept override {
|
||||
assert(parent_);
|
||||
auto itr = outs_.find(std::string {name});
|
||||
if (itr != outs_.end()) {
|
||||
parent_->Send(itr->second, std::move(v));
|
||||
}
|
||||
}
|
||||
|
||||
void Finish() noexcept override {
|
||||
parent_->Finish();
|
||||
parent_ = nullptr;
|
||||
}
|
||||
|
||||
const Info& info() const noexcept override { return parent_->info(); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<nf7::Sequencer::Session> parent_;
|
||||
|
||||
std::unordered_map<std::string, nf7::Value> vars_;
|
||||
std::unordered_map<std::string, std::string> outs_;
|
||||
};
|
||||
class Adaptor::Lambda final : public nf7::Sequencer::Lambda,
|
||||
public std::enable_shared_from_this<Adaptor::Lambda> {
|
||||
public:
|
||||
Lambda(Adaptor& f, const std::shared_ptr<nf7::Context>& parent) noexcept :
|
||||
nf7::Sequencer::Lambda(f, parent), f_(f.life_) {
|
||||
}
|
||||
|
||||
void Run(const std::shared_ptr<nf7::Sequencer::Session>& ss) noexcept override
|
||||
try {
|
||||
f_.EnforceAlive();
|
||||
|
||||
auto& target = f_->target_.GetFileOrThrow();
|
||||
auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
|
||||
if (!la_ || target.id() != cached_id_) {
|
||||
la_ = seq.CreateLambda(shared_from_this());
|
||||
cached_id_ = target.id();
|
||||
}
|
||||
la_->Run(std::make_shared<Adaptor::Session>(*f_, ss));
|
||||
} catch (nf7::Exception&) {
|
||||
ss->Finish();
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Adaptor>::Ref f_;
|
||||
|
||||
nf7::File::Id cached_id_ = 0;
|
||||
std::shared_ptr<nf7::Sequencer::Lambda> la_;
|
||||
};
|
||||
std::shared_ptr<nf7::Sequencer::Lambda> Adaptor::CreateLambda(
|
||||
const std::shared_ptr<nf7::Context>& parent) noexcept {
|
||||
return std::make_shared<Adaptor::Lambda>(*this, parent);
|
||||
}
|
||||
|
||||
|
||||
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
|
||||
const auto em = ImGui::GetFontSize();
|
||||
ImGui::SetCursorPos({.25f*em, .25f*em});
|
||||
if (target_.UpdateButton(true)) {
|
||||
target_popup_.Open();
|
||||
}
|
||||
}
|
||||
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
||||
bool commit = false;
|
||||
|
||||
auto& imm = data().input_imm;
|
||||
auto& inputs = data().input_map;
|
||||
auto& outputs = data().output_map;
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (target_.UpdateButtonWithLabel("target")) {
|
||||
target_popup_.Open();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("table", 3)) {
|
||||
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
|
||||
ImGui::TableSetupColumn("arrow", ImGuiTableColumnFlags_WidthFixed, 1*em);
|
||||
ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthStretch, 1.f);
|
||||
|
||||
// ---- immediate values
|
||||
ImGui::PushID("imm");
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted("imm input");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+")) {
|
||||
imm.push_back({"target_input", {}});
|
||||
commit = true;
|
||||
}
|
||||
if (imm.size() == 0) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("no rule");
|
||||
}
|
||||
for (size_t i = 0; i < imm.size(); ++i) {
|
||||
auto& p = imm[i];
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
commit |= p.second.UpdateEditor();
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("->");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##name", "dst", &p.first);
|
||||
commit |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// ---- input map
|
||||
ImGui::PushID("input");
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted("input");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+")) {
|
||||
inputs.push_back({"target_input", {}});
|
||||
commit = true;
|
||||
}
|
||||
if (inputs.size() == 0) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("no rule");
|
||||
}
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
auto& in = inputs[i];
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
const char* text = in.second.peek? "P": "R";
|
||||
if (ImGui::Button(text)) {
|
||||
in.second.peek = !in.second.peek;
|
||||
commit = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##src", "src", &in.second.name);
|
||||
commit |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("->");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##dst", "dst", &in.first);
|
||||
commit |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// ---- output map
|
||||
ImGui::PushID("output");
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted("output");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+")) {
|
||||
outputs.push_back({"target_output", ""});
|
||||
commit = true;
|
||||
}
|
||||
if (outputs.size() == 0) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("no rule");
|
||||
}
|
||||
for (size_t i = 0; i < outputs.size(); ++i) {
|
||||
auto& out = outputs[i];
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##src", "src", &out.first);
|
||||
commit |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("->");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##dst", "dst", &out.second);
|
||||
commit |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
imm.erase(
|
||||
std::remove_if(
|
||||
imm.begin(), imm.end(),
|
||||
[](auto& x) { return x.first.size() == 0; }),
|
||||
imm.end());
|
||||
inputs.erase(
|
||||
std::remove_if(
|
||||
inputs.begin(), inputs.end(),
|
||||
[](auto& x) { return x.first.size() == 0; }),
|
||||
inputs.end());
|
||||
outputs.erase(
|
||||
std::remove_if(
|
||||
outputs.begin(), outputs.end(),
|
||||
[](auto& x) { return x.first.size() == 0; }),
|
||||
outputs.end());
|
||||
mem_.Commit();
|
||||
}
|
||||
}
|
||||
void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept {
|
||||
ImGui::TextUnformatted("Sequencer/Adaptor");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nf7
|
||||
|
Loading…
x
Reference in New Issue
Block a user