diff --git a/CMakeLists.txt b/CMakeLists.txt index f2e08767ef9cb02acad177159f7c2f1129672ed2..2cab36015d7c3c7e42c026362e0b3958e3960882 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,8 +394,8 @@ if (WIN32) # TODO(nick) Should do based upon compiler (VS) set(OS_LIBS "") else() add_definitions(-DUNIX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -msse3 -Wall") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG -pg") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -msse3 -Wall -Werror=return-type") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -D_DEBUG -pg") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -mfpmath=sse") set(OS_LIBS "dl") endif() diff --git a/applications/gui/src/config_window.hpp b/applications/gui/src/config_window.hpp index a7acd117116553f3e902bdc6640d4f67e71b1f30..06f989e0b6ee3ac328264dc2b2bf0245956e5c7c 100644 --- a/applications/gui/src/config_window.hpp +++ b/applications/gui/src/config_window.hpp @@ -21,7 +21,7 @@ class ConfigWindow : public nanogui::Window { private: ftl::ctrl::Master *ctrl_; - + void _buildForm(const std::string &uri); void _addElements(nanogui::FormHelper *form, const std::string &suri); bool exists(const std::string &uri); diff --git a/applications/gui2/CMakeLists.txt b/applications/gui2/CMakeLists.txt index 5dcee07d03f95a005f1eaefeeb0f72352151dc1e..d3d1a8c5887fcaf483a5874fb952fdbdab0e4af7 100644 --- a/applications/gui2/CMakeLists.txt +++ b/applications/gui2/CMakeLists.txt @@ -2,17 +2,34 @@ #include_directories(${PROJECT_SOURCE_DIR}/reconstruct/include) #include_directories(${PROJECT_BINARY_DIR}) +function(add_gui_module NAME) + list(APPEND GUI2SRC "src/modules/${NAME}/control.cpp") + + get_filename_component(FULLPATH "src/modules/${NAME}/view.cpp" ABSOLUTE) + if (EXISTS ${FULLPATH}) + list(APPEND GUI2SRC "src/modules/${NAME}/view.cpp") + endif() + + set(GUI2SRC ${GUI2SRC} PARENT_SCOPE) +endfunction() + set(GUI2SRC src/main.cpp src/inputoutput.cpp src/screen.cpp src/view.cpp src/gltexture.cpp - src/modules/home.cpp + src/frameview.cpp ) +add_gui_module("config") +add_gui_module("camera") +add_gui_module("thumbnails") +add_gui_module("renderer") +add_gui_module("record") + if (HAVE_OPENVR) - list(APPEND GUI2SRC "src/vr/vr.cpp") + #list(APPEND GUI2SRC "src/vr/vr.cpp") endif() # Various preprocessor definitions have been generated by NanoGUI diff --git a/applications/gui2/README.md b/applications/gui2/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0707c342bb38c3bc258b852ad2dbb5c9fc9b93d0 --- /dev/null +++ b/applications/gui2/README.md @@ -0,0 +1,51 @@ +GUI + +Nanogui based graphical user interface. + +General: + * Do not modify gui outside gui thread (main). Modifications must be done in + GUI callbacks or draw(). + * Expensive processing should be moved out of gui thread (draw() and callbacks) + * Module is only required to implement Module. Each module is expected to be + loaded only once (this design decision could be modified). + +Classes + +Screen + * Implements main screen: toolbar and view + * Interface for registering new modules. + * Interface for adding/removing buttons + * Interface for setting active View. Inactive view is removed and destroyed if + no other references are remaining. + * Note: toolbar could be a module, but other modules likely assume it is + always available anyways. + +Module (controller) + * GUI module class wraps pointers for io, config and net. Initialization should + add necessary buttons to Screen + * Build necessary callbacks to process data from InputOutput to view. + Note: If callback passes data to view, callback handle should be owned by + the view or Module has to keep a nanogui reference to the View. + +View + * active view will be the main window; only one view can be active at time + * button callbacks (eg. those registered by module init) may change active view + * Destroyed when view is changed. Object lifetime can be used to remove + callbacks from InputOutput (TODO: only one active callback supported at the + moment) + * Implementations do not have to inherit from View. Popup/Window/Widget... can + be used to implement UI components available from any mode (config, record). + +InputOutput + * Contains all relevant datastructures and objects for FTL system. + * Network + * Pipelines (incl. rendering) TODO + * Audio player TODO + * Recording TODO + + +NanoGUI notes: + * Window instances can only be deleted with dispose(). + * If sub-windows are created, they have to hold to a reference to parent + object if they share resources (eg. in lambda callbacks + [ref = std::move(ref)] could be used). diff --git a/applications/gui2/src/frameview.cpp b/applications/gui2/src/frameview.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce135aa655684069222d10e98775af2a39c56e0d --- /dev/null +++ b/applications/gui2/src/frameview.cpp @@ -0,0 +1,128 @@ +#include <loguru.hpp> + +#include <nanogui/screen.h> + +#include <ftl/cuda_common.hpp> +#include <ftl/cuda_texture.hpp> +#include <ftl/exception.hpp> + +#include "gltexture.hpp" +#include "frameview.hpp" + +using ftl::gui2::FrameView; + +namespace { + // modify gl_Position to flip y axis (flip sign) + constexpr char const *const GLFrameViewVertexShader = + R"(#version 330 + uniform vec2 scaleFactor; + uniform vec2 position; + in vec2 vertex; + out vec2 uv; + void main() { + uv = vertex; + vec2 scaledVertex = (vertex * scaleFactor) + position; + gl_Position = vec4(2.0*scaledVertex.x - 1.0, + 1.0 - 2.0*scaledVertex.y, + 0.0, 1.0); + + })"; + + // set color.w = 1.0f ; possibly bug: incorrect value from getTexture()? + constexpr char const *const GLFrameViewFragmentShader = + R"(#version 330 + uniform sampler2D image; + out vec4 color; + in vec2 uv; + void main() { + color = texture(image, uv); + color.w = 1.0f; + })"; + +} + +void buildFrameViewShader(nanogui::GLShader &shader) { + + shader.init("GLFrameViewShader", GLFrameViewVertexShader, + GLFrameViewFragmentShader); + + nanogui::MatrixXu indices(3, 2); + indices.col(0) << 0, 1, 2; + indices.col(1) << 2, 3, 1; + + nanogui::MatrixXf vertices(2, 4); + vertices.col(0) << 0, 0; + vertices.col(1) << 1, 0; + vertices.col(2) << 0, 1; + vertices.col(3) << 1, 1; + + shader.bind(); + shader.uploadIndices(indices); + shader.uploadAttrib("vertex", vertices); +} + +FrameView::FrameView(nanogui::Widget *parent) : + Widget(parent), texture(ftl::gui2::GLTexture::Type::BGRA) { + + if (glfwGetCurrentContext() == nullptr) { + throw FTL_Error("No current OpenGL context"); + } + buildFrameViewShader(mShader); +} + +void FrameView::draw(NVGcontext *ctx) { + Widget::draw(ctx); + if (flush_) { + nvgEndFrame(ctx); // Flush the NanoVG draw stack, not necessary to call nvgBeginFrame afterwards. + } + if (copy) { + auto &buffer = frame.createTexture<uchar4>(channel, true); + texture.make(buffer.width(), buffer.height()); + auto dst = texture.map(stream); + cudaMemcpy2D(dst.data, dst.step1(), buffer.devicePtr(), buffer.pitch(), buffer.width()*4, buffer.height(), cudaMemcpyDeviceToDevice); + texture.unmap(stream); + copy = false; + } + + if (!texture.isValid()) { + return; + } + + const nanogui::Vector2f imageSize = nanogui::Vector2f(texture.width(), texture.height()); + const nanogui::Vector2f widgetSize = size().cast<float>(); + const nanogui::Vector2f screenSize = screen()->size().cast<float>(); + + // scale to fill screen + float scale = widgetSize.cwiseQuotient(imageSize).minCoeff(); + const nanogui::Vector2f scaleFactor = imageSize.cwiseQuotient(screenSize)* scale; + const nanogui::Vector2f scaledImageSize = scale * imageSize; + + // center + const nanogui::Vector2f offset = (widgetSize - scaledImageSize) / 2; + const nanogui::Vector2f positionInScreen = absolutePosition().cast<float>() + offset; + const nanogui::Vector2f imagePosition = positionInScreen.cwiseQuotient(screenSize); + + mShader.bind(); + glEnable(GL_SCISSOR_TEST); + float r = screen()->pixelRatio(); + glScissor(positionInScreen.x() * r, + (screenSize.y() - positionInScreen.y() - size().y()) * r, + size().x() * r, size().y() * r); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture.texture()); + mShader.setUniform("image", 0); + mShader.setUniform("scaleFactor", scaleFactor); + mShader.setUniform("position", imagePosition); + mShader.drawIndexed(GL_TRIANGLES, 0, 2); + glDisable(GL_SCISSOR_TEST); +} + +void FrameView::set(ftl::rgbd::Frame &f, ftl::codecs::Channel c, cudaStream_t s, bool cp) { + f.swapTo(frame); + channel = c; + stream = s; + if (cp) { + copy = true; + } +} diff --git a/applications/gui2/src/frameview.hpp b/applications/gui2/src/frameview.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b315ff9d071d43ff6dc2eff1dae1c3251feec3e3 --- /dev/null +++ b/applications/gui2/src/frameview.hpp @@ -0,0 +1,35 @@ + +#include <ftl/rgbd/frame.hpp> + +#include <nanogui/widget.h> +#include <nanogui/glutil.h> + +namespace ftl { +namespace gui2 { + +/// Widget for ftl::cuda::TextureObject<uchar4> +class FrameView : public nanogui::Widget { +public: + FrameView(nanogui::Widget* parent); + virtual void draw(NVGcontext *ctx) override; + + /** Set frame. If copy == true, buffer will be copied to OpenGL framebuffer + * at next draw() call. */ + void set(ftl::rgbd::Frame &frame, ftl::codecs::Channel channel, cudaStream_t stream=0, bool copy=false); + /** should NanoVG draw stack be flushed before drawing the texture? */ + void setFlush(bool v) { flush_ = v; } + +private: + ftl::rgbd::Frame frame; + ftl::codecs::Channel channel; + cudaStream_t stream; + + GLTexture texture; + nanogui::GLShader mShader; + + std::atomic<bool> copy = false; + bool flush_ = false; +}; + +} +} diff --git a/applications/gui2/src/gltexture.cpp b/applications/gui2/src/gltexture.cpp index 5e6d68469616430fbbd7b2a364416fbc112551f6..fe6f7a121ffb4af28a2e1d741b57287f29f9c2f2 100644 --- a/applications/gui2/src/gltexture.cpp +++ b/applications/gui2/src/gltexture.cpp @@ -16,7 +16,6 @@ GLTexture::GLTexture(GLTexture::Type type) { cuda_res_ = nullptr; width_ = 0; height_ = 0; - changed_ = true; type_ = type; } @@ -24,31 +23,6 @@ GLTexture::~GLTexture() { //glDeleteTextures(1, &glid_); } -void GLTexture::update(cv::Mat &m) { - LOG(INFO) << "DEPRECATED"; - if (m.rows == 0) return; - if (glid_ == std::numeric_limits<unsigned int>::max()) { - glGenTextures(1, &glid_); - glBindTexture(GL_TEXTURE_2D, glid_); - //cv::Mat m(cv::Size(100,100), CV_8UC3); - if (type_ == Type::BGRA) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m.cols, m.rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, m.data); - } else if (type_ == Type::Float) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m.cols, m.rows, 0, GL_RED, GL_FLOAT, m.data); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } else { - //glBindTexture(GL_TEXTURE_2D, glid_); - // TODO Allow for other formats - //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m.cols, m.rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, m.data); - } - auto err = glGetError(); - if (err != 0) LOG(ERROR) << "OpenGL Texture error: " << err; -} - void GLTexture::make(int width, int height) { if (width != width_ || height != height_) { free(); @@ -114,6 +88,7 @@ void GLTexture::free() { } cv::cuda::GpuMat GLTexture::map(cudaStream_t stream) { + mtx_.lock(); void *devptr; size_t size; cudaSafeCall(cudaGraphicsMapResources(1, &cuda_res_, stream)); @@ -122,8 +97,9 @@ cv::cuda::GpuMat GLTexture::map(cudaStream_t stream) { } void GLTexture::unmap(cudaStream_t stream) { + // note: code must not throw, otherwise mtx_.unlock() does not happen + cudaSafeCall(cudaGraphicsUnmapResources(1, &cuda_res_, stream)); - changed_ = true; //glActiveTexture(GL_TEXTURE0); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, glbuf_); @@ -139,6 +115,8 @@ void GLTexture::unmap(cudaStream_t stream) { glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0); + + mtx_.unlock(); } unsigned int GLTexture::texture() const { @@ -153,6 +131,6 @@ unsigned int GLTexture::texture() const { return glid_; } else { - return glid_; + throw FTL_Error("No OpenGL texture; use make() first"); } } diff --git a/applications/gui2/src/gltexture.hpp b/applications/gui2/src/gltexture.hpp index ce4e19f46220e56a4be82ff8122a6a509c1946c0..b748c4490f966398a7506a9b9fe353fe95da1ec4 100644 --- a/applications/gui2/src/gltexture.hpp +++ b/applications/gui2/src/gltexture.hpp @@ -1,9 +1,6 @@ -#ifndef _FTL_GUI_GLTEXTURE_HPP_ -#define _FTL_GUI_GLTEXTURE_HPP_ +#pragma once -#include <opencv2/core/mat.hpp> - -#include <cuda_runtime.h> +#include <ftl/cuda_common.hpp> struct cudaGraphicsResource; @@ -11,7 +8,7 @@ namespace ftl { namespace gui2 { class GLTexture { - public: +public: enum class Type { RGBA, BGRA, @@ -21,32 +18,34 @@ class GLTexture { explicit GLTexture(Type); ~GLTexture(); - void update(cv::Mat &m); + bool isValid() const { return glid_ != std::numeric_limits<unsigned int>::max(); } + int width() const { return width_; } + int height() const { return height_; } + + std::mutex& mutex() { return mtx_; } + + // acquire mutex before make() or free() void make(int width, int height); + void free(); unsigned int texture() const; - bool isValid() const { return glid_ != std::numeric_limits<unsigned int>::max(); } cv::cuda::GpuMat map(cudaStream_t stream); void unmap(cudaStream_t stream); - void free(); - - int width() const { return width_; } - int height() const { return height_; } +private: - private: unsigned int glid_; unsigned int glbuf_; int width_; int height_; int stride_; - bool changed_; + Type type_; + std::mutex mtx_; // for locking while in use (opengl thread calls lock() or cuda mapped) + cudaGraphicsResource *cuda_res_; }; } } - -#endif // _FTL_GUI_GLTEXTURE_HPP_ diff --git a/applications/gui2/src/inputoutput.cpp b/applications/gui2/src/inputoutput.cpp index bb7375484df0ea49018536a863a816cd46bf44ae..76a4f5fce8f940a5757de8cde9684e41c8e7fe4a 100644 --- a/applications/gui2/src/inputoutput.cpp +++ b/applications/gui2/src/inputoutput.cpp @@ -8,11 +8,13 @@ using ftl::gui2::InputOutput; using ftl::codecs::Channel; -InputOutput::InputOutput(ftl::Configurable *root, ftl::net::Universe *net) : net_(net) { +InputOutput::InputOutput(ftl::Configurable *root, ftl::net::Universe *net) : + audio_callbacks_(), video_callbacks_(), net_(net) { + UNIQUE_LOCK(mtx_, lk); - controller_ = std::unique_ptr<ftl::ctrl::Master>(new ftl::ctrl::Master(root, net)); - controller_->onLog([](const ftl::ctrl::LogEvent &e){ + master_ = std::unique_ptr<ftl::ctrl::Master>(new ftl::ctrl::Master(root, net)); + master_->onLog([](const ftl::ctrl::LogEvent &e){ const int v = e.verbosity; switch (v) { case -2: LOG(ERROR) << "Remote log: " << e.message; break; @@ -78,11 +80,16 @@ InputOutput::InputOutput(ftl::Configurable *root, ftl::net::Universe *net) : net } }); - receiver_->onFrameSet([this](ftl::rgbd::FrameSet &fs){ return process_callbacks_video(fs); }); + receiver_->onFrameSet([this](ftl::rgbd::FrameSet &fs){ + //processFrameSet_(fs); + video_callbacks_.trigger(fs); + return true; + }); speaker_ = std::unique_ptr<ftl::audio::Speaker>(ftl::create<ftl::audio::Speaker>(root, "speaker_test")); receiver_->onAudio([this](ftl::audio::FrameSet &fs) { + audio_callbacks_.trigger(fs); /* if (framesets_.size() == 0) return true; auto *c = screen_->activeCamera(); @@ -91,7 +98,8 @@ InputOutput::InputOutput(ftl::Configurable *root, ftl::net::Universe *net) : net speaker_->queue(fs.timestamp, fs.frames[0]); //LOG(INFO) << "Audio delay = " << (fs.timestamp - framesets_[0]->timestamp + renddelay); - */return true; + */ + return true; }); /*ftl::timer::add(ftl::timer::kTimerMain, [this](int64_t ts) { @@ -155,28 +163,71 @@ InputOutput::InputOutput(ftl::Configurable *root, ftl::net::Universe *net) : net stream_->begin(); } -bool InputOutput::process_callbacks_video(ftl::rgbd::FrameSet &fs) { - UNIQUE_LOCK(mtx_, lk); - for (auto &f : cb_video_) { - f(fs); - } - return true; -} +void InputOutput::processFrameSet_(ftl::rgbd::FrameSet &fs) { + // Request the channels required by current camera configuration + /*if (fromstream) { + auto cs = _aggregateChannels(fs.id); -bool InputOutput::process_callbacks_audio(ftl::audio::FrameSet &fs) { - UNIQUE_LOCK(mtx_, lk); - for (auto &f : cb_audio_) { - f(fs); + auto avail = static_cast<const ftl::stream::Stream*>(interceptor_)->available(fs.id); + if (cs.has(Channel::Depth) && !avail.has(Channel::Depth) && avail.has(Channel::Right)) { + cs -= Channel::Depth; + cs += Channel::Right; + } + interceptor_->select(fs.id, cs); + }*/ + + // Make sure there are enough framesets allocated + /*{ + UNIQUE_LOCK(mutex_, lk); + _checkFrameSets(fs.id); + }*/ + + // !config()->value("drop_partial_framesets", false) + if (!fs.test(ftl::data::FSFlag::PARTIAL)) { + // Enforce interpolated colour and GPU upload + for (size_t i=0; i<fs.frames.size(); ++i) { + if (!fs.hasFrame(i)) { + continue; + } + fs.frames[i].createTexture<uchar4>(Channel::Colour, true); + + // TODO: Do all channels. This is a fix for screen capture sources. + // pre_pipelines_[fs.id]->getStream() + if (!fs.frames[i].isGPU(Channel::Colour)) { + fs.frames[i].upload(ftl::codecs::Channels<0>(Channel::Colour), 0); + } + } + + //fs.mask &= pre_pipelines_[fs.id]->value("frame_mask", 0xFFFF); + + /*{ + FTL_Profile("Prepipe",0.020); + pre_pipelines_[fs.id]->apply(fs, fs, 0); + }*/ + + //fs.swapTo(*framesets_[fs.id]); + } else { + LOG(WARNING) << "Dropping frameset: " << fs.timestamp; } - return true; + + /* + size_t i=0; + for (auto cam : cameras_) { + // Only update the camera periodically unless the active camera + if (screen_->activeCamera() == cam.second.camera || + (screen_->activeCamera() == nullptr && cycle_ % cameras_.size() == i++)) cam.second.camera->update(framesets_); + + ftl::codecs::Channels<0> channels; + if (fromstream) channels = cstream->available(fs.id); + //if ((*framesets_[fs.id]).frames.size() > 0) channels += (*framesets_[fs.id]).frames[0].getChannels(); + cam.second.camera->update(fs.id, channels); + }*/ } -void InputOutput::subscribe(const std::function<bool(ftl::rgbd::FrameSet&)> &f) { - UNIQUE_LOCK(mtx_, lk); - cb_video_.push_back(f); + ftl::Handle InputOutput::addCallback(const std::function<bool(ftl::rgbd::FrameSet&)> f) { + return video_callbacks_.on(f); } -void InputOutput::subscribe(const std::function<bool(ftl::audio::FrameSet&)> &f) { - UNIQUE_LOCK(mtx_, lk); - cb_audio_.push_back(f); + ftl::Handle InputOutput::addCallback(const std::function<bool(ftl::audio::FrameSet&)> f) { + return audio_callbacks_.on(f); } diff --git a/applications/gui2/src/inputoutput.hpp b/applications/gui2/src/inputoutput.hpp index ad444c3f408b7bf79845aceacea9b202db0fc6f1..e1fb2abd9e44634dcab6b35abfede5efcccbe5ed 100644 --- a/applications/gui2/src/inputoutput.hpp +++ b/applications/gui2/src/inputoutput.hpp @@ -3,6 +3,7 @@ #include <memory> #include <mutex> +#include <ftl/handle.hpp> #include <ftl/configuration.hpp> #include <ftl/net/universe.hpp> #include <ftl/master.hpp> @@ -21,33 +22,27 @@ public: InputOutput(const InputOutput&) = delete; void operator=(const InputOutput&) = delete; - void subscribe(const std::function<bool(ftl::rgbd::FrameSet&)>&); - void subscribe(const std::function<bool(ftl::audio::FrameSet&)>&); - void unsubscribe(); - void unsubscribe_audio(); - void unsubscribe_video(); + ftl::Handle addCallback(std::function<bool(ftl::rgbd::FrameSet&)>); + ftl::Handle addCallback(std::function<bool(ftl::audio::FrameSet&)>); ftl::net::Universe* net() const; + ftl::ctrl::Master* master() const { return master_.get(); }; private: - bool process_callbacks_video(ftl::rgbd::FrameSet &fs); - bool process_callbacks_audio(ftl::audio::FrameSet &fs); + ftl::Handler<ftl::audio::FrameSet&> audio_callbacks_; + ftl::Handler<ftl::rgbd::FrameSet&> video_callbacks_; + + void processFrameSet_(ftl::rgbd::FrameSet &fs); std::mutex mtx_; ftl::net::Universe* net_; - std::unique_ptr<ftl::ctrl::Master> controller_; + std::unique_ptr<ftl::ctrl::Master> master_; std::unique_ptr<ftl::stream::Muxer> stream_; std::unique_ptr<ftl::stream::Intercept> interceptor_; std::unique_ptr<ftl::stream::File> recorder_; std::unique_ptr<ftl::stream::Receiver> receiver_; std::unique_ptr<ftl::audio::Speaker> speaker_; - //std::unordered_map<std::string, ftl::stream::Stream*> available_; - - std::vector<uintptr_t> cb_video_id_; - std::vector<uintptr_t> cb_audio_id_; - std::vector<std::function<bool(ftl::rgbd::FrameSet&)>> cb_video_; - std::vector<std::function<bool(ftl::audio::FrameSet&)>> cb_audio_; int frameset_counter_ = 0; }; diff --git a/applications/gui2/src/main.cpp b/applications/gui2/src/main.cpp index 7a85838c7670be91282a991c3187fca8f0c93229..d18bb2a7e4045671cfe808f9d04dfa44fba90402 100644 --- a/applications/gui2/src/main.cpp +++ b/applications/gui2/src/main.cpp @@ -12,77 +12,120 @@ #include <cuda_gl_interop.h> #include "inputoutput.hpp" -#include "modules.hpp" +#include "module.hpp" #include "screen.hpp" +#include "modules.hpp" + using std::unique_ptr; +using std::make_unique; + +/** + * FTL Graphical User Interface + * Single screen, loads configuration and sets up networking and input/output. + * Loads required modules to gui. + */ +class FTLGui { +public: + FTLGui(int argc, char **argv); + ~FTLGui(); + + template<typename T> + T* loadModule(const std::string &name); + void mainloop(); + +private: + std::unique_ptr<ftl::Configurable> root_; + std::unique_ptr<ftl::net::Universe> net_; + std::unique_ptr<ftl::gui2::InputOutput> io_; + + nanogui::ref<ftl::gui2::Screen> screen_; +}; + +template<typename T> +T* FTLGui::loadModule(const std::string &name) { + return screen_->addModule<T>(name, root_.get(), screen_.get(), io_.get()); +} -int main(int argc, char **argv) { +FTLGui::FTLGui(int argc, char **argv) { + using namespace ftl::gui2; + + screen_ = new Screen(); int cuda_device; cudaSafeCall(cudaGetDevice(&cuda_device)); //cudaSafeCall(cudaGLSetGLDevice(cuda_device)); - auto root = unique_ptr<ftl::Configurable>(ftl::configure(argc, argv, "gui_default")); - auto net = unique_ptr<ftl::net::Universe>(ftl::create<ftl::net::Universe>(root.get(), "net")); + root_ = unique_ptr<ftl::Configurable>(ftl::configure(argc, argv, "gui_default")); + net_ = unique_ptr<ftl::net::Universe>(ftl::create<ftl::net::Universe>(root_.get(), "net")); + io_ = make_unique<ftl::gui2::InputOutput>(root_.get(), net_.get()); + + net_->start(); + net_->waitConnections(); - auto io = ftl::gui2::InputOutput(root.get(), net.get()); + loadModule<ThumbnailsController>("home")->activate(); + loadModule<Camera>("camera"); + loadModule<ConfigWindowController>("configwindow"); + loadModule<Renderer>("renderer"); + //loadModule<RecordController>("record"); +} + +FTLGui::~FTLGui() { + net_->shutdown(); +} - net->start(); - net->waitConnections(); +void FTLGui::mainloop() { + // implements similar main loop as nanogui::mainloop() ftl::timer::start(); - try { - nanogui::init(); - { - nanogui::ref<ftl::gui2::Screen> gui = - new ftl::gui2::Screen(); - nanogui::ref<ftl::gui2::HomeView> home = - new ftl::gui2::HomeView(root.get(), &io); - - gui->setView(home); - gui->drawAll(); - gui->setVisible(true); - float last_draw_time = 0.0f; - - while (ftl::running) { - if (!gui->visible()) { - ftl::running = false; - } - else if (glfwWindowShouldClose(gui->glfwWindow())) { - gui->setVisible(false); - ftl::running = false; - } - else { - float now = float(glfwGetTime()); - float delta = now - last_draw_time; - - // Generate poses and render and virtual frame here - // at full FPS (25 without VR and 90 with VR currently) - //gui->drawFast(); - - // Only draw the GUI at 25fps - if (delta >= 0.04f) { - last_draw_time = now; - gui->drawAll(); - } - } - - // Wait for mouse/keyboard or empty refresh events - glfwPollEvents(); - } - - // Process events once more - glfwPollEvents(); - - LOG(INFO) << "Stopping..."; - ftl::timer::stop(false); - ftl::pool.stop(true); - LOG(INFO) << "All threads stopped."; + screen_->setVisible(true); + screen_->drawAll(); + + float last_draw_time = 0.0f; + + while (ftl::running) { + if (!screen_->visible()) { + ftl::running = false; + } + else if (glfwWindowShouldClose(screen_->glfwWindow())) { + screen_->setVisible(false); + ftl::running = false; + } + else { + float now = float(glfwGetTime()); + //float delta = now - last_draw_time; + + // Generate poses and render and virtual frame here + // at full FPS (25 without VR and 90 with VR currently) + //screen_->render(); + + // Only draw the GUI at 25fps + //if (delta >= 0.04f) { + last_draw_time = now; + screen_->drawAll(); + //} } - nanogui::shutdown(); + // Wait for mouse/keyboard or empty refresh events + glfwWaitEvents(); // VR headest issues + //glfwPollEvents(); + } + + // Process events once more + glfwPollEvents(); +} + +//////////////////////////////////////////////////////////////////////////////// + +int main(int argc, char **argv) { + + nanogui::init(); + + FTLGui gui(argc, argv); + + try { + gui.mainloop(); } catch (const ftl::exception &e) { @@ -99,6 +142,12 @@ int main(int argc, char **argv) { return -1; } - net->shutdown(); + nanogui::shutdown(); + + LOG(INFO) << "Stopping..."; + ftl::timer::stop(false); + ftl::pool.stop(true); + LOG(INFO) << "All threads stopped."; + return 0; } diff --git a/applications/gui2/src/module.hpp b/applications/gui2/src/module.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b00f4b55b80fca23b93557febae04e763345e550 --- /dev/null +++ b/applications/gui2/src/module.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "view.hpp" +#include "inputoutput.hpp" + +#include <ftl/configurable.hpp> + +#include <nanogui/entypo.h> +#include <nanogui/button.h> + +namespace ftl { +namespace gui2 { + +class Screen; + +class Module : public ftl::Configurable { +public: + Module(nlohmann::json &config, Screen *screen, InputOutput *io) : + Configurable(config), screen(screen), io(io) {} + + /// Cerform any initialization + virtual void init() {}; + virtual ~Module() {}; + +protected: + ftl::gui2::Screen* const screen; + ftl::gui2::InputOutput* const io; +}; + +} +} diff --git a/applications/gui2/src/modules.hpp b/applications/gui2/src/modules.hpp index 8fefa25c7e92ed225eea670e07b5d83976d6388d..ca7f8b708a10f0c7549a1fed232ebb2c06b076f0 100644 --- a/applications/gui2/src/modules.hpp +++ b/applications/gui2/src/modules.hpp @@ -1,2 +1,7 @@ #pragma once -#include "modules/home.hpp" + +#include "modules/thumbnails/control.hpp" +#include "modules/record/control.hpp" +#include "modules/camera/control.hpp" +#include "modules/config/control.hpp" +#include "modules/renderer/control.hpp" diff --git a/applications/gui2/src/modules/camera/control.cpp b/applications/gui2/src/modules/camera/control.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a872963a9d80469afe6168b2f0708ad462682c6c --- /dev/null +++ b/applications/gui2/src/modules/camera/control.cpp @@ -0,0 +1,26 @@ +#include "control.hpp" +#include "view.hpp" + +using ftl::gui2::Camera; + +void Camera::activate() { + auto view = new ftl::gui2::CameraView(screen); + + view->setHandle( + io->addCallback([this, view](ftl::rgbd::FrameSet& fs){ + view->update(fs, source_idx); + screen->redraw(); + return true; + })); + + screen->setView(view); +} + +/*void Camera::deactivate() { + io->removeCallbackVideo(); + io->removeCallbackAudio(); +}*/ + +void Camera::setSource(int idx) { + source_idx = idx; +} diff --git a/applications/gui2/src/modules/camera/control.hpp b/applications/gui2/src/modules/camera/control.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c513ed5e4a4e960d3bae78515d921d51b9dd62f6 --- /dev/null +++ b/applications/gui2/src/modules/camera/control.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../../module.hpp" +#include "../../screen.hpp" + +namespace ftl { +namespace gui2 { + +class Camera : public Module { +public: + using Module::Module; + + virtual void activate(); + + void setSource(int); + +private: + int source_idx = -1; +}; + +} +} diff --git a/applications/gui2/src/modules/camera/view.cpp b/applications/gui2/src/modules/camera/view.cpp new file mode 100644 index 0000000000000000000000000000000000000000..77212bc96f12f4503647ad25050ddceaf783bd2b --- /dev/null +++ b/applications/gui2/src/modules/camera/view.cpp @@ -0,0 +1,34 @@ +#include <nanogui/screen.h> + +#include "view.hpp" + +using ftl::gui2::CameraView; + +CameraView::CameraView(nanogui::Widget* parent) : View(parent) { + + fview = new ftl::gui2::FrameView(this); + fview->setFlush(false); // buffer will be rendered as "background" +} + +void CameraView::draw(NVGcontext *ctx) { + + fview->setFixedSize(size()); + performLayout(ctx); + View::draw(ctx); +} + +void CameraView::update(ftl::rgbd::FrameSet &fs, int idx) { + auto channel = ftl::codecs::Channel::Colour; + + if (!fs.hasFrame(idx)) { + return; + } + auto &frame = fs.frames[idx]; + + if (!frame.hasChannel(channel)) { + return; + } + + fview->set(frame, channel, 0, true); + +} diff --git a/applications/gui2/src/modules/camera/view.hpp b/applications/gui2/src/modules/camera/view.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f1a2f57267b0a54c588e9470d467b028bd07bfba --- /dev/null +++ b/applications/gui2/src/modules/camera/view.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../../view.hpp" +#include "../../gltexture.hpp" +#include "../../frameview.hpp" + +#include <nanogui/imageview.h> + +namespace ftl { +namespace gui2 { + +class CameraView : public View { +public: + CameraView(nanogui::Widget* parent); + virtual void draw(NVGcontext *ctx) override; + void update(ftl::rgbd::FrameSet&fs, int id); + void setHandle(ftl::Handle&& handle) { handle_ = std::move(handle); } +private: + ftl::gui2::FrameView *fview = nullptr; + ftl::Handle handle_; +}; + +} +} diff --git a/applications/gui2/src/modules/config/control.cpp b/applications/gui2/src/modules/config/control.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9abc46918fa23ee3fffa4b5f6557a735d19f9fe8 --- /dev/null +++ b/applications/gui2/src/modules/config/control.cpp @@ -0,0 +1,27 @@ +#include "control.hpp" + +using ftl::gui2::ConfigWindowController; + +void ConfigWindowController::init() { + button = screen->addButton(); + button->setIcon(ENTYPO_ICON_COG); + button->setTooltip("Config"); + button->setCallback([this](){ + button->setPushed(false); + show(); + }); + button->setVisible(true); +} + +void ConfigWindowController::show() { + if (screen->childIndex(window) == -1) { + window = new ftl::gui2::ConfigWindow(screen, io->master()); + } + window->requestFocus(); + window->setVisible(true); + screen->performLayout(); +} + +ConfigWindowController::~ConfigWindowController() { + // remove window? +} diff --git a/applications/gui2/src/modules/config/control.hpp b/applications/gui2/src/modules/config/control.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8a4baa52f5c17ed0f9fdb6c21deeb07e8a3b2f5f --- /dev/null +++ b/applications/gui2/src/modules/config/control.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../../module.hpp" +#include "../../screen.hpp" + +#include "view.hpp" + +namespace ftl { +namespace gui2 { + +/** + * Controller for thumbnail view. + */ +class ConfigWindowController : public Module { +public: + using Module::Module; + virtual ~ConfigWindowController(); + + virtual void init() override; + virtual void show(); + +private: + nanogui::ToolButton *button; + ftl::gui2::ConfigWindow *window = nullptr; +}; + +} +} diff --git a/applications/gui2/src/modules/config/view.cpp b/applications/gui2/src/modules/config/view.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d19af3101465405452f1de855bbd6064b23373bc --- /dev/null +++ b/applications/gui2/src/modules/config/view.cpp @@ -0,0 +1,237 @@ + +#include <loguru.hpp> + +#include <nanogui/layout.h> +#include <nanogui/label.h> +#include <nanogui/button.h> +#include <nanogui/entypo.h> +#include <nanogui/formhelper.h> +#include <nanogui/vscrollpanel.h> +#include <nanogui/opengl.h> + +#include <nlohmann/json.hpp> + +#include <vector> +#include <string> + +#include "view.hpp" + +using ftl::gui2::ConfigWindow; +using std::string; +using std::vector; +using ftl::config::json_t; + +class SearchBox : public nanogui::TextBox { +private: + std::vector<std::string> configurables_; + Widget *buttons_; + std::string previous; + + void _setVisible(const std::string &str) { + // Check whether the search string has changed to prevent + // unnecessary searching. + if (str != previous) { + for (int i = configurables_.size()-1; i >= 0; --i) { + if (configurables_[i].find(mValueTemp) != std::string::npos) { + buttons_->childAt(i)->setVisible(true); + } else { + buttons_->childAt(i)->setVisible(false); + } + } + previous = str; + } + } + +public: + SearchBox(Widget *parent, std::vector<std::string> &configurables) : nanogui::TextBox(parent, ""), configurables_(configurables) { + setAlignment(TextBox::Alignment::Left); + setEditable(true); + setPlaceholder("Search"); + } + + ~SearchBox() { + } + + bool keyboardEvent(int key, int scancode, int action, int modifier) { + TextBox::keyboardEvent(key, scancode, action, modifier); + _setVisible(mValueTemp); + return true; + } + + void setButtons(Widget *buttons) { + buttons_ = buttons; + } +}; + +static std::string titleForURI(const ftl::URI &uri) { + auto *cfg = ftl::config::find(uri.getBaseURI()); + if (cfg && cfg->get<std::string>("title")) { + return *cfg->get<std::string>("title"); + } else if (uri.getPath().size() > 0) { + return uri.getPathSegment(-1); + } else { + return uri.getHost(); + } +} + +ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) + : nanogui::Window(parent, "Settings"), ctrl_(ctrl) { + using namespace nanogui; + + setLayout(new GroupLayout()); + setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f)); + + auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS); + close->setCallback([this](){ dispose();}); + + auto configurables = ftl::config::list(); + const auto size = configurables.size(); + + new Label(this, "Select Configurable","sans-bold"); + + auto searchBox = new SearchBox(this, configurables); + + auto vscroll = new VScrollPanel(this); + vscroll->setFixedHeight(300); + auto buttons = new Widget(vscroll); + buttons->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill)); + + searchBox->setButtons(buttons); + + std::vector<std::string> configurable_titles(size); + for (size_t i = 0; i < size; ++i) { + ftl::URI uri(configurables[i]); + std::string label = uri.getFragment(); + + size_t pos = label.find_last_of("/"); + if (pos != std::string::npos) label = label.substr(pos+1); + + std::string parentName = configurables[i]; + size_t pos2 = parentName.find_last_of("/"); + if (pos2 != std::string::npos) parentName = parentName.substr(0,pos2); + + // FIXME: Does not indicated parent indentation ... needs sorting? + + if (i > 0 && parentName == configurables[i-1]) { + ftl::URI uri(configurables[i-1]); + configurable_titles[i-1] = std::string("[") + titleForURI(uri) + std::string("] ") + uri.getFragment(); + + auto *prev = dynamic_cast<Button*>(buttons->childAt(buttons->childCount()-1)); + prev->setCaption(configurable_titles[i-1]); + prev->setBackgroundColor(nanogui::Color(0.3f,0.3f,0.3f,1.0f)); + prev->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); + prev->setIconPosition(Button::IconPosition::Left); + prev->setIcon(ENTYPO_ICON_FOLDER); + } + + configurable_titles[i] = label; + + auto itembutton = new nanogui::Button(buttons, configurable_titles[i]); + std::string c = configurables[i]; + itembutton->setTooltip(c); + itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f)); + itembutton->setCallback([this,c]() { + _buildForm(c); + setVisible(false); + dispose(); + }); + } +} + +ConfigWindow::~ConfigWindow() { + LOG(INFO) << "ConfigWindow::~ConfigWindow()"; +} + +void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &suri) { + using namespace nanogui; + + Configurable *configurable = ftl::config::find(suri); + ftl::config::json_t data; + if (configurable) { + configurable->refresh(); + data = configurable->getConfig(); + } + + for (auto i=data.begin(); i!=data.end(); ++i) { + if (i.key() == "$id") continue; + + if (i.key() == "$ref" && i.value().is_string()) { + const std::string suri = std::string(i.value().get<string>()); + _addElements(form, suri); + continue; + } + + if (i.value().is_boolean()) { + string key = i.key(); + form->addVariable<bool>(i.key(), [this,data,key,suri](const bool &b){ + ftl::config::update(suri+"/"+key, b); + }, [data,key]() -> bool { + return data[key].get<bool>(); + }); + } else if (i.value().is_number_integer()) { + string key = i.key(); + form->addVariable<int>(i.key(), [this,data,key,suri](const int &f){ + ftl::config::update(suri+"/"+key, f); + }, [data,key]() -> int { + return data[key].get<int>(); + }); + } else if (i.value().is_number_float()) { + string key = i.key(); + form->addVariable<float>(i.key(), [this,data,key,suri](const float &f){ + ftl::config::update(suri+"/"+key, f); + }, [data,key]() -> float { + return data[key].get<float>(); + }); + } else if (i.value().is_string()) { + string key = i.key(); + form->addVariable<string>(i.key(), [this,data,key,suri](const string &f){ + ftl::config::update(suri+"/"+key, f); + }, [data,key]() -> string { + return data[key].get<string>(); + }); + } else if (i.value().is_object()) { + string key = i.key(); + + // Checking the URI with exists() prevents unloaded local configurations from being shown. + if (suri.find('#') != string::npos && exists(suri+string("/")+key)) { + form->addButton(key, [this,suri,key]() { + _buildForm(suri+string("/")+key); + })->setIcon(ENTYPO_ICON_FOLDER); + } else if (exists(suri+string("#")+key)) { + form->addButton(key, [this,suri,key]() { + _buildForm(suri+string("#")+key); + })->setIcon(ENTYPO_ICON_FOLDER); + } + } + } +} + +void ConfigWindow::_buildForm(const std::string &suri) { + using namespace nanogui; + + ftl::URI uri(suri); + + FormHelper *form = new FormHelper(this->screen()); + form->addWindow(Vector2i(100,50), uri.getFragment()); + form->window()->setTheme(theme()); + + _addElements(form, suri); + + // prevent parent window from being destroyed too early + incRef(); + + auto close = new nanogui::Button( + form->window()->buttonPanel(), + "", + ENTYPO_ICON_CROSS); + + close->setCallback([this, form](){ + form->window()->dispose(); + decRef(); + }); + form->window()->screen()->performLayout(); +} + +bool ConfigWindow::exists(const std::string &uri) { + return ftl::config::find(uri) != nullptr; +} diff --git a/applications/gui2/src/modules/config/view.hpp b/applications/gui2/src/modules/config/view.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3396a6dd09917e74dcea7fd565ff77c97bae823b --- /dev/null +++ b/applications/gui2/src/modules/config/view.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include <nanogui/window.h> +#include <nanogui/formhelper.h> + +#include <ftl/master.hpp> +#include <ftl/uuid.hpp> +#include <ftl/net_configurable.hpp> + +namespace ftl { +namespace gui2 { + +/** + * Allow configurable editing. + */ +class ConfigWindow : public nanogui::Window { + public: + ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl); + ~ConfigWindow(); + + private: + ftl::ctrl::Master *ctrl_; + + void _buildForm(const std::string &uri); + void _addElements(nanogui::FormHelper *form, const std::string &suri); + bool exists(const std::string &uri); +}; + +} +} diff --git a/applications/gui2/src/modules/home.cpp b/applications/gui2/src/modules/home.cpp deleted file mode 100644 index 7c77a78bc5b43df67f81096c2d41b3ea5eda89e8..0000000000000000000000000000000000000000 --- a/applications/gui2/src/modules/home.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "home.hpp" - -using ftl::gui2::HomeView; - -namespace { - constexpr char const *const defaultImageViewVertexShader = - R"(#version 330 - uniform vec2 scaleFactor; - uniform vec2 position; - in vec2 vertex; - out vec2 uv; - void main() { - uv = vec2(vertex.x, vertex.y); - vec2 scaledVertex = (vertex * scaleFactor) + position; - gl_Position = vec4(2.0*scaledVertex.x - 1.0, - 2.0*scaledVertex.y - 1.0, - 0.0, 1.0); - })"; - - constexpr char const *const defaultImageViewFragmentShader = - R"(#version 330 - uniform sampler2D image1; - uniform sampler2D image2; - uniform sampler2D depthImage; - uniform float blendAmount; - out vec4 color; - in vec2 uv; - void main() { - color = blendAmount * texture(image1, uv) + (1.0 - blendAmount) * texture(image2, uv); - color.w = 1.0f; - gl_FragDepth = texture(depthImage, uv).r; - })"; -} - -HomeView::HomeView(ftl::Configurable *cfg, ftl::gui2::InputOutput *io) : - ftl::gui2::View(cfg, io), texture_(ftl::gui2::GLTexture::Type::RGBA) { - - io_->subscribe([this](ftl::rgbd::FrameSet &fs) { return process_FrameSet(fs); }); - - mShader.init("RGBDShader", defaultImageViewVertexShader, defaultImageViewFragmentShader); - nanogui::MatrixXu indices(3, 2); - indices.col(0) << 0, 1, 2; - indices.col(1) << 2, 3, 1; - - nanogui::MatrixXf vertices(2, 4); - vertices.col(0) << 0, 0; - vertices.col(1) << 1, 0; - vertices.col(2) << 0, 1; - vertices.col(3) << 1, 1; - - mShader.bind(); - mShader.uploadIndices(indices); - mShader.uploadAttrib("vertex", vertices); - -} - -HomeView::~HomeView() { - mShader.free(); -} - -bool HomeView::process_FrameSet(ftl::rgbd::FrameSet &fs) { - auto im = fs.frames[0].fastDownload(ftl::codecs::Channel::Left, cv::cuda::Stream::Null()); - texture_.make(im.cols, im.rows); - return true; -} diff --git a/applications/gui2/src/modules/home.hpp b/applications/gui2/src/modules/home.hpp deleted file mode 100644 index 7484ce2e9fc53142eb515b0ff2decfe453454ee7..0000000000000000000000000000000000000000 --- a/applications/gui2/src/modules/home.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include <nanogui/glutil.h> - -#include "../view.hpp" -#include "../gltexture.hpp" - -namespace ftl { -namespace gui2 { - -class HomeView : public View { -public: - HomeView(ftl::Configurable *config, InputOutput *io); - ~HomeView(); - -private: - bool process_FrameSet(ftl::rgbd::FrameSet&); - - nanogui::GLShader mShader; - GLTexture texture_; -}; - -} -} diff --git a/applications/gui2/src/modules/record/control.cpp b/applications/gui2/src/modules/record/control.cpp new file mode 100644 index 0000000000000000000000000000000000000000..928be5832c0eeb0877254103561e3e98eb85e605 --- /dev/null +++ b/applications/gui2/src/modules/record/control.cpp @@ -0,0 +1,21 @@ +#include "control.hpp" + +using ftl::gui2::RecordController; + +void RecordController::init() { + button_ = screen->addButton(); + LOG(INFO) << "RECORD INIT"; +} + +void RecordController::activate() { + button_->setPushed(false); + if (active) { + button_->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); + button_->setPushed(false); + } + else { + button_->setTextColor(nanogui::Color(1.0f,.1f,.1f,1.0f)); + button_->setPushed(false); + } + active = !active; +} diff --git a/applications/gui2/src/modules/record/control.hpp b/applications/gui2/src/modules/record/control.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1154621c330be09a11eb254d81ed15f666990c59 --- /dev/null +++ b/applications/gui2/src/modules/record/control.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../../module.hpp" +#include "../../screen.hpp" + +namespace ftl { +namespace gui2 { + +class RecordController : public Module { +public: + using Module::Module; + + virtual void init() override; + virtual void activate(); + +private: + nanogui::ToolButton* button_; + bool active = false; +}; + +} +} diff --git a/applications/gui2/src/modules/renderer/control.cpp b/applications/gui2/src/modules/renderer/control.cpp new file mode 100644 index 0000000000000000000000000000000000000000..597d1ba0b80095f3cc50df021ce0e28b001cc688 --- /dev/null +++ b/applications/gui2/src/modules/renderer/control.cpp @@ -0,0 +1,136 @@ +#include "control.hpp" + +using ftl::gui2::Renderer; + +using ftl::codecs::Channel; +using ftl::rgbd::FrameSet; +using ftl::rgbd::Frame; + +void Renderer::init() { + renderer_ = std::unique_ptr<ftl::render::CUDARender>(ftl::create<ftl::render::CUDARender>(this, std::string("vcam0"))); + fsmask_ = renderer_->value("fsmask", fsmask_); + renderer_->on("fsmask", [this](const ftl::config::Event &e) { + fsmask_ = renderer_->value("fsmask", fsmask_); + }); + + // Allow Pose origin to be changed + pose_source_ = renderer_->value("pose_source", pose_source_); + renderer_->on("pose_source", [this](const ftl::config::Event &e) { + pose_source_ = renderer_->value("pose_source", pose_source_); + }); + + intrinsics_ = ftl::create<ftl::Configurable>(renderer_.get(), "intrinsics"); + + state_.getLeft() = ftl::rgbd::Camera::from(intrinsics_); + state_.getRight() = state_.getLeft(); + + intrinsics_->on("width", [this](const ftl::config::Event &e) { + state_.getLeft() = ftl::rgbd::Camera::from(intrinsics_); + state_.getRight() = state_.getLeft(); + }); + + intrinsics_->on("focal", [this](const ftl::config::Event &e) { + state_.getLeft() = ftl::rgbd::Camera::from(intrinsics_); + state_.getRight() = state_.getLeft(); + }); + + /*{ + Eigen::Matrix4d pose; + pose.setIdentity(); + state_.setPose(pose); + + for (auto &t : transforms_) { + t.setIdentity(); + } + } + { + double camera_initial_x = intrinsics_->value("camera_x", 0.0); + double camera_initial_y = intrinsics_->value("camera_y", -1.75); + double camera_initial_z = intrinsics_->value("camera_z", 0.0); + + double lookat_initial_x = intrinsics_->value("lookat_x", 1.0); + double lookat_initial_y = intrinsics_->value("lookat_y", 0.0); + double lookat_initial_z = intrinsics_->value("lookat_z", 0.0); + + Eigen::Vector3f head(camera_initial_x, camera_initial_y, camera_initial_z); + Eigen::Vector3f lookat(lookat_initial_x, lookat_initial_y, lookat_initial_z); + // TODO up vector + Eigen::Matrix4f pose = nanogui::lookAt(head, head+lookat, Eigen::Vector3f(0.0f, 1.0f, 0.0f)); + + //eye_ = Eigen::Vector3d(camera_initial_x, camera_initial_y, camera_initial_z); + //neye_ = Eigen::Vector4d(eye_(0), eye_(1), eye_(2), 0.0); + //rotmat_ = pose.cast<double>(); + //rotmat_.block(0, 3, 3, 1).setZero(); + }*/ +} + +void Renderer::render(std::vector<ftl::rgbd::FrameSet*> &fss) { + frame_.reset(); + frame_.setOrigin(&state_); + frame_.create<cv::cuda::GpuMat>(Channel::Colour); + frame_.create<cv::cuda::GpuMat>(Channel::Depth); + + // TODO + Eigen::Matrix4d pose = Eigen::Matrix4d::Identity(); + + { + //FTL_Profile("Render",0.034); + renderer_->begin(frame_, Channel::Colour); + /*if (isStereo()) { + if (!renderer2_) { + renderer2_ = ftl::create<ftl::render::CUDARender>(screen_->root(), std::string("vcam")+std::to_string(vcamcount++)); + } + renderer2_->begin(frame_, Channel::Colour2); + }*/ + + try { + for (auto *fs : fss) { + if (!usesFrameset(fs->id)) continue; + + fs->mtx.lock(); + renderer_->submit(fs, ftl::codecs::Channels<0>(Channel::Colour), pose); + //if (isStereo()) renderer2_->submit(fs, ftl::codecs::Channels<0>(Channel::Colour), transforms_[fs->id]); + + //if (enable_overlay) { + // Generate and upload an overlay image. + // overlayer_->apply(*fs, overlay_, state_); + // frame_.upload(Channel::Overlay, renderer_->getCUDAStream()); + //} + } + + renderer_->render(); + //if (isStereo()) renderer2_->render(); + + //if (enable_overlay) { + // renderer_->blend(Channel::Overlay); + //} + + renderer_->end(); + //if (isStereo()) renderer2_->end(); + } catch(std::exception &e) { + LOG(ERROR) << "Exception in render: " << e.what(); + } + + for (auto *fs : fss) { + if (!usesFrameset(fs->id)) continue; + fs->mtx.unlock(); + } + } + /* + if (!post_pipe_) { + post_pipe_ = ftl::config::create<ftl::operators::Graph>(screen_->root(), "post_filters"); + post_pipe_->append<ftl::operators::FXAA>("fxaa"); + post_pipe_->append<ftl::operators::GTAnalysis>("gtanalyse"); + } + + post_pipe_->apply(frame_, frame_, 0); + + channels_ = frame_.getChannels();*/ + + + // Normalize depth map + frame_.get<cv::cuda::GpuMat>(Channel::Depth).convertTo(frame_.get<cv::cuda::GpuMat>(Channel::Depth), CV_32F, 1.0/8.0); +} + +Renderer::~Renderer() { +} diff --git a/applications/gui2/src/modules/renderer/control.hpp b/applications/gui2/src/modules/renderer/control.hpp new file mode 100644 index 0000000000000000000000000000000000000000..033f59b08aa1f4c5b216c5357842a5d515576e27 --- /dev/null +++ b/applications/gui2/src/modules/renderer/control.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "../../module.hpp" +#include "../../screen.hpp" + +#include <ftl/render/CUDARender.hpp> + +namespace ftl { +namespace gui2 { + +/** + * Controller for thumbnail view. + */ +class Renderer : public Module { +public: + using Module::Module; + virtual ~Renderer(); + + virtual void init() override; + virtual void render(std::vector<ftl::rgbd::FrameSet*> &fss); + bool usesFrameset(int id) const { return true; } + +private: + std::unique_ptr<ftl::render::CUDARender> renderer_; + unsigned int fsmask_ = 0; // Frameset Mask + std::string pose_source_; + ftl::Configurable *intrinsics_; + ftl::operators::Graph *post_pipe_; + ftl::rgbd::Frame frame_; + ftl::rgbd::FrameState state_; +}; + +} +} diff --git a/applications/gui2/src/modules/thumbnails/control.cpp b/applications/gui2/src/modules/thumbnails/control.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4fbc218ebe21aea54478dcce623f48a36d51f556 --- /dev/null +++ b/applications/gui2/src/modules/thumbnails/control.cpp @@ -0,0 +1,46 @@ +#include "control.hpp" +#include "view.hpp" + +#include "../camera/control.hpp" + +#include <nanogui/entypo.h> + +using ftl::gui2::ThumbnailsController; + +void ThumbnailsController::init() { + button = screen->addButton(); + button->setIcon(ENTYPO_ICON_HOME); + button->setTooltip("Home"); + button->setCallback([this](){ + button->setPushed(false); + activate(); + }); + button->setVisible(true); +} + +void ThumbnailsController::activate() { + show_thumbnails(); +} + +ThumbnailsController::~ThumbnailsController() { + +} + +void ThumbnailsController::show_thumbnails() { + auto thumb_view = new ftl::gui2::Thumbnails(screen, this); + + thumb_view->setHandle( + io->addCallback([this, thumb_view](ftl::rgbd::FrameSet& fs){ + thumb_view->update(fs); + screen->redraw(); + return true; + })); + + screen->setView(thumb_view); +} + +void ThumbnailsController::show_camera(int frame_idx) { + auto* camera = screen->getModule<ftl::gui2::Camera>(); + camera->setSource(frame_idx); + camera->activate(); +} diff --git a/applications/gui2/src/modules/thumbnails/control.hpp b/applications/gui2/src/modules/thumbnails/control.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f457d5dfb9e498559da4fbb11948d3c7583ad610 --- /dev/null +++ b/applications/gui2/src/modules/thumbnails/control.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../../module.hpp" +#include "../../screen.hpp" + +namespace ftl { +namespace gui2 { + +/** + * Controller for thumbnail view. + */ +class ThumbnailsController : public Module { +public: + using Module::Module; + virtual ~ThumbnailsController(); + + virtual void init() override; + virtual void activate(); + + void show_thumbnails(); + void show_camera(int frame_idx); + +private: + nanogui::ToolButton *button; +}; + +} +} diff --git a/applications/gui2/src/modules/thumbnails/view.cpp b/applications/gui2/src/modules/thumbnails/view.cpp new file mode 100644 index 0000000000000000000000000000000000000000..282593ed4afef72260778df4711453d43084653d --- /dev/null +++ b/applications/gui2/src/modules/thumbnails/view.cpp @@ -0,0 +1,105 @@ +#include "view.hpp" + +#include <opencv2/imgproc.hpp> +#include <opencv2/imgcodecs.hpp> +#include <opencv2/cudaarithm.hpp> + +#include <ftl/operators/antialiasing.hpp> +#include <ftl/cuda/normals.hpp> +#include <ftl/render/colouriser.hpp> +#include <ftl/cuda/transform.hpp> +#include <ftl/operators/gt_analysis.hpp> +#include <ftl/operators/poser.hpp> +#include <ftl/cuda/colour_cuda.hpp> +#include <ftl/streams/parsers.hpp> + +#include <nanogui/vscrollpanel.h> +#include <nanogui/layout.h> + +using ftl::gui2::Thumbnails; +using ftl::gui2::ThumbView; + +ThumbView::ThumbView(nanogui::Widget *parent, ThumbnailsController *control, int idx) : + ftl::gui2::FrameView(parent), control(control), idx(idx) { + setCursor(nanogui::Cursor::Hand); +} + +bool ThumbView::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) { + if (!down) { + // this widget (and view it belongs to) MUST NOT receive any events + // after control->show_camera() (automatic destruction with reference + // counting, use nanogui::ref<> for View to keep keep it alive + // [not tested]) + control->show_camera(idx); + } + return true; +} + +void ThumbView::draw(NVGcontext *ctx) { + ftl::gui2::FrameView::draw(ctx); + nvgScissor(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y()); + nvgFontSize(ctx, 14); + nvgFontFace(ctx, "sans-bold"); + nvgResetScissor(ctx); +} + +Thumbnails::Thumbnails(nanogui::Widget *parent, ftl::gui2::ThumbnailsController *control) : + View(parent), control(control) { + + panel = new nanogui::Widget(this); + panel->setLayout( + new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, + nanogui::Alignment::Middle, 0, 10)); + +} + +Thumbnails::~Thumbnails() { + +} + +void Thumbnails::update(ftl::rgbd::FrameSet &fs) { + // Just swap frameset: creating opengl buffers (FrameView) requires + // active context. Easiest way is to generate elements at draw() + + std::unique_lock<std::mutex> lk(mtx_, std::defer_lock); + if (lk.try_lock()) { + fs.swapTo(fs_); + } +} + +void Thumbnails::draw(NVGcontext *ctx) { + std::unique_lock<std::mutex> lk(mtx_); + const auto channel = ftl::codecs::Channel::Colour; + + bool update = false; + + for (unsigned int i = 0; i < fs_.frames.size(); i++) { + + if (thumbnails_.size() == i) { + thumbnails_.push_back(new ftl::gui2::ThumbView(panel, control, i)); + thumbnails_[i]->setFixedSize(thumbsize_); + } + + if (!fs_.hasFrame(i)) { + continue; + } + + auto &frame = fs_.frames[i]; + if (!frame.hasChannel(channel)) { + continue; + } + + thumbnails_[i]->set(frame, channel, 0, true); + update = true; + } + + if (update) { + performLayout(ctx); + // center + nanogui::Vector2i margin = (size() - panel->size())/2; + panel->setPosition(margin); + } + + View::draw(ctx); +} + diff --git a/applications/gui2/src/modules/thumbnails/view.hpp b/applications/gui2/src/modules/thumbnails/view.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0459bffad6434f068f7dc4ae5cddd8d3a756bfdc --- /dev/null +++ b/applications/gui2/src/modules/thumbnails/view.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "../../view.hpp" +#include "../../gltexture.hpp" +#include "../../frameview.hpp" + +#include "control.hpp" + +#include <nanogui/glcanvas.h> +#include <nanogui/glutil.h> +#include <nanogui/imageview.h> + +namespace ftl { +namespace gui2 { + +class ThumbView : public ftl::gui2::FrameView { +public: + ThumbView(nanogui::Widget *parent, ThumbnailsController *control, int idx); + ~ThumbView() {} + + virtual bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override; + virtual void draw(NVGcontext *ctx) override; + +private: + ThumbnailsController *control; + const int idx; +}; + +class Thumbnails : public View { +public: + Thumbnails(nanogui::Widget *parent, ThumbnailsController *controller); + virtual ~Thumbnails(); + + void update(ftl::rgbd::FrameSet &fs); + virtual void draw(NVGcontext *ctx) override; + + void setHandle(ftl::Handle&& handle) { handle_ = std::move(handle); } + +private: + ftl::gui2::ThumbnailsController *control; + nanogui::Widget* panel; + + std::vector<ThumbView*> thumbnails_; + + std::mutex mtx_; + ftl::rgbd::FrameSet fs_; + + nanogui::Vector2i thumbsize_ = nanogui::Vector2i(320,180); + ftl::Handle handle_; +}; + +} +} diff --git a/applications/gui2/src/screen.cpp b/applications/gui2/src/screen.cpp index 64be6251a79761e4a82689d49229ca4522b7bc30..57e05178f40017a981d0d63592c5617a4a51367e 100644 --- a/applications/gui2/src/screen.cpp +++ b/applications/gui2/src/screen.cpp @@ -12,7 +12,6 @@ #include "window.hpp" #include "screen.hpp" -#include "modules.hpp" #include <loguru.hpp> @@ -23,115 +22,132 @@ using Eigen::Vector2i; using ftl::gui2::Screen; +static const int toolbar_w = 50; +static const Vector2i wsize(1280+toolbar_w,720); + Screen::Screen() : nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence"), - mediatheme(nullptr), - toolbuttheme(nullptr), - windowtheme(nullptr), - toolbar(nullptr), - view(nullptr) { + mediatheme_(nullptr), + toolbuttheme_(nullptr), + windowtheme_(nullptr), + toolbar_(nullptr), + active_view_(nullptr) { using namespace nanogui; - setSize(Vector2i(1280,720)); + setSize(wsize); // themes - toolbuttheme = new Theme(*theme()); - toolbuttheme->mBorderDark = nanogui::Color(0,0); - toolbuttheme->mBorderLight = nanogui::Color(0,0); - toolbuttheme->mButtonGradientBotFocused = nanogui::Color(60,255); - toolbuttheme->mButtonGradientBotUnfocused = nanogui::Color(0,0); - toolbuttheme->mButtonGradientTopFocused = nanogui::Color(60,255); - toolbuttheme->mButtonGradientTopUnfocused = nanogui::Color(0,0); - toolbuttheme->mButtonGradientTopPushed = nanogui::Color(60,180); - toolbuttheme->mButtonGradientBotPushed = nanogui::Color(60,180); - toolbuttheme->mTextColor = nanogui::Color(0.9f,0.9f,0.9f,0.9f); - - mediatheme = new Theme(*theme()); - mediatheme->mIconScale = 1.2f; - mediatheme->mWindowDropShadowSize = 0; - mediatheme->mWindowFillFocused = nanogui::Color(45, 150); - mediatheme->mWindowFillUnfocused = nanogui::Color(45, 80); - mediatheme->mButtonGradientTopUnfocused = nanogui::Color(0,0); - mediatheme->mButtonGradientBotUnfocused = nanogui::Color(0,0); - mediatheme->mButtonGradientTopFocused = nanogui::Color(80,230); - mediatheme->mButtonGradientBotFocused = nanogui::Color(80,230); - mediatheme->mIconColor = nanogui::Color(255,255); - mediatheme->mTextColor = nanogui::Color(1.0f,1.0f,1.0f,1.0f); - mediatheme->mBorderDark = nanogui::Color(0,0); - mediatheme->mBorderMedium = nanogui::Color(0,0); - mediatheme->mBorderLight = nanogui::Color(0,0); - mediatheme->mDropShadow = nanogui::Color(0,0); - mediatheme->mButtonFontSize = 30; - mediatheme->mStandardFontSize = 20; - - windowtheme = new Theme(*theme()); - windowtheme->mWindowFillFocused = nanogui::Color(220, 200); - windowtheme->mWindowFillUnfocused = nanogui::Color(220, 200); - windowtheme->mWindowHeaderGradientBot = nanogui::Color(60,230); - windowtheme->mWindowHeaderGradientTop = nanogui::Color(60,230); - windowtheme->mTextColor = nanogui::Color(20,255); - windowtheme->mWindowCornerRadius = 0; - windowtheme->mButtonGradientBotFocused = nanogui::Color(210,255); - windowtheme->mButtonGradientBotUnfocused = nanogui::Color(190,255); - windowtheme->mButtonGradientTopFocused = nanogui::Color(230,255); - windowtheme->mButtonGradientTopUnfocused = nanogui::Color(230,255); - windowtheme->mButtonGradientTopPushed = nanogui::Color(170,255); - windowtheme->mButtonGradientBotPushed = nanogui::Color(210,255); - windowtheme->mBorderDark = nanogui::Color(150,255); - windowtheme->mBorderMedium = nanogui::Color(165,255); - windowtheme->mBorderLight = nanogui::Color(230,255); - windowtheme->mButtonFontSize = 16; - windowtheme->mTextColorShadow = nanogui::Color(0,0); - windowtheme->mWindowTitleUnfocused = windowtheme->mWindowTitleFocused; - windowtheme->mWindowTitleFocused = nanogui::Color(240,255); - windowtheme->mWindowDropShadowSize = 0; - windowtheme->mDropShadow = nanogui::Color(0,0); - windowtheme->mIconScale = 0.85f; - - toolbar = new StationaryWindow(this, ""); - toolbar->setPosition(Vector2i(0,0)); - toolbar->setFixedWidth(50); - toolbar->setFixedHeight(height()); + toolbuttheme_ = new Theme(*theme()); + toolbuttheme_->mBorderDark = nanogui::Color(0,0); + toolbuttheme_->mBorderLight = nanogui::Color(0,0); + toolbuttheme_->mButtonGradientBotFocused = nanogui::Color(60,255); + toolbuttheme_->mButtonGradientBotUnfocused = nanogui::Color(0,0); + toolbuttheme_->mButtonGradientTopFocused = nanogui::Color(60,255); + toolbuttheme_->mButtonGradientTopUnfocused = nanogui::Color(0,0); + toolbuttheme_->mButtonGradientTopPushed = nanogui::Color(60,180); + toolbuttheme_->mButtonGradientBotPushed = nanogui::Color(60,180); + toolbuttheme_->mTextColor = nanogui::Color(0.9f,0.9f,0.9f,0.9f); + toolbuttheme_->mWindowDropShadowSize = 0; + toolbuttheme_->mDropShadow = nanogui::Color(0,0); + + windowtheme_ = new Theme(*theme()); + windowtheme_->mWindowHeaderGradientBot = nanogui::Color(0,0); + windowtheme_->mWindowHeaderGradientTop = nanogui::Color(0,0); + windowtheme_->mTextColor = nanogui::Color(20,255); + windowtheme_->mWindowCornerRadius = 0; + windowtheme_->mBorderDark = nanogui::Color(0,0); + windowtheme_->mBorderMedium = nanogui::Color(0,0); + windowtheme_->mBorderLight = nanogui::Color(0,0); + windowtheme_->mWindowFillFocused = nanogui::Color(64, 0); + windowtheme_->mWindowFillUnfocused= nanogui::Color(64, 0); + windowtheme_->mWindowDropShadowSize = 0; + windowtheme_->mDropShadow = nanogui::Color(0, 0); + + toolbar_ = new FixedWindow(this); + toolbar_->setPosition(Vector2i(0,0)); + toolbar_->setFixedWidth(toolbar_w); + toolbar_->setFixedHeight(height()); setResizeCallback([this](Vector2i s) { - toolbar->setFixedSize({toolbar->width(), s[1]}); - toolbar->setPosition({0, 0}); - if (view) { - view->setFixedSize({max(s[0] - toolbar->width(), 0), s[1]}); - view->setPosition({toolbar->width(), 0}); + toolbar_->setFixedSize({toolbar_->width(), s[1]}); + toolbar_->setPosition({0, 0}); + if (active_view_) { + active_view_->setFixedSize({max(s[0] - toolbar_->width(), 0), s[1]}); + active_view_->setPosition({toolbar_->width(), 0}); } - this->performLayout(); + performLayout(); }); - auto tools = new Widget(toolbar); - tools->setLayout(new BoxLayout( Orientation::Vertical, + tools_ = new Widget(toolbar_); + tools_->setLayout(new BoxLayout( Orientation::Vertical, Alignment::Middle, 0, 10)); - tools->setPosition(Vector2i(5,10)); + tools_->setPosition(Vector2i(5,10)); - auto button = new ToolButton(tools, ENTYPO_ICON_HOME); - button->setIconExtraScale(1.5f); - button->setTheme(toolbuttheme); - button->setTooltip("Home"); - button->setFixedSize(Vector2i(40,40)); - button->setCallback([this]() { + setVisible(true); + performLayout(); +} - }); +Screen::~Screen() { + for (auto [name, ptr] : modules_) { + std::ignore = name; + delete ptr; + } +} - setVisible(true); +void Screen::redraw() { + // glfwPostEmptyEvent() is safe to call from any thread + // https://www.glfw.org/docs/3.3/intro_guide.html#thread_safety + glfwPostEmptyEvent(); +} + +void Screen::setView(ftl::gui2::View *view) { + + view->setPosition(Vector2i(toolbar_->width(), 0)); + view->setFixedSize(Vector2i(width() - toolbar_->width(), height())); + view->setTheme(windowtheme_); + view->setVisible(true); + + if (active_view_) { + active_view_->setVisible(false); + + // View requires same cleanup as Window (see screen.cpp) before removed. + if (std::find(mFocusPath.begin(), mFocusPath.end(), active_view_) != mFocusPath.end()) { + mFocusPath.clear(); + } + if (mDragWidget == active_view_) { + mDragWidget = nullptr; + } + + removeChild(active_view_); + } + + active_view_ = view; + LOG(INFO) << "number of children (Screen): "<< mChildren.size(); performLayout(); } -Screen::~Screen() {} +void Screen::render() { + if (active_view_) { + active_view_->render(); + } +} + +nanogui::ToolButton* Screen::addButton() { + auto button = new nanogui::ToolButton(tools_, ENTYPO_ICON_MENU, ""); + button->setIconExtraScale(1.5f); + button->setTheme(toolbuttheme_); + button->setFixedSize(Vector2i(40,40)); + return button; +} -void Screen::setView(ftl::gui2::View *window) { - if (view) { - view->setVisible(false); - this->removeChild(view); +ftl::gui2::Module* Screen::addModule_(const std::string &name, ftl::gui2::Module* ptr) { + ptr->init(); + if (modules_.find(name) != modules_.end()) { + LOG(WARNING) << "Module " << name << " already loaded. Removing old module"; + delete modules_[name]; } - view = window; - view->setTheme(windowtheme); - this->addChild(view); - view->setFixedSize(Vector2i(width() - toolbar->width(), height())); - view->setVisible(true); + + modules_[name] = ptr; + return ptr; } diff --git a/applications/gui2/src/screen.hpp b/applications/gui2/src/screen.hpp index 0ddef9ff07bb53606a39fdf69a52aa0ebf0dd5e2..83f09c2c89196ed71dad7303ddc18f771052e448 100644 --- a/applications/gui2/src/screen.hpp +++ b/applications/gui2/src/screen.hpp @@ -3,13 +3,14 @@ #include <nanogui/screen.h> #include <nanogui/glutil.h> -#include <memory> +#include <nanogui/toolbutton.h> -#ifdef HAVE_OPENVR -#include <openvr/openvr.h> -#endif +#include <map> +#include <memory> +#include <typeinfo> #include "view.hpp" +#include "module.hpp" namespace ftl { namespace gui2 { @@ -19,19 +20,99 @@ class Screen : public nanogui::Screen { explicit Screen(); ~Screen(); - void render(); - void setView(ftl::gui2::View *view); + void render(); // necessary? + /** Redraw the screen (triggers an empty event). Thread safe. */ + void redraw(); + + void activate(Module *ptr); + + /** set active view (existing object */ + void setView(ftl::gui2::View* view); + /** set active view (create new object)*/ + template<typename T, typename ... Args> + void setView(Args ... args); + + /** Add a module.*/ + template<typename T, typename ... Args> + T* addModule(const std::string &name, ftl::Configurable *config, Args ... args); + + /** Get a pointer to module. Module identified by name, exception thrown if not found */ + template<typename T> + T* getModule(const std::string &name); + + /** Get a pointer to module. Module indentified by dynamic type from template parameter. + * Throws an exception if not found. If more than one possible match (same module + * loaded multiple times), return value can be any. + */ + template<typename T> + T* getModule(); + + // prever above template (explicit who manages delete) + // template<typename T> + // T* addModule(T* ptr) { return addModule_(ptr); } + + // TODO removeModule() as well? + + /** add a button to toolbar */ + nanogui::ToolButton* addButton(); private: + Module* addModule_(const std::string &name, Module* ptr); + + //std::mutex mtx_; // not used: do not modify gui outside gui (main) thread + std::map<std::string, ftl::gui2::Module*> modules_; + nanogui::Theme* mediatheme_; + nanogui::Theme* toolbuttheme_; + nanogui::Theme* windowtheme_; - nanogui::Theme* mediatheme; - nanogui::Theme* toolbuttheme; - nanogui::Theme* windowtheme; + nanogui::Widget *toolbar_; + nanogui::Widget *tools_; - nanogui::Widget *toolbar; - ftl::gui2::View *view; + ftl::gui2::View *active_view_; }; +template<typename T, typename ... Args> +void Screen::setView(Args ... args) { + setView_(new T(this, args ...)); +} + +template<typename T, typename ... Args> +T* Screen::addModule(const std::string &name, ftl::Configurable *config, Args ... args) { + return dynamic_cast<T*>( + addModule_( + name, + ftl::config::create<T>(config, name, args ...) + ) + ); +} + +template<typename T> +T* Screen::getModule(const std::string &name) { + if (modules_.find(name) == modules_.end()) { + throw ftl::exception("module: " + name + " not found"); + } + + auto* ptr = dynamic_cast<T*>(modules_[name]); + + if (ptr == nullptr) { + throw ftl::exception("bad cast, module requested with wrong type"); + } + + return ptr; +} + +template<typename T> +T* Screen::getModule() { + for (auto& [name, ptr] : modules_) { + std::ignore = name; + if (typeid(*ptr) == typeid(T)) { + return dynamic_cast<T*>(ptr); + } + } + + throw ftl::exception("module not found"); +} + } } diff --git a/applications/gui2/src/toolbar.cpp b/applications/gui2/src/toolbar.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/applications/gui2/src/toolbar.hpp b/applications/gui2/src/toolbar.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3f59c932d39b02caf58f2abac65bdd9246f0a7da --- /dev/null +++ b/applications/gui2/src/toolbar.hpp @@ -0,0 +1,2 @@ +#pragma once + diff --git a/applications/gui2/src/view.cpp b/applications/gui2/src/view.cpp index a59ca1da9905dd3c04bd1bb3ad9d615baf85014b..9e65837bc5a143ed7106d365776d56dbb4fcae56 100644 --- a/applications/gui2/src/view.cpp +++ b/applications/gui2/src/view.cpp @@ -2,9 +2,3 @@ using ftl::gui2::View; -View::View(ftl::Configurable *config, ftl::gui2::InputOutput *io, - const std::string &title) : - StationaryWindow(nullptr, title), config_(config), io_(io) { - - -} diff --git a/applications/gui2/src/view.hpp b/applications/gui2/src/view.hpp index 0a31a0a3de3412f4abff6d5ed365347dc5f5c945..ef747dc1832a4859f5e20490562e12882286d601 100644 --- a/applications/gui2/src/view.hpp +++ b/applications/gui2/src/view.hpp @@ -3,20 +3,18 @@ #include "window.hpp" #include "inputoutput.hpp" +#include <nanogui/vscrollpanel.h> + namespace ftl { namespace gui2 { -class View : public nanogui::StationaryWindow { +class View : public nanogui::Widget { public: - View(ftl::Configurable *config, ftl::gui2::InputOutput *io, const std::string &title = ""); - virtual ~View() {} - - virtual void render() {} + using nanogui::Widget::Widget; + virtual ~View() {} - protected: - ftl::Configurable *config_; - ftl::gui2::InputOutput *io_; + virtual void render() {} // TODO remove if VR works? }; }; diff --git a/applications/gui2/src/window.hpp b/applications/gui2/src/window.hpp index 8dcb2339b842cdfa9f9b00d635c13bef591e1d7e..1e3f125de3c51e33914873b7246b511136b2ba3f 100644 --- a/applications/gui2/src/window.hpp +++ b/applications/gui2/src/window.hpp @@ -7,9 +7,13 @@ namespace nanogui { /** * Non-movable Window widget */ -class StationaryWindow : public nanogui::Window { - using Window::Window; +class FixedWindow : public nanogui::Window { +public: + FixedWindow(nanogui::Widget *parent, const char* name="") : + nanogui::Window(parent, name) {}; + virtual bool mouseDragEvent(const Vector2i&, const Vector2i &, int, int) override { return false; } + virtual ~FixedWindow() {} }; } diff --git a/components/common/cpp/include/ftl/handle.hpp b/components/common/cpp/include/ftl/handle.hpp index d7e2f8089f87e4664cafaf8ebc886539a491eb1a..ac634443e89673ea54f6ad0217a415289dd42a12 100644 --- a/components/common/cpp/include/ftl/handle.hpp +++ b/components/common/cpp/include/ftl/handle.hpp @@ -52,7 +52,11 @@ struct Handle { return *this; } - inline ~Handle() { if (handler_) handler_->remove(*this); } + inline ~Handle() { + if (handler_) { + handler_->remove(*this); + } + } private: BaseHandler *handler_; @@ -117,4 +121,4 @@ ftl::Handle ftl::BaseHandler::make_handle(BaseHandler *h, int id) { return ftl::Handle(h, id); } -#endif \ No newline at end of file +#endif