implement Lambda of Sequencer/Timeline
This commit is contained in:
parent
40112d224d
commit
b8cb2a87d0
@ -59,6 +59,7 @@ target_sources(nf7
|
|||||||
common/generic_type_info.hh
|
common/generic_type_info.hh
|
||||||
common/generic_watcher.hh
|
common/generic_watcher.hh
|
||||||
common/gui_dnd.hh
|
common/gui_dnd.hh
|
||||||
|
common/gui_context.hh
|
||||||
common/gui_file.hh
|
common/gui_file.hh
|
||||||
common/gui_node.hh
|
common/gui_node.hh
|
||||||
common/gui_popup.hh
|
common/gui_popup.hh
|
||||||
|
46
common/gui_context.hh
Normal file
46
common/gui_context.hh
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::gui {
|
||||||
|
|
||||||
|
std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
|
||||||
|
auto f = ctx.env().GetFile(ctx.initiator());
|
||||||
|
|
||||||
|
const auto initiator =
|
||||||
|
f? f->abspath().Stringify(): std::string {"<owner missing>"};
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
std::snprintf(buf, sizeof(buf), "(0x%0" PRIXPTR ")", reinterpret_cast<uintptr_t>(&ctx));
|
||||||
|
|
||||||
|
return initiator + " " + buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
|
||||||
|
if (auto parent = ctx.parent()) {
|
||||||
|
return nf7::gui::GetContextDisplayName(*parent);
|
||||||
|
} else if (ctx.depth() == 0) {
|
||||||
|
return "(isolated)";
|
||||||
|
} else {
|
||||||
|
return "<owner disappeared> MEMORY LEAK? ;(";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextStack(const nf7::Context& ctx) noexcept {
|
||||||
|
for (auto p = ctx.parent(); p; p = p->parent()) {
|
||||||
|
auto f = ctx.env().GetFile(p->initiator());
|
||||||
|
|
||||||
|
const auto path = f? f->abspath().Stringify(): "[missing file]";
|
||||||
|
|
||||||
|
ImGui::TextUnformatted(path.c_str());
|
||||||
|
ImGui::TextDisabled("%s", p->GetDescription().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7::gui
|
@ -13,8 +13,12 @@ namespace nf7::gui {
|
|||||||
|
|
||||||
bool Timeline::Begin(uint64_t len) noexcept {
|
bool Timeline::Begin(uint64_t len) noexcept {
|
||||||
assert(frame_state_ == kRoot);
|
assert(frame_state_ == kRoot);
|
||||||
layer_y_ = 0;
|
layer_idx_ = 0;
|
||||||
layer_h_ = 0;
|
layer_y_ = 0;
|
||||||
|
layer_h_ = 0;
|
||||||
|
|
||||||
|
layer_idx_first_display_ = 0;
|
||||||
|
layer_offset_y_.clear();
|
||||||
|
|
||||||
len_ = len;
|
len_ = len;
|
||||||
scroll_size_.x = GetXFromTime(len_) + 100;
|
scroll_size_.x = GetXFromTime(len_) + 100;
|
||||||
@ -57,15 +61,30 @@ void Timeline::End() noexcept {
|
|||||||
|
|
||||||
void Timeline::NextLayerHeader(Layer layer, float height) noexcept {
|
void Timeline::NextLayerHeader(Layer layer, float height) noexcept {
|
||||||
assert(frame_state_ == kHeader);
|
assert(frame_state_ == kHeader);
|
||||||
|
assert(height > 0);
|
||||||
|
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
if (layer_h_ > 0) {
|
if (layer_h_ > 0) {
|
||||||
|
++layer_idx_;
|
||||||
layer_y_ += layer_h_+padding()*2;
|
layer_y_ += layer_h_+padding()*2;
|
||||||
}
|
}
|
||||||
layer_h_ = height*em;
|
layer_h_ = height*em;
|
||||||
layer_ = layer;
|
layer_ = layer;
|
||||||
|
|
||||||
|
// save Y offset of the layer if shown
|
||||||
|
if (layer_idx_first_display_) {
|
||||||
|
if (layer_y_ < scroll_.y+body_size_.y) {
|
||||||
|
layer_offset_y_.push_back(layer_y_);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const auto bottom = layer_y_+layer_h_;
|
||||||
|
if (bottom > scroll_.y) {
|
||||||
|
layer_idx_first_display_ = layer_idx_;
|
||||||
|
layer_offset_y_.push_back(layer_y_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto mouse = ImGui::GetMousePos().y;
|
const auto mouse = ImGui::GetMousePos().y;
|
||||||
if (layerTopScreenY() <= mouse && mouse < layerBottomScreenY()) {
|
if (layerTopScreenY() <= mouse && mouse < layerBottomScreenY()) {
|
||||||
mouse_layer_ = layer;
|
mouse_layer_ = layer;
|
||||||
@ -87,7 +106,9 @@ void Timeline::NextLayerHeader(Layer layer, float height) noexcept {
|
|||||||
bool Timeline::BeginBody() noexcept {
|
bool Timeline::BeginBody() noexcept {
|
||||||
assert(frame_state_ == kHeader);
|
assert(frame_state_ == kHeader);
|
||||||
|
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
|
const auto ctx = ImGui::GetCurrentContext();
|
||||||
|
const auto& io = ImGui::GetIO();
|
||||||
|
|
||||||
// end of header group
|
// end of header group
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
@ -104,15 +125,34 @@ bool Timeline::BeginBody() noexcept {
|
|||||||
frame_state_ = kBody;
|
frame_state_ = kBody;
|
||||||
body_screen_offset_ = ImGui::GetCursorScreenPos();
|
body_screen_offset_ = ImGui::GetCursorScreenPos();
|
||||||
|
|
||||||
ImGui::InvisibleButton("viewport-grip", scroll_size_, ImGuiButtonFlags_MouseButtonMiddle);
|
ImGui::InvisibleButton(
|
||||||
|
"viewport-grip", scroll_size_,
|
||||||
|
ImGuiButtonFlags_MouseButtonMiddle |
|
||||||
|
ImGuiButtonFlags_MouseButtonLeft);
|
||||||
ImGui::SetItemAllowOverlap();
|
ImGui::SetItemAllowOverlap();
|
||||||
if (ImGui::IsItemActive()) {
|
if (ImGui::IsItemActive()) {
|
||||||
scroll_ -= ImGui::GetIO().MouseDelta;
|
switch (ctx->ActiveIdMouseButton) {
|
||||||
|
case ImGuiMouseButton_Left: // click timeline to set time
|
||||||
|
action_time_ = GetTimeFromScreenX(io.MousePos.x);
|
||||||
|
if (ImGui::IsItemActivated() || action_time_ != action_last_set_time_) {
|
||||||
|
action_ = kSetTime;
|
||||||
|
action_last_set_time_ = action_time_;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImGuiMouseButton_Middle: // easyscroll
|
||||||
|
scroll_ -= io.MouseDelta;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer_ = nullptr;
|
layer_ = nullptr;
|
||||||
layer_y_ = 0;
|
layer_idx_ = 0;
|
||||||
layer_h_ = 0;
|
layer_y_ = 0;
|
||||||
|
layer_h_ = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -177,16 +217,19 @@ void Timeline::EndBody() noexcept {
|
|||||||
}
|
}
|
||||||
bool Timeline::NextLayer(Layer layer, float height) noexcept {
|
bool Timeline::NextLayer(Layer layer, float height) noexcept {
|
||||||
assert(frame_state_ == kBody);
|
assert(frame_state_ == kBody);
|
||||||
|
assert(height > 0);
|
||||||
|
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
if (layer_h_ > 0) {
|
if (layer_h_ > 0) {
|
||||||
|
++layer_idx_;
|
||||||
layer_y_ += layer_h_+padding()*2;
|
layer_y_ += layer_h_+padding()*2;
|
||||||
}
|
}
|
||||||
layer_h_ = height*em;
|
layer_h_ = height*em;
|
||||||
layer_ = layer;
|
layer_ = layer;
|
||||||
|
|
||||||
return true; // TODO check if shown
|
// it's shown if y offset is saved
|
||||||
|
return !!layerTopY(layer_idx_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept {
|
bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept {
|
||||||
@ -241,7 +284,7 @@ void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept {
|
|||||||
const auto size = ImGui::GetWindowSize();
|
const auto size = ImGui::GetWindowSize();
|
||||||
const auto grid_h = xgridHeight();
|
const auto grid_h = xgridHeight();
|
||||||
const auto x = GetScreenXFromTime(t);
|
const auto x = GetScreenXFromTime(t);
|
||||||
if (x < body_offset_.x) return;
|
if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return;
|
||||||
|
|
||||||
d->AddLine({x, spos.y}, {x, spos.y+size.y}, col);
|
d->AddLine({x, spos.y}, {x, spos.y+size.y}, col);
|
||||||
|
|
||||||
@ -250,6 +293,19 @@ void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept {
|
|||||||
d->AddText({x, spos.y + grid_h*0.1f }, col, num.c_str());
|
d->AddText({x, spos.y + grid_h*0.1f }, col, num.c_str());
|
||||||
d->AddText({x, spos.y + grid_h*0.1f+em}, col, name);
|
d->AddText({x, spos.y + grid_h*0.1f+em}, col, name);
|
||||||
}
|
}
|
||||||
|
void Timeline::Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept {
|
||||||
|
const auto d = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
|
const auto x = GetScreenXFromTime(t);
|
||||||
|
if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return;
|
||||||
|
|
||||||
|
const auto y = layerTopScreenY(layer);
|
||||||
|
if (!y || *y < scroll_.y) return;
|
||||||
|
|
||||||
|
d->AddTriangleFilled({x, *y}, {x+em, *y-em/2}, {x+em, *y+em/2}, col);
|
||||||
|
}
|
||||||
|
|
||||||
void Timeline::UpdateXGrid() noexcept {
|
void Timeline::UpdateXGrid() noexcept {
|
||||||
constexpr uint64_t kAccentInterval = 5;
|
constexpr uint64_t kAccentInterval = 5;
|
||||||
@ -295,7 +351,7 @@ void Timeline::HandleGrip(Item item, float off, Action ac, Action acdone, ImGuiM
|
|||||||
off += ImGui::GetCurrentContext()->ActiveIdClickOffset.x;
|
off += ImGui::GetCurrentContext()->ActiveIdClickOffset.x;
|
||||||
|
|
||||||
const auto pos = ImGui::GetMousePos() - ImVec2{off, 0};
|
const auto pos = ImGui::GetMousePos() - ImVec2{off, 0};
|
||||||
grip_time_ = GetTimeFromScreenX(pos.x);
|
action_time_ = GetTimeFromScreenX(pos.x);
|
||||||
|
|
||||||
scroll_x_to_mouse_ = true;
|
scroll_x_to_mouse_ = true;
|
||||||
scroll_y_to_mouse_ = (ac == kMove);
|
scroll_y_to_mouse_ = (ac == kMove);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
@ -14,9 +15,9 @@
|
|||||||
namespace nf7::gui {
|
namespace nf7::gui {
|
||||||
|
|
||||||
// if (tl.Begin()) {
|
// if (tl.Begin()) {
|
||||||
// tl.NextLayer(layer1, &layer1_height)
|
// tl.NextLayerHeader(layer1, &layer1_height)
|
||||||
// ImGui::Button("layer1");
|
// ImGui::Button("layer1");
|
||||||
// tl.NextLayer(layer2, &layer2_height)
|
// tl.NextLayerHeader(layer2, &layer2_height)
|
||||||
// ImGui::Button("layer2");
|
// ImGui::Button("layer2");
|
||||||
//
|
//
|
||||||
// if (tl.BeginBody()) {
|
// if (tl.BeginBody()) {
|
||||||
@ -60,6 +61,7 @@ struct Timeline {
|
|||||||
kResizeEndDone,
|
kResizeEndDone,
|
||||||
kMove,
|
kMove,
|
||||||
kMoveDone,
|
kMoveDone,
|
||||||
|
kSetTime,
|
||||||
};
|
};
|
||||||
using Layer = void*;
|
using Layer = void*;
|
||||||
using Item = void*;
|
using Item = void*;
|
||||||
@ -94,6 +96,7 @@ struct Timeline {
|
|||||||
void EndItem() noexcept;
|
void EndItem() noexcept;
|
||||||
|
|
||||||
void Cursor(const char*, uint64_t t, uint32_t col) noexcept;
|
void Cursor(const char*, uint64_t t, uint32_t col) noexcept;
|
||||||
|
void Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept;
|
||||||
|
|
||||||
uint64_t GetTimeFromX(float x) const noexcept {
|
uint64_t GetTimeFromX(float x) const noexcept {
|
||||||
return static_cast<uint64_t>(std::max(0.f, x/ImGui::GetFontSize()/zoom_));
|
return static_cast<uint64_t>(std::max(0.f, x/ImGui::GetFontSize()/zoom_));
|
||||||
@ -113,6 +116,22 @@ struct Timeline {
|
|||||||
float xgridHeight() const noexcept { return xgrid_height_*ImGui::GetFontSize(); }
|
float xgridHeight() const noexcept { return xgrid_height_*ImGui::GetFontSize(); }
|
||||||
float padding() const noexcept { return padding_*ImGui::GetFontSize(); }
|
float padding() const noexcept { return padding_*ImGui::GetFontSize(); }
|
||||||
|
|
||||||
|
std::optional<float> layerTopY(size_t idx) noexcept {
|
||||||
|
if (!layer_idx_first_display_ || idx < *layer_idx_first_display_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
idx -= *layer_idx_first_display_;
|
||||||
|
if (idx >= layer_offset_y_.size()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return layer_offset_y_[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<float> layerTopScreenY(size_t idx) noexcept {
|
||||||
|
auto y = layerTopY(idx);
|
||||||
|
if (!y) return std::nullopt;
|
||||||
|
return *y + body_screen_offset_.y;
|
||||||
|
}
|
||||||
float layerTopScreenY() noexcept {
|
float layerTopScreenY() noexcept {
|
||||||
return body_screen_offset_.y + layer_y_;
|
return body_screen_offset_.y + layer_y_;
|
||||||
}
|
}
|
||||||
@ -130,7 +149,7 @@ struct Timeline {
|
|||||||
|
|
||||||
Action action() const noexcept { return action_; }
|
Action action() const noexcept { return action_; }
|
||||||
Item actionTarget() const noexcept { return action_target_; }
|
Item actionTarget() const noexcept { return action_target_; }
|
||||||
uint64_t gripTime() const noexcept { return grip_time_; }
|
uint64_t actionTime() const noexcept { return action_time_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// immutable params
|
// immutable params
|
||||||
@ -161,15 +180,21 @@ struct Timeline {
|
|||||||
float mouse_layer_y_;
|
float mouse_layer_y_;
|
||||||
float mouse_layer_h_;
|
float mouse_layer_h_;
|
||||||
|
|
||||||
Layer layer_;
|
Layer layer_;
|
||||||
float layer_y_;
|
size_t layer_idx_;
|
||||||
float layer_h_;
|
float layer_y_;
|
||||||
|
float layer_h_;
|
||||||
|
|
||||||
|
std::optional<size_t> layer_idx_first_display_;
|
||||||
|
std::vector<float> layer_offset_y_;
|
||||||
|
|
||||||
Item item_;
|
Item item_;
|
||||||
|
|
||||||
Action action_;
|
Action action_;
|
||||||
Item action_target_;
|
Item action_target_;
|
||||||
uint64_t grip_time_;
|
uint64_t action_time_;
|
||||||
|
|
||||||
|
uint64_t action_last_set_time_ = UINT64_MAX; // for kSetTime
|
||||||
|
|
||||||
|
|
||||||
void UpdateXGrid() noexcept;
|
void UpdateXGrid() noexcept;
|
||||||
|
@ -13,8 +13,9 @@ namespace nf7 {
|
|||||||
|
|
||||||
class Sequencer : public nf7::File::Interface {
|
class Sequencer : public nf7::File::Interface {
|
||||||
public:
|
public:
|
||||||
class Lambda;
|
|
||||||
class Editor;
|
class Editor;
|
||||||
|
class Session;
|
||||||
|
class Lambda;
|
||||||
|
|
||||||
struct Period { uint64_t begin, end; };
|
struct Period { uint64_t begin, end; };
|
||||||
|
|
||||||
@ -34,7 +35,8 @@ class Sequencer : public nf7::File::Interface {
|
|||||||
Sequencer& operator=(Sequencer&&) = delete;
|
Sequencer& operator=(Sequencer&&) = delete;
|
||||||
|
|
||||||
// Sequencer* is a dummy parameter to avoid issues of multi inheritance.
|
// Sequencer* is a dummy parameter to avoid issues of multi inheritance.
|
||||||
virtual std::shared_ptr<Lambda> CreateLambda(const std::shared_ptr<Lambda>&) noexcept = 0;
|
virtual std::shared_ptr<Lambda> CreateLambda(
|
||||||
|
const std::shared_ptr<nf7::Context>&) noexcept = 0;
|
||||||
|
|
||||||
virtual void UpdateItem(Editor&) noexcept { }
|
virtual void UpdateItem(Editor&) noexcept { }
|
||||||
virtual void UpdateTooltip(Editor&) noexcept { }
|
virtual void UpdateTooltip(Editor&) noexcept { }
|
||||||
@ -42,12 +44,6 @@ class Sequencer : public nf7::File::Interface {
|
|||||||
|
|
||||||
Flags flags() const noexcept { return flags_; }
|
Flags flags() const noexcept { return flags_; }
|
||||||
|
|
||||||
std::span<const std::string> input() const noexcept { return input_; }
|
|
||||||
std::span<const std::string> output() const noexcept { return output_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<std::string> input_, output_;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Flags flags_;
|
Flags flags_;
|
||||||
};
|
};
|
||||||
@ -62,4 +58,49 @@ class Sequencer::Editor {
|
|||||||
Editor& operator=(Editor&&) = delete;
|
Editor& operator=(Editor&&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Sequencer::Session {
|
||||||
|
public:
|
||||||
|
class UnknownNameException final : public nf7::Exception {
|
||||||
|
public:
|
||||||
|
using nf7::Exception::Exception;
|
||||||
|
};
|
||||||
|
|
||||||
|
Session() = default;
|
||||||
|
virtual ~Session() = default;
|
||||||
|
Session(const Session&) = delete;
|
||||||
|
Session(Session&&) = delete;
|
||||||
|
Session& operator=(const Session&) = delete;
|
||||||
|
Session& operator=(Session&&) = delete;
|
||||||
|
|
||||||
|
// these can throw UnknownNameException
|
||||||
|
virtual const nf7::Value& Peek(std::string_view) = 0;
|
||||||
|
virtual nf7::Value Receive(std::string_view) = 0;
|
||||||
|
|
||||||
|
virtual void Send(std::string_view, nf7::Value&&) noexcept = 0;
|
||||||
|
|
||||||
|
// thread-safe
|
||||||
|
virtual void Finish() noexcept = 0;
|
||||||
|
|
||||||
|
struct Info final {
|
||||||
|
public:
|
||||||
|
uint64_t time;
|
||||||
|
uint64_t begin;
|
||||||
|
uint64_t end;
|
||||||
|
};
|
||||||
|
virtual const Info& info() const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Sequencer::Lambda : public nf7::Context {
|
||||||
|
public:
|
||||||
|
Lambda(nf7::File& f, const std::shared_ptr<Context>& ctx = nullptr) noexcept :
|
||||||
|
Lambda(f.env(), f.id(), ctx) {
|
||||||
|
}
|
||||||
|
Lambda(nf7::Env& env, nf7::File::Id id,
|
||||||
|
const std::shared_ptr<nf7::Context>& ctx = nullptr) noexcept :
|
||||||
|
Context(env, id, ctx) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Run(const std::shared_ptr<Sequencer::Session>&) noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -20,6 +24,7 @@
|
|||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/gui_context.hh"
|
||||||
#include "common/gui_file.hh"
|
#include "common/gui_file.hh"
|
||||||
#include "common/gui_popup.hh"
|
#include "common/gui_popup.hh"
|
||||||
#include "common/gui_timeline.hh"
|
#include "common/gui_timeline.hh"
|
||||||
@ -30,6 +35,10 @@
|
|||||||
#include "common/squashed_history.hh"
|
#include "common/squashed_history.hh"
|
||||||
#include "common/yas_nf7.hh"
|
#include "common/yas_nf7.hh"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
@ -52,8 +61,20 @@ class Null final : public nf7::File, public nf7::Sequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Sequencer::Lambda> CreateLambda(
|
std::shared_ptr<Sequencer::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<Sequencer::Lambda>&) noexcept override {
|
const std::shared_ptr<nf7::Context>& parent) noexcept override {
|
||||||
return nullptr;
|
class Emitter final : public Sequencer::Lambda,
|
||||||
|
public std::enable_shared_from_this<Emitter> {
|
||||||
|
public:
|
||||||
|
using Sequencer::Lambda::Lambda;
|
||||||
|
|
||||||
|
void Run(const std::shared_ptr<Sequencer::Session>& ss) noexcept override {
|
||||||
|
env().ExecAsync(shared_from_this(), [ss]() {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
ss->Finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return std::make_shared<Emitter>(*this, parent);
|
||||||
}
|
}
|
||||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<nf7::Sequencer>(t).Select(this);
|
return nf7::InterfaceSelector<nf7::Sequencer>(t).Select(this);
|
||||||
@ -78,25 +99,31 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
|
|
||||||
class Item;
|
class Item;
|
||||||
class Layer;
|
class Layer;
|
||||||
class Lambda;
|
|
||||||
class Editor;
|
class Editor;
|
||||||
|
|
||||||
|
class Session;
|
||||||
|
class Lambda;
|
||||||
|
|
||||||
|
using ItemId = uint64_t;
|
||||||
|
|
||||||
TL(Env& env,
|
TL(Env& env,
|
||||||
uint64_t length = 1000,
|
uint64_t length = 1000,
|
||||||
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
||||||
uint64_t next = 1,
|
ItemId next = 1,
|
||||||
const nf7::gui::Window* win = nullptr) noexcept :
|
const nf7::gui::Window* win = nullptr) noexcept :
|
||||||
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kMenu),
|
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kMenu),
|
||||||
length_(length), layers_(std::move(layers)), next_(next),
|
length_(length), layers_(std::move(layers)), next_(next),
|
||||||
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
||||||
popup_add_item_(*this) {
|
popup_add_item_(*this), popup_config_(*this) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetUpAfterDeserialize();
|
||||||
TL(Env& env, Deserializer& ar) : TL(env) {
|
TL(Env& env, Deserializer& ar) : TL(env) {
|
||||||
ar(length_, layers_, next_, win_, tl_);
|
ar(length_, layers_, win_, tl_);
|
||||||
|
SetUpAfterDeserialize();
|
||||||
}
|
}
|
||||||
void Serialize(Serializer& ar) const noexcept override {
|
void Serialize(Serializer& ar) const noexcept override {
|
||||||
ar(length_, layers_, next_, win_, tl_);
|
ar(length_, layers_, win_, tl_);
|
||||||
}
|
}
|
||||||
std::unique_ptr<File> Clone(Env& env) const noexcept override;
|
std::unique_ptr<File> Clone(Env& env) const noexcept override;
|
||||||
|
|
||||||
@ -107,7 +134,8 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
void Update() noexcept override;
|
void Update() noexcept override;
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
|
|
||||||
void UpdateEditor() noexcept;
|
void UpdateEditorWindow() noexcept;
|
||||||
|
void UpdateLambdaSelector() noexcept;
|
||||||
void HandleTimelineAction() noexcept;
|
void HandleTimelineAction() noexcept;
|
||||||
|
|
||||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
@ -117,11 +145,18 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
private:
|
private:
|
||||||
nf7::SquashedHistory<History::Command> history_;
|
nf7::SquashedHistory<History::Command> history_;
|
||||||
|
|
||||||
|
std::shared_ptr<TL::Lambda> lambda_;
|
||||||
|
std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_;
|
||||||
|
|
||||||
// permanentized params
|
// permanentized params
|
||||||
uint64_t length_;
|
uint64_t length_;
|
||||||
|
uint64_t cursor_;
|
||||||
std::vector<std::unique_ptr<Layer>> layers_;
|
std::vector<std::unique_ptr<Layer>> layers_;
|
||||||
|
|
||||||
uint64_t next_;
|
std::vector<std::string> seq_inputs_;
|
||||||
|
std::vector<std::string> seq_outputs_;
|
||||||
|
|
||||||
|
ItemId next_;
|
||||||
|
|
||||||
nf7::gui::Window win_;
|
nf7::gui::Window win_;
|
||||||
nf7::gui::Timeline tl_;
|
nf7::gui::Timeline tl_;
|
||||||
@ -149,6 +184,27 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
|
|
||||||
nf7::gui::FileFactory<0> factory_;
|
nf7::gui::FileFactory<0> factory_;
|
||||||
} popup_add_item_;
|
} popup_add_item_;
|
||||||
|
struct ConfigPopup final : nf7::gui::Popup {
|
||||||
|
public:
|
||||||
|
ConfigPopup(TL& f) noexcept :
|
||||||
|
Popup("ConfigPopup"), owner_(&f) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Open() noexcept {
|
||||||
|
inputs_ = StringifySocketList(owner_->seq_inputs_);
|
||||||
|
outputs_ = StringifySocketList(owner_->seq_outputs_);
|
||||||
|
error_ = "";
|
||||||
|
Popup::Open();
|
||||||
|
}
|
||||||
|
void Update() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TL* const owner_;
|
||||||
|
|
||||||
|
std::string inputs_;
|
||||||
|
std::string outputs_;
|
||||||
|
std::string error_;
|
||||||
|
} popup_config_;
|
||||||
|
|
||||||
|
|
||||||
// GUI temporary params
|
// GUI temporary params
|
||||||
@ -168,7 +224,6 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
void ExecApplyLayerOfSelected() noexcept;
|
void ExecApplyLayerOfSelected() noexcept;
|
||||||
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
|
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
|
||||||
|
|
||||||
|
|
||||||
// history
|
// history
|
||||||
void ExecUnDo() {
|
void ExecUnDo() {
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
@ -180,6 +235,55 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node {
|
|||||||
std::make_shared<nf7::GenericContext>(*this, "applying commands to redo"),
|
std::make_shared<nf7::GenericContext>(*this, "applying commands to redo"),
|
||||||
[this]() { history_.ReDo(); });
|
[this]() { history_.ReDo(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// instant running
|
||||||
|
void MoveCursorTo(uint64_t t) noexcept;
|
||||||
|
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
|
||||||
|
|
||||||
|
// socket operation
|
||||||
|
void ApplySeqSocketChanges() noexcept {
|
||||||
|
input_ = seq_inputs_;
|
||||||
|
output_ = seq_outputs_;
|
||||||
|
output_.push_back("_cursor");
|
||||||
|
Touch();
|
||||||
|
}
|
||||||
|
static std::vector<std::string> ParseSocketSpecifier(std::string_view names) {
|
||||||
|
const auto n = names.size();
|
||||||
|
|
||||||
|
std::vector<std::string> ret;
|
||||||
|
size_t begin = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i <= n; ++i) {
|
||||||
|
if (i == n || names[i] == '\n') {
|
||||||
|
const auto name = names.substr(begin, i-begin);
|
||||||
|
if (name.size() > 0) {
|
||||||
|
ret.push_back(std::string {name});
|
||||||
|
}
|
||||||
|
begin = i+1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValidateSocketList(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static void ValidateSocketList(const std::vector<std::string>& a) {
|
||||||
|
for (size_t i = 0; i < a.size(); ++i) {
|
||||||
|
File::Path::ValidateTerm(a[i]);
|
||||||
|
for (size_t j = i+1; j < a.size(); ++j) {
|
||||||
|
if (a[i] == a[j]) {
|
||||||
|
throw nf7::Exception {"name duplication: "+a[i]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static std::string StringifySocketList(const std::vector<std::string>& a) noexcept {
|
||||||
|
std::stringstream st;
|
||||||
|
for (auto& name : a) {
|
||||||
|
st << name << '\n';
|
||||||
|
}
|
||||||
|
return st.str();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -228,7 +332,7 @@ struct TL::Timing {
|
|||||||
class TL::Item final {
|
class TL::Item final {
|
||||||
public:
|
public:
|
||||||
Item() = delete;
|
Item() = delete;
|
||||||
Item(uint64_t id, std::unique_ptr<nf7::File>&& f, const Timing& t) :
|
Item(ItemId id, std::unique_ptr<nf7::File>&& f, const Timing& t) :
|
||||||
id_(id), file_(std::move(f)),
|
id_(id), file_(std::move(f)),
|
||||||
seq_(&file_->interfaceOrThrow<nf7::Sequencer>()),
|
seq_(&file_->interfaceOrThrow<nf7::Sequencer>()),
|
||||||
timing_(t), display_timing_(t) {
|
timing_(t), display_timing_(t) {
|
||||||
@ -242,13 +346,13 @@ class TL::Item final {
|
|||||||
ar(id_, file_, timing_);
|
ar(id_, file_, timing_);
|
||||||
}
|
}
|
||||||
static std::unique_ptr<TL::Item> Load(auto& ar) noexcept {
|
static std::unique_ptr<TL::Item> Load(auto& ar) noexcept {
|
||||||
uint64_t id;
|
ItemId id;
|
||||||
std::unique_ptr<nf7::File> file;
|
std::unique_ptr<nf7::File> file;
|
||||||
Timing timing;
|
Timing timing;
|
||||||
ar(id, file, timing);
|
ar(id, file, timing);
|
||||||
return std::make_unique<TL::Item>(id, std::move(file), timing);
|
return std::make_unique<TL::Item>(id, std::move(file), timing);
|
||||||
}
|
}
|
||||||
std::unique_ptr<TL::Item> Clone(nf7::Env& env, uint64_t id) const {
|
std::unique_ptr<TL::Item> Clone(nf7::Env& env, ItemId id) const {
|
||||||
return std::make_unique<TL::Item>(id, file_->Clone(env), timing_);
|
return std::make_unique<TL::Item>(id, file_->Clone(env), timing_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,12 +391,8 @@ class TL::Item final {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Update() noexcept;
|
void Update() noexcept;
|
||||||
bool UpdateResizer(uint64_t& target, float offset,
|
|
||||||
const std::function<uint64_t()>& min,
|
|
||||||
const std::function<uint64_t()>& max) noexcept;
|
|
||||||
void UpdateMover(float offset_x) noexcept;
|
|
||||||
|
|
||||||
uint64_t id() const noexcept { return id_; }
|
ItemId id() const noexcept { return id_; }
|
||||||
nf7::File& file() const noexcept { return *file_; }
|
nf7::File& file() const noexcept { return *file_; }
|
||||||
TL::Layer& layer() const noexcept { return *layer_; }
|
TL::Layer& layer() const noexcept { return *layer_; }
|
||||||
nf7::Sequencer& seq() const noexcept { return *seq_; }
|
nf7::Sequencer& seq() const noexcept { return *seq_; }
|
||||||
@ -304,7 +404,7 @@ class TL::Item final {
|
|||||||
TL* owner_ = nullptr;
|
TL* owner_ = nullptr;
|
||||||
TL::Layer* layer_ = nullptr;
|
TL::Layer* layer_ = nullptr;
|
||||||
|
|
||||||
uint64_t id_;
|
ItemId id_;
|
||||||
std::unique_ptr<nf7::File> file_;
|
std::unique_ptr<nf7::File> file_;
|
||||||
nf7::Sequencer* const seq_;
|
nf7::Sequencer* const seq_;
|
||||||
|
|
||||||
@ -343,7 +443,7 @@ class TL::Layer final {
|
|||||||
ar(items, enabled, height);
|
ar(items, enabled, height);
|
||||||
return std::make_unique<TL::Layer>(std::move(items), enabled, height);
|
return std::make_unique<TL::Layer>(std::move(items), enabled, height);
|
||||||
}
|
}
|
||||||
std::unique_ptr<TL::Layer> Clone(Env& env, uint64_t& id) const {
|
std::unique_ptr<TL::Layer> Clone(Env& env, ItemId& id) const {
|
||||||
std::vector<std::unique_ptr<TL::Item>> items;
|
std::vector<std::unique_ptr<TL::Item>> items;
|
||||||
items.reserve(items_.size());
|
items.reserve(items_.size());
|
||||||
for (auto& item : items_) items.push_back(item->Clone(env, id++));
|
for (auto& item : items_) items.push_back(item->Clone(env, id++));
|
||||||
@ -474,22 +574,254 @@ class TL::Layer final {
|
|||||||
size_t index_;
|
size_t index_;
|
||||||
float offset_y_;
|
float offset_y_;
|
||||||
};
|
};
|
||||||
|
void TL::SetUpAfterDeserialize() {
|
||||||
|
next_ = 1;
|
||||||
|
std::unordered_set<ItemId> ids;
|
||||||
|
for (auto& layer : layers_) {
|
||||||
|
for (auto& item : layer->items()) {
|
||||||
|
if (item->id() == 0) {
|
||||||
|
throw nf7::DeserializeException {"item id cannot be zero"};
|
||||||
|
}
|
||||||
|
if (ids.contains(item->id())) {
|
||||||
|
throw nf7::DeserializeException {"id duplication"};
|
||||||
|
}
|
||||||
|
ids.insert(item->id());
|
||||||
|
next_ = std::max(next_, item->id()+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TL::Lambda final : public Node::Lambda {
|
class TL::Lambda final : public Node::Lambda,
|
||||||
|
public std::enable_shared_from_this<TL::Lambda> {
|
||||||
public:
|
public:
|
||||||
Lambda() = delete;
|
Lambda() = delete;
|
||||||
Lambda(TL& f, const std::shared_ptr<Node::Lambda>& parent) noexcept :
|
Lambda(TL& f, const std::shared_ptr<Node::Lambda>& parent) noexcept :
|
||||||
Node::Lambda(f, parent) {
|
Node::Lambda(f, parent), owner_(&f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view, const nf7::Value&,
|
void Handle(std::string_view, const nf7::Value&,
|
||||||
const std::shared_ptr<Node::Lambda>&) noexcept override {
|
const std::shared_ptr<Node::Lambda>&) noexcept override;
|
||||||
|
|
||||||
|
std::shared_ptr<TL::Session> CreateSession(uint64_t t) noexcept {
|
||||||
|
auto ss = std::make_shared<TL::Session>(shared_from_this(), last_session_, t, vars_);
|
||||||
|
last_session_ = ss;
|
||||||
|
sessions_.erase(
|
||||||
|
std::remove_if(
|
||||||
|
sessions_.begin(), sessions_.end(),
|
||||||
|
[](auto& x) { return x.expired(); }),
|
||||||
|
sessions_.end());
|
||||||
|
sessions_.push_back(ss);
|
||||||
|
return ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<TL::Item*, std::shared_ptr<Sequencer::Lambda>> GetNext(
|
||||||
|
uint64_t& layer_idx, uint64_t layer_until, uint64_t t) noexcept {
|
||||||
|
if (aborted_ || !env().GetFile(initiator())) {
|
||||||
|
return {nullptr, nullptr};
|
||||||
|
}
|
||||||
|
layer_until = std::min(layer_until, owner_->layers_.size());
|
||||||
|
for (; layer_idx < layer_until; ++layer_idx) {
|
||||||
|
auto& layer = *owner_->layers_[layer_idx];
|
||||||
|
if (!layer.enabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (auto item = layer.GetAt(t)) {
|
||||||
|
auto itr = lambdas_.find(item->id());
|
||||||
|
if (itr == lambdas_.end()) {
|
||||||
|
auto la = item->seq().CreateLambda(shared_from_this());
|
||||||
|
lambdas_.emplace(item->id(), la);
|
||||||
|
return {item, la};
|
||||||
|
} else {
|
||||||
|
return {item, itr->second};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {nullptr, nullptr};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCursor(uint64_t t) noexcept {
|
||||||
|
if (auto p = parent()) {
|
||||||
|
p->Handle("_cursor", static_cast<nf7::Value::Integer>(t), shared_from_this());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void EmitResults(const std::unordered_map<std::string, nf7::Value>& vars) noexcept {
|
||||||
|
if (!env().GetFile(initiator())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto caller = parent();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Abort() noexcept {
|
||||||
|
aborted_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const std::weak_ptr<TL::Session>> sessions() const noexcept {
|
||||||
|
return sessions_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TL* const owner_;
|
||||||
|
|
||||||
|
std::atomic<bool> aborted_ = false;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, nf7::Value> vars_;
|
||||||
|
std::unordered_map<ItemId, std::shared_ptr<Sequencer::Lambda>> lambdas_;
|
||||||
|
|
||||||
|
std::weak_ptr<TL::Session> last_session_;
|
||||||
|
std::vector<std::weak_ptr<TL::Session>> sessions_;
|
||||||
};
|
};
|
||||||
std::shared_ptr<Node::Lambda> TL::CreateLambda(
|
std::shared_ptr<Node::Lambda> TL::CreateLambda(
|
||||||
const std::shared_ptr<Node::Lambda>& parent) noexcept {
|
const std::shared_ptr<Node::Lambda>& parent) noexcept {
|
||||||
return std::make_shared<TL::Lambda>(*this, parent);
|
auto la = std::make_shared<TL::Lambda>(*this, parent);
|
||||||
|
|
||||||
|
lambdas_running_.erase(
|
||||||
|
std::remove_if(lambdas_running_.begin(), lambdas_running_.end(),
|
||||||
|
[](auto& x) { return x.expired(); }),
|
||||||
|
lambdas_running_.end());;
|
||||||
|
lambdas_running_.emplace_back(la);
|
||||||
|
return la;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TL::Session final : public Sequencer::Session,
|
||||||
|
public std::enable_shared_from_this<TL::Session> {
|
||||||
|
public:
|
||||||
|
Session(const std::shared_ptr<TL::Lambda>& initiator,
|
||||||
|
const std::weak_ptr<Session>& leader,
|
||||||
|
uint64_t time, const std::unordered_map<std::string, nf7::Value>& vars) noexcept :
|
||||||
|
env_(&initiator->env()), last_active_(std::chrono::system_clock::now()),
|
||||||
|
initiator_(initiator), leader_(leader),
|
||||||
|
time_(time), vars_(vars) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const nf7::Value& Peek(std::string_view name) override {
|
||||||
|
auto itr = vars_.find(std::string {name});
|
||||||
|
if (itr == vars_.end()) {
|
||||||
|
throw UnknownNameException {std::string {name}+" is unknown"};
|
||||||
|
}
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
nf7::Value Receive(std::string_view name) override {
|
||||||
|
auto itr = vars_.find(std::string {name});
|
||||||
|
if (itr == vars_.end()) {
|
||||||
|
throw UnknownNameException {std::string {name}+" is unknown"};
|
||||||
|
}
|
||||||
|
auto ret = std::move(itr->second);
|
||||||
|
vars_.erase(itr);
|
||||||
|
return std::move(itr->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Send(std::string_view name, nf7::Value&& v) noexcept override {
|
||||||
|
vars_[std::string {name}] = std::move(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartNext() noexcept {
|
||||||
|
auto leader = leader_.lock();
|
||||||
|
|
||||||
|
uint64_t layer_until = UINT64_MAX;
|
||||||
|
if (leader && !leader->done_) {
|
||||||
|
layer_until = leader->layer_;
|
||||||
|
} else {
|
||||||
|
leader = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [item, lambda] = initiator_->GetNext(layer_, layer_until, time_);
|
||||||
|
if (item) {
|
||||||
|
assert(lambda);
|
||||||
|
|
||||||
|
const auto& t = item->timing();
|
||||||
|
info_.time = time_;
|
||||||
|
info_.begin = t.begin();
|
||||||
|
info_.end = t.end();
|
||||||
|
lambda->Run(shared_from_this());
|
||||||
|
|
||||||
|
last_active_ = std::chrono::system_clock::now();
|
||||||
|
++layer_;
|
||||||
|
|
||||||
|
} else if (leader) {
|
||||||
|
assert(!leader->follower_);
|
||||||
|
leader->follower_ = shared_from_this();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
done_ = true;
|
||||||
|
initiator_->EmitResults(vars_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto follower = std::exchange(follower_, nullptr)) {
|
||||||
|
follower->StartNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Finish() noexcept override {
|
||||||
|
auto self = shared_from_this();
|
||||||
|
env_->ExecSub(initiator_, [self]() { self->StartNext(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const Info& info() const noexcept override { return info_; }
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point lastActive() const noexcept { return last_active_; }
|
||||||
|
bool done() const noexcept { return done_; }
|
||||||
|
uint64_t time() const noexcept { return time_; }
|
||||||
|
uint64_t layer() const noexcept { return layer_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::Env* const env_;
|
||||||
|
std::chrono::system_clock::time_point last_active_;
|
||||||
|
|
||||||
|
std::shared_ptr<TL::Lambda> initiator_;
|
||||||
|
|
||||||
|
std::weak_ptr<Session> leader_;
|
||||||
|
std::shared_ptr<Session> follower_;
|
||||||
|
|
||||||
|
const uint64_t time_;
|
||||||
|
std::unordered_map<std::string, nf7::Value> vars_;
|
||||||
|
|
||||||
|
bool done_ = false;
|
||||||
|
uint64_t layer_ = 0;
|
||||||
|
Info info_;
|
||||||
|
};
|
||||||
|
void TL::Lambda::Handle(std::string_view name, const nf7::Value& v,
|
||||||
|
const std::shared_ptr<Node::Lambda>&) noexcept {
|
||||||
|
if (name == "exec") {
|
||||||
|
if (!env().GetFile(initiator())) return;
|
||||||
|
const auto t_max = owner_->length_-1;
|
||||||
|
|
||||||
|
uint64_t t;
|
||||||
|
if (v.isInteger()) {
|
||||||
|
const auto ti = std::clamp(v.integer(), int64_t{0}, static_cast<int64_t>(t_max));
|
||||||
|
t = static_cast<uint64_t>(ti);
|
||||||
|
} else if (v.isScalar()) {
|
||||||
|
const auto tf = std::clamp(v.scalar(), 0., 1.)*static_cast<double>(t_max);
|
||||||
|
t = static_cast<uint64_t>(tf);
|
||||||
|
} else {
|
||||||
|
// TODO: error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CreateSession(t)->StartNext();
|
||||||
|
} else {
|
||||||
|
vars_[std::string {name}] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void TL::MoveCursorTo(uint64_t time) noexcept {
|
||||||
|
cursor_ = std::min(time, length_-1);
|
||||||
|
|
||||||
|
if (!lambda_) {
|
||||||
|
AttachLambda(std::make_shared<TL::Lambda>(*this, nullptr));
|
||||||
|
}
|
||||||
|
lambda_->CreateSession(time)->StartNext();
|
||||||
|
lambda_->EmitCursor(time);
|
||||||
|
}
|
||||||
|
void TL::AttachLambda(const std::shared_ptr<TL::Lambda>& la) noexcept {
|
||||||
|
if (la == lambda_) return;
|
||||||
|
if (lambda_ && lambda_->depth() == 0) {
|
||||||
|
lambda_->Abort();
|
||||||
|
}
|
||||||
|
lambda_ = la;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -825,7 +1157,7 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
|
|||||||
std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
|
std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
|
||||||
std::vector<std::unique_ptr<TL::Layer>> layers;
|
std::vector<std::unique_ptr<TL::Layer>> layers;
|
||||||
layers.reserve(layers_.size());
|
layers.reserve(layers_.size());
|
||||||
uint64_t next = 1;
|
ItemId next = 1;
|
||||||
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next));
|
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next));
|
||||||
return std::make_unique<TL>(env, length_, std::move(layers), next, &win_);
|
return std::make_unique<TL>(env, length_, std::move(layers), next, &win_);
|
||||||
}
|
}
|
||||||
@ -861,19 +1193,25 @@ void TL::Handle(const Event& ev) noexcept {
|
|||||||
|
|
||||||
void TL::Update() noexcept {
|
void TL::Update() noexcept {
|
||||||
popup_add_item_.Update();
|
popup_add_item_.Update();
|
||||||
UpdateEditor();
|
popup_config_.Update();
|
||||||
|
|
||||||
|
UpdateEditorWindow();
|
||||||
|
|
||||||
history_.Squash();
|
history_.Squash();
|
||||||
}
|
}
|
||||||
void TL::UpdateMenu() noexcept {
|
void TL::UpdateMenu() noexcept {
|
||||||
ImGui::MenuItem("Editor", nullptr, &win_.shown());
|
ImGui::MenuItem("Editor", nullptr, &win_.shown());
|
||||||
}
|
}
|
||||||
void TL::UpdateEditor() noexcept {
|
void TL::UpdateEditorWindow() noexcept {
|
||||||
const auto kInit = []() {
|
const auto kInit = []() {
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
ImGui::SetNextWindowSizeConstraints({24*em, 8*em}, {1e8, 1e8});
|
ImGui::SetNextWindowSizeConstraints({24*em, 8*em}, {1e8, 1e8});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (win_.Begin(kInit)) {
|
if (win_.Begin(kInit)) {
|
||||||
|
UpdateLambdaSelector();
|
||||||
|
|
||||||
|
// timeline
|
||||||
if (tl_.Begin(length_)) {
|
if (tl_.Begin(length_)) {
|
||||||
// layer headers
|
// layer headers
|
||||||
for (size_t i = 0; i < layers_.size(); ++i) {
|
for (size_t i = 0; i < layers_.size(); ++i) {
|
||||||
@ -899,6 +1237,10 @@ void TL::UpdateEditor() noexcept {
|
|||||||
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
|
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
|
||||||
ExecReDo();
|
ExecReDo();
|
||||||
}
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("config")) {
|
||||||
|
popup_config_.Open();
|
||||||
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -922,56 +1264,136 @@ void TL::UpdateEditor() noexcept {
|
|||||||
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
|
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
|
||||||
if (ImGui::IsWindowHovered(kFlags)) {
|
if (ImGui::IsWindowHovered(kFlags)) {
|
||||||
tl_.Cursor(
|
tl_.Cursor(
|
||||||
"cursor",
|
"mouse",
|
||||||
std::min(length_-1, tl_.GetTimeFromScreenX(ImGui::GetMousePos().x)),
|
std::min(length_-1, tl_.GetTimeFromScreenX(ImGui::GetMousePos().x)),
|
||||||
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
|
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frame cursor
|
||||||
|
tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f));
|
||||||
|
|
||||||
// end of timeline
|
// end of timeline
|
||||||
tl_.Cursor("END", length_, ImGui::GetColorU32(ImGuiCol_TextDisabled));
|
tl_.Cursor("END", length_, ImGui::GetColorU32(ImGuiCol_TextDisabled));
|
||||||
|
|
||||||
|
// running sessions
|
||||||
|
if (lambda_) {
|
||||||
|
const auto now = std::chrono::system_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();
|
HandleTimelineAction();
|
||||||
}
|
}
|
||||||
tl_.End();
|
tl_.End();
|
||||||
|
|
||||||
|
// key bindings
|
||||||
|
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
||||||
|
if (focused && !ImGui::IsAnyItemFocused()) {
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
|
||||||
|
if (cursor_ > 0) MoveCursorTo(cursor_-1);
|
||||||
|
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
|
||||||
|
MoveCursorTo(cursor_+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
win_.End();
|
win_.End();
|
||||||
}
|
}
|
||||||
void TL::HandleTimelineAction() noexcept {
|
void TL::UpdateLambdaSelector() noexcept {
|
||||||
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
|
const auto current_lambda =
|
||||||
if (!item) return;
|
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
|
||||||
|
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
|
||||||
|
if (lambda_) {
|
||||||
|
if (ImGui::Selectable("detach from current lambda")) {
|
||||||
|
AttachLambda(nullptr);
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
for (const auto& wptr : lambdas_running_) {
|
||||||
|
auto ptr = wptr.lock();
|
||||||
|
if (!ptr) continue;
|
||||||
|
|
||||||
|
const auto name = nf7::gui::GetParentContextDisplayName(*ptr);
|
||||||
|
if (ImGui::Selectable(name.c_str(), ptr == lambda_)) {
|
||||||
|
AttachLambda(ptr);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::TextUnformatted("call stack:");
|
||||||
|
ImGui::Indent();
|
||||||
|
nf7::gui::ContextStack(*ptr);
|
||||||
|
ImGui::Unindent();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void TL::HandleTimelineAction() noexcept {
|
||||||
|
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
|
||||||
|
const auto action_time = tl_.actionTime();
|
||||||
|
const auto action_time_i = static_cast<int64_t>(action_time);
|
||||||
|
|
||||||
const auto& t = item->displayTiming();
|
|
||||||
switch (tl_.action()) {
|
switch (tl_.action()) {
|
||||||
case tl_.kSelect:
|
case tl_.kSelect:
|
||||||
|
assert(item);
|
||||||
item->Select();
|
item->Select();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case tl_.kResizeBegin:
|
case tl_.kResizeBegin:
|
||||||
|
assert(item);
|
||||||
ResizeDisplayTimingOfSelected(
|
ResizeDisplayTimingOfSelected(
|
||||||
static_cast<int64_t>(tl_.gripTime()) - static_cast<int64_t>(t.begin()), 0);
|
action_time_i - static_cast<int64_t>(item->displayTiming().begin()),
|
||||||
|
0);
|
||||||
break;
|
break;
|
||||||
case tl_.kResizeEnd:
|
case tl_.kResizeEnd:
|
||||||
|
assert(item);
|
||||||
ResizeDisplayTimingOfSelected(
|
ResizeDisplayTimingOfSelected(
|
||||||
0, static_cast<int64_t>(tl_.gripTime()+t.dur()) - static_cast<int64_t>(t.end()));
|
0,
|
||||||
|
action_time_i +
|
||||||
|
static_cast<int64_t>(item->displayTiming().dur()) -
|
||||||
|
static_cast<int64_t>(item->displayTiming().end()));
|
||||||
break;
|
break;
|
||||||
case tl_.kResizeBeginDone:
|
case tl_.kResizeBeginDone:
|
||||||
case tl_.kResizeEndDone:
|
case tl_.kResizeEndDone:
|
||||||
|
assert(item);
|
||||||
ExecApplyTimingOfSelected();
|
ExecApplyTimingOfSelected();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case tl_.kMove:
|
case tl_.kMove:
|
||||||
|
assert(item);
|
||||||
MoveDisplayTimingOfSelected(
|
MoveDisplayTimingOfSelected(
|
||||||
static_cast<int64_t>(tl_.gripTime()) - static_cast<int64_t>(t.begin()));
|
action_time_i - static_cast<int64_t>(item->displayTiming().begin()));
|
||||||
if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
|
if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
|
||||||
MoveDisplayLayerOfSelected(
|
MoveDisplayLayerOfSelected(
|
||||||
static_cast<int64_t>(layer->index()) - static_cast<int64_t>(item->displayLayer().index()));
|
static_cast<int64_t>(layer->index()) -
|
||||||
|
static_cast<int64_t>(item->displayLayer().index()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case tl_.kMoveDone:
|
case tl_.kMoveDone:
|
||||||
|
assert(item);
|
||||||
ExecApplyTimingOfSelected();
|
ExecApplyTimingOfSelected();
|
||||||
ExecApplyLayerOfSelected();
|
ExecApplyLayerOfSelected();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case tl_.kSetTime:
|
||||||
|
MoveCursorTo(action_time);
|
||||||
|
break;
|
||||||
|
|
||||||
case tl_.kNone:
|
case tl_.kNone:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1101,6 +1523,36 @@ void TL::AddItemPopup::Update() noexcept {
|
|||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void TL::ConfigPopup::Update() noexcept {
|
||||||
|
if (Popup::Begin()) {
|
||||||
|
ImGui::TextUnformatted("Sequencer/Timeline: updating config...");
|
||||||
|
ImGui::InputTextMultiline("inputs", &inputs_);
|
||||||
|
ImGui::InputTextMultiline("outputs", &outputs_);
|
||||||
|
|
||||||
|
if (ImGui::Button("ok")) {
|
||||||
|
try {
|
||||||
|
auto i = ParseSocketSpecifier(inputs_);
|
||||||
|
auto o = ParseSocketSpecifier(outputs_);
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(
|
||||||
|
*owner_, "updating Sequencer/Timeline config");
|
||||||
|
owner_->env().ExecMain(ctx, [f = owner_, i = std::move(i), o = std::move(o)]() {
|
||||||
|
f->seq_inputs_ = std::move(i);
|
||||||
|
f->seq_outputs_ = std::move(o);
|
||||||
|
f->ApplySeqSocketChanges();
|
||||||
|
});
|
||||||
|
} catch (nf7::Exception& e) {
|
||||||
|
error_ = e.msg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error_.size() > 0) {
|
||||||
|
ImGui::Bullet();
|
||||||
|
ImGui::TextUnformatted(error_.c_str());
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
4
main.cc
4
main.cc
@ -59,8 +59,8 @@ class Env final : public nf7::Env {
|
|||||||
try {
|
try {
|
||||||
yas::load<yas::file|yas::binary>("root.nf7", root_);
|
yas::load<yas::file|yas::binary>("root.nf7", root_);
|
||||||
root_->MakeAsRoot();
|
root_->MakeAsRoot();
|
||||||
} catch (yas::io_exception&) {
|
} catch (std::exception& e) {
|
||||||
throw Exception("failed to read: "s+kFileName);
|
throw Exception("failed to read: "s+kFileName+" ("+e.what()+")");
|
||||||
}
|
}
|
||||||
} catch (Exception&) {
|
} catch (Exception&) {
|
||||||
Panic();
|
Panic();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user