improve nf7::gui::Window

This commit is contained in:
falsycat 2022-11-05 22:55:05 +09:00
parent dd14217f5b
commit bb799adfb4
7 changed files with 494 additions and 525 deletions

View File

@ -1,72 +1,57 @@
#pragma once
#include <utility>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#include <imgui.h>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
namespace nf7::gui {
class Window {
class Window : public nf7::FileBase::Feature {
public:
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
return f.abspath().Stringify() + " | " + std::string {name};
}
Window() = delete;
Window(File& owner, std::string_view title, const gui::Window* src = nullptr) noexcept :
owner_(&owner), title_(title),
shown_(src? src->shown_: false) {
Window(nf7::FileBase& owner, std::string_view title) noexcept :
nf7::FileBase::Feature(owner), owner_(&owner), title_(title), shown_(false) {
}
Window(const Window&) = delete;
Window(Window&&) = delete;
Window& operator=(const Window&) = delete;
Window& operator=(Window&&) = delete;
bool Begin() noexcept {
if (std::exchange(set_focus_, false)) {
ImGui::SetNextWindowFocus();
shown_ = true;
}
if (!shown_) return false;
need_end_ = true;
return ImGui::Begin(id().c_str(), &shown_);
}
void End() noexcept {
if (need_end_) {
ImGui::End();
need_end_ = false;
}
void serialize(auto& ar) {
ar(shown_);
}
void Show() noexcept {
shown_ = true;
}
void SetFocus() noexcept {
shown_ = true;
set_focus_ = true;
}
template <typename Ar>
Ar& serialize(Ar& ar) {
ar(shown_);
return ar;
}
std::string id() const noexcept {
return ConcatId(*owner_, title_);
}
bool shownInCurrentFrame() const noexcept {
return shown_ || set_focus_;
bool MenuItem() noexcept {
return ImGui::MenuItem(title_.c_str(), nullptr, &shown_);
}
std::string id() const noexcept { return ConcatId(*owner_, title_); }
bool shown() const noexcept { return shown_; }
bool& shown() noexcept { return shown_; }
std::function<void()> onConfig = [](){};
std::function<void()> onUpdate;
private:
File* const owner_;
@ -77,6 +62,31 @@ class Window {
// persistent params
bool shown_;
void Handle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kReqFocus:
SetFocus();
return;
default:
return;
}
}
void Update() noexcept override {
if (std::exchange(set_focus_, false)) {
ImGui::SetNextWindowFocus();
shown_ = true;
}
if (!shown_) return;
onConfig();
if (ImGui::Begin(id().c_str(), &shown_)) {
onUpdate();
}
ImGui::End();
}
};
} // namespace nf7::gui

View File

@ -109,6 +109,11 @@ class ObjBase : public nf7::FileBase,
if constexpr (HasWindow<T>) {
win_.emplace(*this, T::kWindowTitle);
win_->onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
};
win_->onUpdate = [this]() { mem_->UpdateWindow(fu_); };
}
}
@ -170,22 +175,6 @@ class ObjBase : public nf7::FileBase,
});
}
void Update() noexcept override {
nf7::FileBase::Update();
if constexpr (HasWindow<T>) {
if (win_->shownInCurrentFrame()) {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
}
if (win_->Begin()) {
mem_->UpdateWindow(fu_);
}
win_->End();
}
}
void UpdateMenu() noexcept override {
if (ImGui::BeginMenu("object management")) {
if (ImGui::MenuItem("create", nullptr, false, !fu_)) {
@ -211,7 +200,7 @@ class ObjBase : public nf7::FileBase,
}
if constexpr (HasWindow<T>) {
ImGui::Separator();
ImGui::MenuItem(T::kWindowTitle, nullptr, &win_->shown());
win_->MenuItem();
}
}
void UpdateTooltip() noexcept override {

View File

@ -109,7 +109,6 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
};
Network(nf7::Env& env,
const gui::Window* win = nullptr,
ItemList&& items = {},
nf7::NodeLinkStore&& links = {},
Data&& d = {}) :
@ -119,9 +118,15 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kNone),
life_(*this),
win_(*this, "Editor Node/Network", win),
win_(*this, "Editor Node/Network"),
items_(std::move(items)), links_(std::move(links)),
mem_(std::move(d), *this) {
win_.onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({36*em, 36*em}, ImGuiCond_FirstUseEver);
};
win_.onUpdate = [this]() { NetworkEditor(); };
Sanitize();
}
~Network() noexcept {
@ -142,7 +147,7 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
items.push_back(std::make_unique<Item>(env, *item));
}
return std::make_unique<Network>(
env, &win_, std::move(items), NodeLinkStore(links_));
env, std::move(items), NodeLinkStore(links_));
}
File* Find(std::string_view name) const noexcept;
@ -243,6 +248,7 @@ class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Nod
}
// gui
void NetworkEditor() noexcept;
void ItemAdder(const ImVec2&) noexcept;
void Config() noexcept;
@ -964,8 +970,6 @@ void Network::Item::Watcher::Handle(const File::Event& ev) noexcept {
void Network::Update() noexcept {
nf7::FileBase::Update();
const auto em = ImGui::GetFontSize();
// forget expired lambdas
lambdas_running_.erase(
std::remove_if(lambdas_running_.begin(), lambdas_running_.end(),
@ -977,132 +981,13 @@ void Network::Update() noexcept {
item->Update();
}
// ---- editor window
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({36*em, 36*em}, ImGuiCond_FirstUseEver);
}
if (win_.Begin()) {
// ---- editor window / toolbar
ImGui::BeginGroup();
{
// ---- editor window / toolbar / attached lambda combo
const auto current_lambda =
!lambda_? "(unselected)"s:
lambda_->depth() == 0? "(isolated)"s:
nf7::gui::GetContextDisplayName(*lambda_);
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
if (lambda_) {
if (ImGui::Selectable("detach current lambda")) {
AttachLambda(nullptr);
}
ImGui::Separator();
}
for (const auto& wptr : lambdas_running_) {
auto ptr = wptr.lock();
if (!ptr) continue;
const auto name = nf7::gui::GetContextDisplayName(*ptr);
if (ImGui::Selectable(name.c_str(), ptr == lambda_)) {
AttachLambda(nullptr);
lambda_ = ptr;
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("call stack:");
ImGui::Indent();
nf7::gui::ContextStack(*ptr);
ImGui::Unindent();
ImGui::EndTooltip();
}
}
if (lambdas_running_.size() == 0) {
ImGui::TextDisabled("no running lambda found...");
}
ImGui::EndCombo();
}
}
ImGui::EndGroup();
// ---- editor window / canvas
if (ImGui::BeginChild("canvas", {0, 0}, false, ImGuiWindowFlags_NoMove)) {
canvas_pos_ = ImGui::GetCursorScreenPos();
ImNodes::BeginCanvas(&canvas_);
// update child nodes
auto ed = Network::Editor {*this};
for (const auto& item : items_) {
item->UpdateNode(ed);
}
// handle existing links
for (const auto& lk : links_.items()) {
const auto src_id = reinterpret_cast<void*>(lk.src_id);
const auto dst_id = reinterpret_cast<void*>(lk.dst_id);
const auto src_name = lk.src_name.c_str();
const auto dst_name = lk.dst_name.c_str();
if (!ImNodes::Connection(dst_id, dst_name, src_id, src_name)) {
ExecUnlink(lk);
}
}
// handle new link
void* src_ptr;
const char* src_name;
void* dst_ptr;
const char* dst_name;
if (ImNodes::GetNewConnection(&dst_ptr, &dst_name, &src_ptr, &src_name)) {
ExecLink({
.src_id = reinterpret_cast<ItemId>(src_ptr),
.src_name = src_name,
.dst_id = reinterpret_cast<ItemId>(dst_ptr),
.dst_name = dst_name,
});
}
ImNodes::EndCanvas();
// popup menu for canvas
constexpr auto kFlags =
ImGuiPopupFlags_MouseButtonRight |
ImGuiPopupFlags_NoOpenOverExistingPopup;
if (ImGui::BeginPopupContextWindow(nullptr, kFlags)) {
const auto pos =
GetCanvasPosFromScreenPos(ImGui::GetMousePosOnOpeningCurrentPopup());
if (ImGui::BeginMenu("add")) {
ItemAdder(pos);
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
UnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ReDo();
}
ImGui::Separator();
if (ImGui::MenuItem("reset canvas zoom")) {
canvas_.Zoom = 1.f;
}
ImGui::Separator();
if (ImGui::BeginMenu("config")) {
Config();
ImGui::EndMenu();
}
ImGui::EndPopup();
}
}
ImGui::EndChild();
}
win_.End();
// squash queued commands
if (history_.Squash()) {
Touch();
}
}
void Network::UpdateMenu() noexcept {
if (ImGui::MenuItem("Editor", nullptr, &win_.shown()) && win_.shown()) {
win_.SetFocus();
}
win_.MenuItem();
if (ImGui::BeginMenu("config")) {
Config();
ImGui::EndMenu();
@ -1119,61 +1004,119 @@ void Network::UpdateWidget() noexcept {
Config();
}
void Network::Item::UpdateNode(Node::Editor& ed) noexcept {
assert(owner_);
ImGui::PushID(node_);
const auto id = reinterpret_cast<void*>(id_);
if (ImNodes::BeginNode(id, &pos_, &select_)) {
if (node_->flags() & nf7::Node::kCustomNode) {
node_->UpdateNode(ed);
} else {
ImGui::TextUnformatted(file_->type().name().c_str());
nf7::gui::NodeInputSockets(node_->GetInputs());
ImGui::SameLine();
nf7::gui::NodeOutputSockets(node_->GetOutputs());
void Network::NetworkEditor() noexcept {
// ---- editor window / toolbar
ImGui::BeginGroup();
{
// ---- editor window / toolbar / attached lambda combo
const auto current_lambda =
!lambda_? "(unselected)"s:
lambda_->depth() == 0? "(isolated)"s:
nf7::gui::GetContextDisplayName(*lambda_);
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
if (lambda_) {
if (ImGui::Selectable("detach current lambda")) {
AttachLambda(nullptr);
}
ImGui::Separator();
}
for (const auto& wptr : lambdas_running_) {
auto ptr = wptr.lock();
if (!ptr) continue;
const auto name = nf7::gui::GetContextDisplayName(*ptr);
if (ImGui::Selectable(name.c_str(), ptr == lambda_)) {
AttachLambda(nullptr);
lambda_ = ptr;
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("call stack:");
ImGui::Indent();
nf7::gui::ContextStack(*ptr);
ImGui::Unindent();
ImGui::EndTooltip();
}
}
if (lambdas_running_.size() == 0) {
ImGui::TextDisabled("no running lambda found...");
}
ImGui::EndCombo();
}
}
ImNodes::EndNode();
ImGui::EndGroup();
const bool moved =
pos_.x != prev_pos_.x || pos_.y != prev_pos_.y;
if (moved && !ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
owner_->history_.Add(std::make_unique<Item::MoveCommand>(*this, prev_pos_));
prev_pos_ = pos_;
}
// ---- editor window / canvas
if (ImGui::BeginChild("canvas", {0, 0}, false, ImGuiWindowFlags_NoMove)) {
canvas_pos_ = ImGui::GetCursorScreenPos();
ImNodes::BeginCanvas(&canvas_);
constexpr auto kFlags =
ImGuiPopupFlags_MouseButtonRight |
ImGuiPopupFlags_NoOpenOverExistingPopup;
if (ImGui::BeginPopupContextItem(nullptr, kFlags)) {
const auto pos =
owner_->GetCanvasPosFromScreenPos(
ImGui::GetMousePosOnOpeningCurrentPopup());
if (ImGui::MenuItem("remove")) {
owner_->ExecRemoveItem(id_);
// update child nodes
auto ed = Network::Editor {*this};
for (const auto& item : items_) {
item->UpdateNode(ed);
}
if (ImGui::MenuItem("clone")) {
owner_->ExecAddItem(
std::make_unique<Item>(owner_->next_++, file_->Clone(env())), pos);
// handle existing links
for (const auto& lk : links_.items()) {
const auto src_id = reinterpret_cast<void*>(lk.src_id);
const auto dst_id = reinterpret_cast<void*>(lk.dst_id);
const auto src_name = lk.src_name.c_str();
const auto dst_name = lk.dst_name.c_str();
if (!ImNodes::Connection(dst_id, dst_name, src_id, src_name)) {
ExecUnlink(lk);
}
}
if (node_->flags() & nf7::Node::kMenu_DirItem) {
// handle new link
void* src_ptr;
const char* src_name;
void* dst_ptr;
const char* dst_name;
if (ImNodes::GetNewConnection(&dst_ptr, &dst_name, &src_ptr, &src_name)) {
ExecLink({
.src_id = reinterpret_cast<ItemId>(src_ptr),
.src_name = src_name,
.dst_id = reinterpret_cast<ItemId>(dst_ptr),
.dst_name = dst_name,
});
}
ImNodes::EndCanvas();
// popup menu for canvas
constexpr auto kFlags =
ImGuiPopupFlags_MouseButtonRight |
ImGuiPopupFlags_NoOpenOverExistingPopup;
if (ImGui::BeginPopupContextWindow(nullptr, kFlags)) {
const auto pos =
GetCanvasPosFromScreenPos(ImGui::GetMousePosOnOpeningCurrentPopup());
if (ImGui::BeginMenu("add")) {
ItemAdder(pos);
ImGui::EndMenu();
}
ImGui::Separator();
auto dir = file_->interface<nf7::DirItem>();
assert(dir);
dir->UpdateMenu();
}
if (node_->flags() & nf7::Node::kMenu) {
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
UnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ReDo();
}
ImGui::Separator();
node_->UpdateMenu(ed);
if (ImGui::MenuItem("reset canvas zoom")) {
canvas_.Zoom = 1.f;
}
ImGui::Separator();
if (ImGui::BeginMenu("config")) {
Config();
ImGui::EndMenu();
}
ImGui::EndPopup();
}
ImGui::EndPopup();
}
ImGui::PopID();
ImGui::EndChild();
}
void Network::ItemAdder(const ImVec2& pos) noexcept {
static const nf7::File::TypeInfo* type;
if (ImGui::IsWindowAppearing()) {
@ -1241,6 +1184,61 @@ void Network::Config() noexcept {
}
void Network::Item::UpdateNode(Node::Editor& ed) noexcept {
assert(owner_);
ImGui::PushID(node_);
const auto id = reinterpret_cast<void*>(id_);
if (ImNodes::BeginNode(id, &pos_, &select_)) {
if (node_->flags() & nf7::Node::kCustomNode) {
node_->UpdateNode(ed);
} else {
ImGui::TextUnformatted(file_->type().name().c_str());
nf7::gui::NodeInputSockets(node_->GetInputs());
ImGui::SameLine();
nf7::gui::NodeOutputSockets(node_->GetOutputs());
}
}
ImNodes::EndNode();
const bool moved =
pos_.x != prev_pos_.x || pos_.y != prev_pos_.y;
if (moved && !ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
owner_->history_.Add(std::make_unique<Item::MoveCommand>(*this, prev_pos_));
prev_pos_ = pos_;
}
constexpr auto kFlags =
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())), pos);
}
if (node_->flags() & nf7::Node::kMenu_DirItem) {
ImGui::Separator();
auto dir = file_->interface<nf7::DirItem>();
assert(dir);
dir->UpdateMenu();
}
if (node_->flags() & nf7::Node::kMenu) {
ImGui::Separator();
node_->UpdateMenu(ed);
}
ImGui::EndPopup();
}
ImGui::PopID();
}
void Network::Initiator::UpdateNode(nf7::Node::Editor& ed) noexcept {
ImGui::TextUnformatted("INITIATOR");

View File

@ -67,14 +67,14 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
TL(nf7::Env& env,
std::vector<std::unique_ptr<Layer>>&& layers = {},
ItemId next = 1,
const nf7::gui::Window* win = nullptr) noexcept :
ItemId next = 1) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this), log_(*this),
layers_(std::move(layers)), next_(next),
win_(*this, "Timeline Editor", win), tl_("timeline") {
win_(*this, "Timeline Editor"), tl_("timeline") {
win_.onUpdate = [this]() { TimelineEditor(); };
}
~TL() noexcept {
history_.Clear();
@ -103,7 +103,6 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
void Handle(const nf7::File::Event& ev) noexcept;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateWidget() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
@ -175,8 +174,8 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
// gui
void EditorWindow() noexcept;
void ParamPanelWindow() noexcept;
void TimelineEditor() noexcept;
void ParamPanel() noexcept;
void LambdaSelector() noexcept;
void ItemAdder() noexcept;
@ -1089,8 +1088,10 @@ std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
std::vector<std::unique_ptr<TL::Layer>> layers;
layers.reserve(layers_.size());
ItemId next = 1;
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next));
return std::make_unique<TL>(env, std::move(layers), next, &win_);
for (const auto& layer : layers_) {
layers.push_back(layer->Clone(env, next));
}
return std::make_unique<TL>(env, std::move(layers), next);
}
void TL::Handle(const Event& ev) noexcept {
nf7::FileBase::Handle(ev);
@ -1127,188 +1128,174 @@ void TL::Handle(const Event& ev) noexcept {
void TL::Update() noexcept {
nf7::FileBase::Update();
// display param panel window
if (win_.shown()) {
const auto em = ImGui::GetFontSize();
const auto id = nf7::gui::Window::ConcatId(*this, "Parameter Panel");
if (std::exchange(param_panel_request_focus_, false)) {
ImGui::SetNextWindowFocus();
}
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
if (ImGui::Begin(id.c_str())) {
ParamPanel();
}
ImGui::End();
}
// update children
for (const auto& layer : layers_) {
for (const auto& item : layer->items()) {
item->file().Update();
}
}
EditorWindow();
ParamPanelWindow();
// squash queued commands
if (history_.Squash()) {
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
[this]() { Touch(); });
}
}
void TL::UpdateMenu() noexcept {
if (ImGui::MenuItem("editor", nullptr, &win_.shown()) && win_.shown()) {
win_.SetFocus();
}
}
void TL::UpdateWidget() noexcept {
ImGui::TextUnformatted("Sequencer/Timeline");
if (ImGui::Button("Editor")) {
win_.SetFocus();
}
win_.MenuItem();
}
void TL::EditorWindow() noexcept {
if (win_.shownInCurrentFrame()) {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSizeConstraints({32*em, 16*em}, {1e8, 1e8});
}
if (win_.Begin()) {
LambdaSelector();
void TL::TimelineEditor() noexcept {
LambdaSelector();
// timeline
if (tl_.Begin()) {
// layer headers
for (size_t i = 0; i < layers_.size(); ++i) {
auto& layer = layers_[i];
tl_.NextLayerHeader(layer.get(), layer->height());
ImGui::PushID(layer.get());
layer->UpdateHeader(i);
ImGui::PopID();
}
// timeline
if (tl_.Begin()) {
// layer headers
for (size_t i = 0; i < layers_.size(); ++i) {
auto& layer = layers_[i];
tl_.NextLayerHeader(layer.get(), layer->height());
ImGui::PushID(layer.get());
layer->UpdateHeader(i);
ImGui::PopID();
}
if (tl_.BeginBody()) {
// context menu on timeline
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::IsWindowAppearing()) {
action_time_ = tl_.mouseTime();
action_layer_ = 0;
if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
action_layer_ = layer->index();
}
if (tl_.BeginBody()) {
// context menu on timeline
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::IsWindowAppearing()) {
action_time_ = tl_.mouseTime();
action_layer_ = 0;
if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
action_layer_ = layer->index();
}
if (action_layer_ < layers_.size()) {
if (ImGui::BeginMenu("add new item")) {
ItemAdder();
ImGui::EndMenu();
}
}
if (selected_.size()) {
ImGui::Separator();
if (ImGui::MenuItem("deselect")) {
Deselect();
}
}
if (action_layer_ < layers_.size()) {
if (ImGui::BeginMenu("add new item")) {
ItemAdder();
ImGui::EndMenu();
}
}
if (selected_.size()) {
ImGui::Separator();
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
ExecUnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ExecReDo();
}
ImGui::EndPopup();
}
// layer body
for (auto& layer : layers_) {
tl_.NextLayer(layer.get(), layer->height());
for (auto& item : layer->items()) {
const auto& t = item->displayTiming();
const bool select = selected_.contains(item.get());
ImGui::PushStyleColor(
ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.3f));
ImGui::PushStyleColor(
ImGuiCol_Border,
ImGui::GetColorU32(select? ImGuiCol_FrameBgActive: ImGuiCol_Border));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2);
const bool shown = tl_.BeginItem(item.get(), t.begin(), t.end());
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(2);
if (shown) {
item->Update();
}
tl_.EndItem();
if (ImGui::MenuItem("deselect")) {
Deselect();
}
}
}
tl_.EndBody();
// mouse curosr
constexpr auto kFlags =
ImGuiHoveredFlags_ChildWindows |
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
if (ImGui::IsWindowHovered(kFlags)) {
tl_.Cursor(
"mouse",
tl_.GetTimeFromScreenX(ImGui::GetMousePos().x),
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
ImGui::Separator();
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
ExecUnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ExecReDo();
}
ImGui::EndPopup();
}
// frame cursor
tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f));
// layer body
for (auto& layer : layers_) {
tl_.NextLayer(layer.get(), layer->height());
for (auto& item : layer->items()) {
const auto& t = item->displayTiming();
const bool select = selected_.contains(item.get());
// running sessions
if (lambda_) {
const auto now = nf7::Env::Clock::now();
for (auto& wss : lambda_->sessions()) {
auto ss = wss.lock();
if (!ss || ss->done()) continue;
const auto elapsed =
static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - ss->lastActive()).count()) / 1000;
const auto alpha = 1.f - std::clamp(elapsed, 0.f, 1.f)*0.6f;
const auto color = IM_COL32(255, 0, 0, static_cast<uint8_t>(alpha*255));
tl_.Cursor("S", ss->time(), color);
if (ss->layer() > 0) {
tl_.Arrow(ss->time(), ss->layer()-1, color);
ImGui::PushStyleColor(
ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.3f));
ImGui::PushStyleColor(
ImGuiCol_Border,
ImGui::GetColorU32(select? ImGuiCol_FrameBgActive: ImGuiCol_Border));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2);
const bool shown = tl_.BeginItem(item.get(), t.begin(), t.end());
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(2);
if (shown) {
item->Update();
}
tl_.EndItem();
}
}
HandleTimelineAction();
}
tl_.End();
tl_.EndBody();
// key bindings
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
if (focused && !ImGui::IsAnyItemFocused()) {
if (!lambda_ || lambda_->depth() == 0) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
if (cursor_ > 0) MoveCursorTo(cursor_-1);
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
MoveCursorTo(cursor_+1);
// mouse curosr
constexpr auto kFlags =
ImGuiHoveredFlags_ChildWindows |
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
if (ImGui::IsWindowHovered(kFlags)) {
tl_.Cursor(
"mouse",
tl_.GetTimeFromScreenX(ImGui::GetMousePos().x),
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
}
// frame cursor
tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f));
// running sessions
if (lambda_) {
const auto now = nf7::Env::Clock::now();
for (auto& wss : lambda_->sessions()) {
auto ss = wss.lock();
if (!ss || ss->done()) continue;
const auto elapsed =
static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - ss->lastActive()).count()) / 1000;
const auto alpha = 1.f - std::clamp(elapsed, 0.f, 1.f)*0.6f;
const auto color = IM_COL32(255, 0, 0, static_cast<uint8_t>(alpha*255));
tl_.Cursor("S", ss->time(), color);
if (ss->layer() > 0) {
tl_.Arrow(ss->time(), ss->layer()-1, color);
}
}
}
HandleTimelineAction();
}
tl_.End();
// key bindings
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
if (focused && !ImGui::IsAnyItemFocused()) {
if (!lambda_ || lambda_->depth() == 0) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
if (cursor_ > 0) MoveCursorTo(cursor_-1);
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
MoveCursorTo(cursor_+1);
}
}
}
win_.End();
}
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");
}
void TL::ParamPanel() noexcept {
if (auto item = param_panel_target_) {
if (item->seq().flags() & Sequencer::kParamPanel) {
TL::Editor ed {*item};
item->seq().UpdateParamPanel(ed);
} else {
ImGui::TextUnformatted("no item selected");
ImGui::TextUnformatted("item doesn't have parameter panel");
}
} else {
ImGui::TextUnformatted("no item selected");
}
ImGui::End();
}
void TL::LambdaSelector() noexcept {
const auto current_lambda =

View File

@ -36,13 +36,18 @@ 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 :
Dir(nf7::Env& env, ItemMap&& items = {}) noexcept :
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) {
items_(std::move(items)), win_(*this, "Tree View") {
win_.onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
};
win_.onUpdate = [this]() { TreeView(); };
}
Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
@ -113,23 +118,21 @@ class Dir final : public nf7::FileBase,
void Handle(const Event& ev) noexcept override {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case Event::kAdd:
// force to show window if this is the root
if (name() == "$") {
win_.shown() = true;
win_.Show();
}
for (const auto& item : items_) item.second->MoveUnder(*this, item.first);
break;
return;
case Event::kRemove:
for (const auto& item : items_) item.second->Isolate();
break;
case Event::kReqFocus:
win_.SetFocus();
break;
return;
default:
break;
return;
}
}
@ -154,44 +157,20 @@ class Dir final : public nf7::FileBase,
}
// imgui widgets
void TreeView() noexcept;
void ItemAdder() noexcept;
void ItemRenamer(const std::string& name) noexcept;
bool ValidateName(const std::string& name) noexcept;
};
void Dir::Update() noexcept {
nf7::FileBase::Update();
const auto em = ImGui::GetFontSize();
// update children
for (const auto& item : items_) {
ImGui::PushID(item.second.get());
item.second->Update();
ImGui::PopID();
}
// tree view window
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
}
if (win_.Begin()) {
if (ImGui::BeginPopupContextWindow()) {
UpdateMenu();
ImGui::EndPopup();
}
UpdateTree();
if (nf7::gui::dnd::IsFirstAccept()) {
ImGui::SetCursorPos({0, 0});
ImGui::Dummy(ImGui::GetContentRegionAvail());
if (ImGui::BeginDragDropTarget()) {
UpdateDragDropTarget();
ImGui::EndDragDropTarget();
}
}
}
win_.End();
nf7::FileBase::Update();
}
void Dir::UpdateTree() noexcept {
for (const auto& item : items_) {
@ -315,7 +294,7 @@ void Dir::UpdateMenu() noexcept {
ImGui::EndMenu();
}
ImGui::Separator();
ImGui::MenuItem("TreeView", nullptr, &win_.shown());
win_.MenuItem();
}
void Dir::UpdateTooltip() noexcept {
ImGui::Text("children: %zu", items_.size());
@ -348,6 +327,23 @@ try {
}
void Dir::TreeView() noexcept {
if (ImGui::BeginPopupContextWindow()) {
UpdateMenu();
ImGui::EndPopup();
}
UpdateTree();
if (nf7::gui::dnd::IsFirstAccept()) {
ImGui::SetCursorPos({0, 0});
ImGui::Dummy(ImGui::GetContentRegionAvail());
if (ImGui::BeginDragDropTarget()) {
UpdateDragDropTarget();
ImGui::EndDragDropTarget();
}
}
}
void Dir::ItemAdder() noexcept {
static const nf7::File::TypeInfo* type;
static std::string name;

View File

@ -17,6 +17,7 @@
#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"
@ -34,7 +35,7 @@ using namespace std::literals;
namespace nf7 {
namespace {
class Logger final : public nf7::File,
class Logger final : public nf7::FileBase,
public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<Logger> kType = {
@ -110,13 +111,17 @@ class Logger final : public nf7::File,
};
Logger(nf7::Env& env, Data&& d = {}) noexcept :
nf7::File(kType, env), DirItem(DirItem::kMenu),
nf7::FileBase(kType, env), nf7::DirItem(DirItem::kMenu),
mem_(std::move(d), *this), win_(*this, "Log View") {
win_.shown() = true;
mem_.onCommit = mem_.onRestore = [this]() {
store_->param(mem_.data());
};
win_.onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
};
win_.onUpdate = [this]() { LogView(); };
}
Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
@ -142,7 +147,6 @@ class Logger final : public nf7::File,
}
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateRowMenu(const Row&) noexcept;
@ -161,11 +165,13 @@ class Logger final : public nf7::File,
nf7::gui::Window win_;
// log record management
void DropExceededRows() noexcept {
if (rows_.size() <= mem_->max_rows) return;
rows_.erase(rows_.begin(), rows_.end()-mem_->max_rows);
}
// stringify
std::string GetPathString(File::Id id) const noexcept
try {
return env().GetFileOrThrow(id).abspath().Stringify();
@ -191,6 +197,9 @@ class Logger final : public nf7::File,
return loc.file_name()+":"s+std::to_string(loc.line());
}
// gui
void LogView() noexcept;
class ItemStore final : public nf7::Context,
public nf7::Logger,
@ -266,100 +275,13 @@ class Logger final : public nf7::File,
};
void Logger::Update() noexcept {
const auto em = ImGui::GetFontSize();
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
}
if (win_.Begin()) {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_ContextMenuInBody |
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("logs", 4, kTableFlags, ImGui::GetContentRegionAvail(), 0)) {
const bool updated = store_->MoveItemsTo(*this);
const bool autoscroll = updated && ImGui::GetScrollY() == ImGui::GetScrollMaxY();
ImGui::TableSetupColumn("level");
ImGui::TableSetupColumn("msg");
ImGui::TableSetupColumn("path");
ImGui::TableSetupColumn("location");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (const auto& row : rows_) {
ImGui::TableNextRow();
ImGui::PushID(&row);
if (autoscroll && &row == &rows_.back()) {
ImGui::SetScrollHereY();
}
// level column
if (ImGui::TableSetColumnIndex(0)) {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
}
}
// msg column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.msg.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.msg.c_str());
}
}
// path column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.path.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.path.c_str());
}
}
// location column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text(row.location.c_str());
for (auto ptr = row.ex; ptr;)
try {
ImGui::Bullet();
std::rethrow_exception(ptr);
} catch (Exception& e) {
e.UpdatePanic();
ImGui::Spacing();
ptr = e.reason();
} catch (std::exception& e) {
ImGui::Text("std::exception (%s)", e.what());
ptr = nullptr;
}
ImGui::EndTooltip();
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
win_.End();
}
void Logger::UpdateMenu() noexcept {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
ImGui::Separator();
ImGui::MenuItem("Log View", nullptr, &win_.shown());
win_.MenuItem();
}
void Logger::UpdateRowMenu(const Row& row) noexcept {
if (ImGui::MenuItem("copy as text")) {
@ -372,5 +294,85 @@ void Logger::UpdateRowMenu(const Row& row) noexcept {
}
}
void Logger::LogView() noexcept {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_ContextMenuInBody |
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("logs", 4, kTableFlags, ImGui::GetContentRegionAvail(), 0)) {
const bool updated = store_->MoveItemsTo(*this);
const bool autoscroll = updated && ImGui::GetScrollY() == ImGui::GetScrollMaxY();
ImGui::TableSetupColumn("level");
ImGui::TableSetupColumn("msg");
ImGui::TableSetupColumn("path");
ImGui::TableSetupColumn("location");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (const auto& row : rows_) {
ImGui::TableNextRow();
ImGui::PushID(&row);
if (autoscroll && &row == &rows_.back()) {
ImGui::SetScrollHereY();
}
// level column
if (ImGui::TableSetColumnIndex(0)) {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
}
}
// msg column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.msg.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.msg.c_str());
}
}
// path column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.path.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.path.c_str());
}
}
// location column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text(row.location.c_str());
for (auto ptr = row.ex; ptr;)
try {
ImGui::Bullet();
std::rethrow_exception(ptr);
} catch (Exception& e) {
e.UpdatePanic();
ImGui::Spacing();
ptr = e.reason();
} catch (std::exception& e) {
ImGui::Text("std::exception (%s)", e.what());
ptr = nullptr;
}
ImGui::EndTooltip();
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
}
} // namespace nf7

View File

@ -109,11 +109,12 @@ class Plot final : public nf7::FileBase,
std::vector<Series> series;
};
Plot(nf7::Env& env, const nf7::gui::Window* win = nullptr, Data&& data = {}) noexcept :
Plot(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), win_(*this, "Plot", win), mem_(std::move(data)) {
life_(*this), log_(*this), win_(*this, "Plot"), mem_(std::move(data)) {
win_.onUpdate = [this]() { PlotGraph(); };
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
Sanitize();
}
@ -126,7 +127,7 @@ class Plot final : public nf7::FileBase,
ar(win_, mem_->series);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Plot>(env, &win_, Data {mem_.data()});
return std::make_unique<Plot>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
@ -139,12 +140,8 @@ class Plot final : public nf7::FileBase,
return {};
}
void Update() noexcept override;
void UpdateWidget() noexcept override;
void UpdateMenu() noexcept override;
void UpdatePlot() noexcept;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
@ -161,6 +158,7 @@ class Plot final : public nf7::FileBase,
std::vector<std::string> inputs_;
// config management
void Sanitize() {
nf7::util::Uniq(mem_->series);
mem_.CommitAmend();
@ -172,6 +170,9 @@ class Plot final : public nf7::FileBase,
inputs_.push_back(s.name);
}
}
// gui
void PlotGraph() noexcept;
};
@ -237,22 +238,8 @@ std::shared_ptr<nf7::Node::Lambda> Plot::CreateLambda(
}
void Plot::Update() noexcept {
nf7::FileBase::Update();
if (win_.Begin()) {
UpdatePlot();
}
win_.End();
}
void Plot::UpdateWidget() noexcept {
if (ImGui::Button("plot window")) {
win_.SetFocus();
}
nf7::gui::Config(mem_);
}
void Plot::UpdateMenu() noexcept {
ImGui::MenuItem("plot window", nullptr, &win_.shown());
win_.MenuItem();
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
@ -260,7 +247,7 @@ void Plot::UpdateMenu() noexcept {
}
}
void Plot::UpdatePlot() noexcept {
void Plot::PlotGraph() noexcept {
if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) {
ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit);