Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • nicolaspope/ftl
1 result
Select Git revision
Show changes
Showing
with 3399 additions and 45 deletions
#pragma once
#include <nanogui/widget.h>
#include <ftl/calibration/structures.hpp>
#include "../../modules/calibration/calibration.hpp"
namespace ftl {
namespace gui2 {
class OpenCVFlagWidget : public nanogui::Widget {
public:
OpenCVFlagWidget(nanogui::Widget* parent, OpenCVCalibrateFlags* flags, int defaultv=-1);
void reset();
void setDefaults(int v) { defaults_ = v; }
private:
OpenCVCalibrateFlags* flags_;
int defaults_;
};
class IntrinsicDetails : public nanogui::Widget {
public:
IntrinsicDetails(nanogui::Widget* parent);
void update(const ftl::calibration::CalibrationData::Intrinsic &values);
private:
nanogui::Widget* params_;
nanogui::Widget* dist_;
int padding_;
};
}
}
#include <nanogui/screen.h>
#include <nanogui/layout.h>
#include <nanogui/button.h>
#include <nanogui/vscrollpanel.h>
#include <ftl/utility/string.hpp>
#include <ftl/codecs/touch.hpp>
#include "camera.hpp"
#include "../modules/camera.hpp"
#include "../modules/config.hpp"
#include "../modules/statistics.hpp"
#include "../widgets/popupbutton.hpp"
#include <loguru.hpp>
using ftl::gui2::Camera;
using ftl::gui2::FixedWindow;
using ftl::gui2::MediaPanel;
using ftl::gui2::ToolPanel;
using ftl::gui2::CameraView;
using ftl::gui2::PopupButton;
using ftl::gui2::VolumeButton;
using ftl::gui2::Tools;
using ftl::gui2::ToolGroup;
using ftl::codecs::Channel;
// ==== Record Options =========================================================
class RecordOptions : public nanogui::Window {
public:
RecordOptions(nanogui::Widget *parent, Camera* ctrl);
virtual ~RecordOptions();
void show(const std::function<void(bool)> &cb);
private:
Camera* ctrl_;
std::list<std::tuple<nanogui::CheckBox*,ftl::codecs::Channel>> channels_;
std::function<void(bool)> callback_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
RecordOptions::RecordOptions(nanogui::Widget *parent, Camera* ctrl)
: nanogui::Window(parent, "Recording"), ctrl_(ctrl) {
using namespace nanogui;
//setFixedWidth(300);
setLayout(new GroupLayout(15, 6, 14, 10));
setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
setVisible(false);
auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS);
close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
close->setBackgroundColor(theme()->mWindowHeaderGradientBot);
close->setCallback([this](){
setVisible(false);
if (callback_) callback_(false);
});
auto filename_box = new TextBox(this, "test.ftl");
filename_box->setEditable(true);
VScrollPanel *vscroll = new VScrollPanel(this);
vscroll->setFixedHeight(150);
Widget *scroll = new Widget(vscroll);
scroll->setLayout(new GridLayout(Orientation::Horizontal, 3));
//auto *label = new Label(vscroll, "Select Channels:", "sans-bold");
// Add all available channels as checkboxes
// TODO: Refresh this list on show
auto channels = ctrl_->allAvailableChannels();
for (auto c : channels) {
// Skip channels that can't be encoded
if (int(c) < 32) {
switch (c) {
case Channel::Colour :
case Channel::Colour2 :
case Channel::Depth :
case Channel::Depth2 : break;
default: continue;
}
}
auto check = new CheckBox(scroll, ftl::codecs::name(c));
switch (c) {
case Channel::Colour :
case Channel::Pose :
case Channel::Capabilities :
case Channel::Calibration :
case Channel::MetaData : check->setChecked(true); break;
default: break;
}
if (c == Channel::Calibration) {
check->setEnabled(false);
}
channels_.emplace_back(check, c);
}
auto *button_panel = new Widget(this);
button_panel->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 6));
auto start = new Button(button_panel, "Record");
start->setCallback([this, filename_box]() {
std::unordered_set<ftl::codecs::Channel> selection;
for (auto &s : channels_) {
if (std::get<0>(s)->checked()) {
selection.emplace(std::get<1>(s));
}
}
if (selection.size() > 0) {
ctrl_->startRecording(filename_box->value(), selection);
setVisible(false);
}
if (callback_) callback_(true);
});
auto stream = new Button(button_panel, "Stream");
stream->setCallback([this]() {
std::unordered_set<ftl::codecs::Channel> selection;
for (auto &s : channels_) {
if (std::get<0>(s)->checked()) {
selection.emplace(std::get<1>(s));
}
}
if (selection.size() > 0) {
ctrl_->startStreaming(selection);
setVisible(false);
}
if (callback_) callback_(true);
});
auto closebut = new Button(button_panel, "Cancel");
closebut->setCallback([this]() {
setVisible(false);
if (callback_) callback_(false);
});
auto advanced = new Button(button_panel, "Advanced");
advanced->setEnabled(false);
screen()->performLayout();
}
RecordOptions::~RecordOptions() {
}
void RecordOptions::show(const std::function<void(bool)> &cb) {
setVisible(true);
callback_ = cb;
}
// === MediaPanel ==============================================================
class MediaPanel : public FixedWindow {
public:
MediaPanel(nanogui::Widget *parent, Camera* ctrl, CameraView* view);
virtual ~MediaPanel();
void setAvailableChannels(const std::unordered_set<ftl::codecs::Channel> &channels);
void setActiveChannel(ftl::codecs::Channel c);
void draw(NVGcontext *ctx) override;
/** add button to position. */
nanogui::Button* addButton(int position = -1);
PopupButton* button_channels;
VolumeButton* button_volume;
private:
std::vector<nanogui::Widget*> buttons(); // channel buttons
Camera* ctrl_;
CameraView* view_;
RecordOptions *record_opts_=nullptr;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
MediaPanel::MediaPanel(nanogui::Widget *parent, ftl::gui2::Camera* ctrl, CameraView* view) :
ftl::gui2::FixedWindow(parent, ""), ctrl_(ctrl), view_(view) {
LOG(INFO) << __func__ << " (" << this << ")";
using namespace nanogui;
record_opts_ = new RecordOptions(screen(), ctrl);
setLayout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 5, 10));
auto theme = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("media");
this->setTheme(theme);
// Volume control
button_volume = new ftl::gui2::VolumeButton(this, ctrl_->mixer());
button_volume->setValue(ctrl_->volume());
button_volume->setCallback([ctrl = ctrl_](float v){ ctrl->setVolume(v); });
// Pause/Unpause
auto button_pause = new Button(this, "", ENTYPO_ICON_CONTROLLER_PAUS);
if (ctrl->isPaused()) {
button_pause->setIcon(ENTYPO_ICON_CONTROLLER_PLAY);
}
button_pause->setCallback([ctrl = ctrl_ ,button_pause]() {
ctrl->setPaused(!ctrl->isPaused());
if (ctrl->isPaused()) {
button_pause->setIcon(ENTYPO_ICON_CONTROLLER_PLAY);
} else {
button_pause->setIcon(ENTYPO_ICON_CONTROLLER_PAUS);
}
});
// Record
/*auto button_record = new ftl::gui2::PopupButton(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
button_record->setSide(Popup::Side::Right);
button_record->setChevronIcon(0);
auto rec_popup = button_record->popup();
rec_popup->setLayout(new GroupLayout());
{
auto button = new Button(rec_popup, "Record to File");
//button->setFlags(Button::RadioButton);
//button->setVisible(true);
button->setCallback([this, button_record]() {
if (!ctrl_->isRecording()) {
button_record->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
button_record->setPushed(false);
ctrl_->startRecording("test.ftl");
}
});
}
button_record->setCallback([this, button_record]() {
if (ctrl_->isRecording()) {
button_record->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
button_record->setPushed(false);
ctrl_->stopRecording();
}
});*/
// Record
auto button_record = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
button_record->setCallback([this, button_record]() {
if (record_opts_->visible()) return;
if (ctrl_->isRecording()) {
ctrl_->stopRecording();
button_record->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
} else {
record_opts_->show([button_record](bool rec) {
if (rec) button_record->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
});
}
});
auto button_stereo = new nanogui::Button(this, "", ENTYPO_ICON_GRID);
button_stereo->setFlags(nanogui::Button::Flags::ToggleButton);
button_stereo->setChangeCallback([view = view_](bool v){
view->setStereo(v);
});
// Channel select. Creates buttons for 32 channels and sets available ones
// visible (a bit of a hack, only used here and setAvailableChannels())
button_channels = new ftl::gui2::PopupButton(this, "", ENTYPO_ICON_LAYERS);
button_channels->setSide(Popup::Side::Right);
button_channels->setChevronIcon(0);
auto popup = button_channels->popup();
popup->setLayout(new GroupLayout());
for (int i=0; i < 32; ++i) {
ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i);
auto button = new Button(popup, ftl::codecs::name(c));
button->setFlags(Button::RadioButton);
button->setVisible(false);
button->setCallback([this,c]() {
ctrl_->setChannel(c);
setActiveChannel(c);
});
}
setAvailableChannels(ctrl_->availableChannels());
// Settings
auto button_config = new Button(this, "", ENTYPO_ICON_COG);
button_config->setCallback([ctrl = ctrl_]() {
auto uri = ctrl->getActiveSourceURI();
if (uri.size() > 0) ctrl->screen->getModule<ftl::gui2::ConfigCtrl>()->show(uri);
else ctrl->screen->showError("Error", "This source does not have any settings");
});
}
MediaPanel::~MediaPanel() {
if (parent()->getRefCount() > 0) record_opts_->dispose();
}
void MediaPanel::draw(NVGcontext *ctx) {
auto size = this->size();
setPosition(
nanogui::Vector2i( screen()->width() / 2 - size[0]/2,
screen()->height() - 30 - size[1]));
FixedWindow::draw(ctx);
}
std::vector<nanogui::Widget*> MediaPanel::buttons() {
auto popup = button_channels->popup();
if (popup->childCount() != 32) {
LOG(ERROR) << "Wrong number of buttons!";
}
return popup->children();
}
void MediaPanel::setAvailableChannels(const std::unordered_set<Channel> &channels) {
const auto &button = buttons();
bool update = false;
for (int i = 0; i < 32; ++i) {
ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i);
bool visible = channels.count(c) > 0;
update |= (visible != button[i]->visible());
button[i]->setVisible(visible);
}
if (update) {
auto popup = button_channels->popup();
screen()->performLayout();
popup->setAnchorHeight(popup->height() - 20);
}
}
void MediaPanel::setActiveChannel(Channel c) {
auto button = dynamic_cast<nanogui::Button*>
(buttons()[static_cast<size_t>(c)]);
button->setVisible(true);
button->setPushed(true);
}
nanogui::Button* MediaPanel::addButton(int pos) {
auto* button = new nanogui::Button(this, "", 0);
if (pos >= 0) {
mChildren.pop_back();
mChildren.insert(mChildren.begin() + pos, button);
}
performLayout(screen()->nvgContext());
return button;
}
// === ToolPanel ===============================================================
ToolPanel::ToolPanel(nanogui::Widget *parent, ftl::gui2::Camera* ctrl, CameraView* view) :
ftl::gui2::FixedWindow(parent, ""), ctrl_(ctrl), view_(view) {
LOG(INFO) << __func__ << " (" << this << ")";
using namespace nanogui;
setLayout(new BoxLayout(Orientation::Vertical,
Alignment::Middle, 5, 10));
container_ = new Widget(this);
container_->setLayout(new BoxLayout(Orientation::Vertical,
Alignment::Middle, 0, 10));
auto theme = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("media_small");
this->setTheme(theme);
auto *mouse_group = _addGroup(ToolGroup::MOUSE_MOTION, Button::Flags::RadioButton, {
Tools::SELECT_POINT,
Tools::MOVEMENT,
Tools::MOVE_CURSOR,
Tools::ROTATE_CURSOR,
Tools::PAN,
Tools::INSPECT_POINT,
Tools::ZOOM_IN,
Tools::ZOOM_OUT,
Tools::ROTATE_X,
Tools::ROTATE_Y,
Tools::ROTATE_Z,
Tools::TRANSLATE_X,
Tools::TRANSLATE_Y,
Tools::TRANSLATE_Z
});
_addButton(mouse_group, Tools::SELECT_POINT, ENTYPO_ICON_MOUSE_POINTER, "Select Point");
_addButton(mouse_group, Tools::MOVEMENT, ENTYPO_ICON_MAN, "First Person Camera");
_addButton(mouse_group, Tools::MOVE_CURSOR, ENTYPO_ICON_DIRECTION, "Move 3D Cursor");
_addButton(mouse_group, Tools::PAN, ENTYPO_ICON_MOUSE, "Pan Image");
_addButton(mouse_group, Tools::INSPECT_POINT, ENTYPO_ICON_MAGNIFYING_GLASS, "Inspect Point");
_addButton(mouse_group, Tools::ZOOM_IN, ENTYPO_ICON_CIRCLE_WITH_PLUS, "Zoom In (+)");
_addButton(mouse_group, Tools::ZOOM_OUT, ENTYPO_ICON_CIRCLE_WITH_MINUS, "Zoom Out (-)");
auto *trans_but = _addButton(mouse_group, {
Tools::ROTATE_X,
Tools::ROTATE_Y,
Tools::ROTATE_Z
}, ENTYPO_ICON_CYCLE, "Transform Pose");
_addButton(trans_but, Tools::ROTATE_X, "Rotate X");
_addButton(trans_but, Tools::ROTATE_Y, "Rotate Y");
_addButton(trans_but, Tools::ROTATE_Z, "Rotate Z");
_addButton(trans_but, Tools::TRANSLATE_X, "Translate X");
_addButton(trans_but, Tools::TRANSLATE_Y, "Translate Y");
_addButton(trans_but, Tools::TRANSLATE_Z, "Translate Z");
auto *view2d_group = _addGroup(ToolGroup::VIEW_2D_ACTIONS, Button::Flags::NormalButton, {
Tools::CENTRE_VIEW,
Tools::ZOOM_FIT
});
_addButton(view2d_group, Tools::CENTRE_VIEW, ENTYPO_ICON_ALIGN_HORIZONTAL_MIDDLE, "Centre the View (c)");
_addButton(view2d_group, Tools::ZOOM_FIT, ENTYPO_ICON_RESIZE_FULL_SCREEN, "Zoom to Fit (f)");
//_addButton(CameraTools::ORIGIN_TO_CURSOR, ENTYPO_ICON_LOCATION, "Origin to 3D Cursor");
auto *action3d_group = _addGroup(ToolGroup::VIEW_3D_ACTIONS, Button::Flags::NormalButton, {
Tools::ORIGIN_TO_CURSOR,
Tools::RESET_ORIGIN,
Tools::SAVE_CURSOR
});
auto *cur_but = _addButton(action3d_group, {
Tools::ORIGIN_TO_CURSOR,
Tools::RESET_ORIGIN,
Tools::SAVE_CURSOR
}, ENTYPO_ICON_LOCATION, "Use Cursor");
_addButton(cur_but, Tools::ORIGIN_TO_CURSOR, "Origin to Cursor");
_addButton(cur_but, Tools::RESET_ORIGIN, "Reset Origin");
_addButton(cur_but, Tools::SAVE_CURSOR, "Save Cursor as Pose");
auto *view3d_group = _addGroup(ToolGroup::VIEW_3D_LAYERS, Button::Flags::ToggleButton, {
Tools::OVERLAY,
Tools::CLIPPING
});
_addButton(view3d_group, Tools::OVERLAY, ENTYPO_ICON_LINE_GRAPH, "Show/Hide Overlay");
_addButton(view3d_group, Tools::CLIPPING, ENTYPO_ICON_SCISSORS, "Enable/Disable Clipping");
auto *b = new Button(this, "", ENTYPO_ICON_CHEVRON_THIN_UP);
b->setTooltip("Show/Hide Tools");
b->setCallback([this, b]() {
if (container_->visible()) {
container_->setVisible(false);
b->setIcon(ENTYPO_ICON_CHEVRON_THIN_UP);
screen()->performLayout();
} else {
container_->setVisible(true);
b->setIcon(ENTYPO_ICON_CHEVRON_THIN_DOWN);
screen()->performLayout();
}
});
container_->setVisible(false);
}
ToolPanel::~ToolPanel() {
}
bool ToolPanel::isActive(ftl::gui2::Tools tool) {
if (group_map_.count(tool)) {
auto &grp = group_data_[group_map_[tool]];
return grp.active.count(tool) > 0;
}
return false;
}
void ToolPanel::setTool(ftl::gui2::Tools tool) {
if (group_map_.count(tool)) {
auto &grp = group_data_[group_map_[tool]];
if (grp.type == nanogui::Button::Flags::RadioButton) {
for (auto t : grp.active) {
if (t != tool) {
if (buttons_.count(t)) {
auto *b = buttons_[t];
b->setTextColor(nanogui::Color(255,255,255,255));
b->setPushed(false);
}
}
}
grp.active.clear();
grp.active.insert(tool);
if (buttons_.count(tool)) {
auto *b = buttons_[tool];
b->setTextColor(dynamic_cast<Screen*>(screen())->getColor("highlight1"));
b->setPushed(true);
}
} else if (grp.type == nanogui::Button::Flags::ToggleButton) {
grp.active.insert(tool);
if (buttons_.count(tool)) {
auto *b = buttons_[tool];
b->setTextColor(dynamic_cast<Screen*>(screen())->getColor("highlight1"));
b->setPushed(true);
}
} else {
}
for (auto &f : callbacks_) {
if (f(tool)) break;
}
}
}
nanogui::Widget *ToolPanel::_addGroup(ftl::gui2::ToolGroup group, nanogui::Button::Flags type, const std::unordered_set<ftl::gui2::Tools> &tools) {
auto &grp = group_data_[group];
grp.tools = tools;
grp.type = type;
for (auto t : tools) group_map_[t] = group;
auto *w = new nanogui::Widget(container_);
w->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Middle, 0, 10));
return w;
}
void ToolPanel::_addButton(nanogui::Widget *g, ftl::gui2::Tools tool, int icon, const std::string &tooltip) {
auto *b = new nanogui::Button(g, "", icon);
b->setTooltip(tooltip);
b->setCallback([this, tool]() {
setTool(tool);
});
buttons_[tool] = b;
}
void ToolPanel::_addButton(ftl::gui2::PopupButton *parent, ftl::gui2::Tools tool, const std::string &label) {
auto *b = new nanogui::Button(parent->popup(), label);
b->setCallback([this, parent, tool]() {
parent->setPushed(false);
setTool(tool);
});
//buttons_[tool] = b;
}
ftl::gui2::PopupButton *ToolPanel::_addButton(nanogui::Widget *g, std::unordered_set<ftl::gui2::Tools> tools, int icon, const std::string &tooltip) {
auto *b = new ftl::gui2::PopupButton(g, "", icon);
b->setTooltip(tooltip);
b->setSide(nanogui::Popup::Side::Left);
b->setChevronIcon(0);
for (auto t : tools) {
buttons_[t] = b;
}
auto *popup = b->popup();
popup->setLayout(new nanogui::BoxLayout
(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 10, 6));
auto theme = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("media_small");
popup->setTheme(theme);
return b;
}
void ToolPanel::setAvailable(const std::unordered_set<ftl::gui2::Tools> &s) {
for (auto &b : buttons_) {
if (s.count(b.first)) {
b.second->setVisible(true);
} else {
b.second->setVisible(false);
}
}
}
void ToolPanel::setEnabled(const std::unordered_set<ftl::gui2::Tools> &s) {
for (auto &b : buttons_) {
if (s.count(b.first)) {
b.second->setVisible(true);
b.second->setEnabled(true);
} else {
b.second->setEnabled(false);
}
}
}
void ToolPanel::enable(const std::unordered_set<ftl::gui2::Tools> &s) {
for (auto &b : buttons_) {
if (s.count(b.first)) {
b.second->setVisible(true);
b.second->setEnabled(true);
}
}
}
void ToolPanel::disable(const std::unordered_set<ftl::gui2::Tools> &s) {
for (auto &b : buttons_) {
if (s.count(b.first)) {
b.second->setEnabled(false);
}
}
}
void ToolPanel::draw(NVGcontext *ctx) {
auto size = this->size();
setPosition(
nanogui::Vector2i( screen()->width() - 30 - size[0],
screen()->height() - 30 - size[1]));
FixedWindow::draw(ctx);
}
// ==== CameraView =============================================================
CameraView::CameraView(ftl::gui2::Screen* parent, ftl::gui2::Camera* ctrl) :
View(parent), enable_zoom_(false), enable_pan_(false), ctrl_(ctrl),
stereoim_(nullptr) {
imview_ = new ftl::gui2::FTLImageView(this);
panel_ = new ftl::gui2::MediaPanel(screen(), ctrl, this);
tools_ = new ftl::gui2::ToolPanel(screen(), ctrl, this);
imview_->setFlipped(ctrl->isVR());
auto *mod = ctrl_->screen->getModule<ftl::gui2::Statistics>();
if (ctrl_->isMovable()) {
imview_->setCursor(nanogui::Cursor::Hand);
mod->setCursor(nanogui::Cursor::Hand);
} else {
imview_->setCursor(nanogui::Cursor::Crosshair);
mod->setCursor(nanogui::Cursor::Crosshair);
}
auto theme = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("toolbutton");
//this->setTheme(theme);
context_menu_ = new nanogui::Window(parent, "");
context_menu_->setVisible(false);
context_menu_->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical));
context_menu_->setTheme(theme);
auto *button = new nanogui::Button(context_menu_, "Capture Image");
button->setCallback([this]() {
char timestamp[18];
std::time_t t=std::time(NULL);
std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
context_menu_->setVisible(false);
ctrl_->snapshot(std::string(timestamp)+std::string(".png"));
});
button = new nanogui::Button(context_menu_, "Settings");
button->setCallback([this, button]() {
context_menu_->setVisible(false);
ctrl_->screen->getModule<ftl::gui2::ConfigCtrl>()->show(ctrl_->getID());
});
tools_->setAvailable({
Tools::SELECT_POINT,
Tools::OVERLAY,
Tools::PAN,
Tools::ZOOM_FIT,
Tools::ZOOM_IN,
Tools::ZOOM_OUT,
Tools::CENTRE_VIEW,
Tools::INSPECT_POINT
});
tools_->addCallback([this](ftl::gui2::Tools tool) {
switch (tool) {
case Tools::OVERLAY : ctrl_->toggleOverlay(); return true;
case Tools::ZOOM_FIT : imview_->fit(); return true;
case Tools::CENTRE_VIEW : imview_->center(); return true;
//case CameraTools::ZOOM_OUT : imview_->zoom(-1, imview_->sizeF() / 2); return true;
//case CameraTools::ZOOM_IN : imview_->zoom(1, imview_->sizeF() / 2); return true;
default: return false;
}
});
}
CameraView::~CameraView() {
if (parent()->getRefCount() > 0) {
// segfault without this check; nanogui already deleted windows?
// should be fixed in nanogui
panel_->dispose();
tools_->dispose();
}
if (context_menu_->parent()->getRefCount() > 0) {
context_menu_->setVisible(false);
context_menu_->dispose();
}
}
void CameraView::setStereo(bool v) {
if (v) {
if (!stereoim_) {
removeChild(imview_);
stereoim_ = new StereoImageView(this);
imview_ = stereoim_->right();
performLayout(screen()->nvgContext());
}
}
else {
if (stereoim_) {
removeChild(stereoim_);
imview_ = new FTLImageView(this);
stereoim_ = nullptr;
performLayout(screen()->nvgContext());
}
}
}
void CameraView::refresh() {
bool was_valid = imview_->texture().isValid();
if (ctrl_->hasFrame()) {
imview_->copyFrom(ctrl_->getFrame());
}
if (!was_valid && imview_->texture().isValid()) {
screen()->performLayout();
}
}
void CameraView::setZoom(bool v) {
enable_zoom_ = v;
imview_->setFixedScale(!v);
if (!v) {
imview_->setScale(1.0f);
}
}
void CameraView::setPan(bool v) {
enable_pan_ = v;
imview_->setFixedOffset(!v);
if (!v) {
imview_->fit();
}
}
bool CameraView::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) {
//if (button == 1) {
if (tools_->isActive(Tools::SELECT_POINT)) {
auto pos = imview_->imageCoordinateAt((p - mPos + rel).cast<float>());
if (pos.x() >= 0.0f && pos.y() >= 0.0f) {
ctrl_->touch(0, ftl::codecs::TouchType::MOUSE_LEFT, pos.x(), pos.y(), 0.0f, (button > 0) ? 255 : 0);
//LOG(INFO) << "Depth at " << pos.x() << "," << pos.y() << " = " << ctrl_->depthAt(pos.x(), pos.y());
}
}
return true;
//}
return false;
}
bool CameraView::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) {
//LOG(INFO) << "mouseButtonEvent: " << p << " - " << button;
if (button == 0) {
if (tools_->isActive(Tools::SELECT_POINT)) {
auto pos = imview_->imageCoordinateAt((p - mPos).cast<float>());
if (pos.x() >= 0.0f && pos.y() >= 0.0f) {
ctrl_->touch(0, ftl::codecs::TouchType::MOUSE_LEFT, pos.x(), pos.y(), 0.0f, (down) ? 255 : 0);
}
} else if (tools_->isActive(Tools::ZOOM_IN)) {
imview_->zoom(1, p.cast<float>());
} else if (tools_->isActive(Tools::ZOOM_OUT)) {
imview_->zoom(-1, p.cast<float>());
}
context_menu_->setVisible(false);
return true;
} else if (button == 1) {
if (!down) {
context_menu_->setPosition(p - mPos);
context_menu_->setVisible(true);
return true;
}
} else {
context_menu_->setVisible(false);
}
return false;
}
void CameraView::draw(NVGcontext*ctx) {
using namespace nanogui;
if (ctrl_->hasFrame()) {
try {
// TODO: Select shader to flip if VR capability found...
imview_->copyFrom(ctrl_->getFrame());
if (stereoim_) {
stereoim_->left()->copyFrom(ctrl_->getFrame(Channel::Left));
}
}
catch (std::exception& e) {
gui()->showError("Exception", e.what());
}
}
View::draw(ctx);
auto osize = imview_->scaledImageSizeF();
ctrl_->drawOverlay(ctx, screen()->size().cast<float>(), osize, imview_->offset());
if (tools_->isActive(Tools::INSPECT_POINT)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
float d = ctrl_->depthAt(pos.x(), pos.y());
if (d > 0.0f) {
nvgText(ctx, mouse.x()+25.0f, mouse.y()+20.0f, (to_string_with_precision(d,2) + std::string("m")).c_str(), nullptr);
}
}
}
void CameraView::performLayout(NVGcontext* ctx) {
if (stereoim_) {
stereoim_->setFixedSize(size());
if (!(enable_zoom_ && enable_pan_)) {
stereoim_->fit();
}
}
else {
imview_->setSize(size());
if (!(enable_zoom_ && enable_pan_)) {
imview_->fit();
}
}
View::performLayout(ctx);
}
#pragma once
#include "../view.hpp"
#include <ftl/utility/gltexture.hpp>
#include "../widgets/window.hpp"
#include "../widgets/soundctrl.hpp"
#include "../widgets/imageview.hpp"
#include "../widgets/popupbutton.hpp"
#include "../modules/camera_tools.hpp"
namespace ftl {
namespace gui2 {
class Camera;
class MediaPanel;
class CameraView;
struct ToolGroupData {
nanogui::Button::Flags type;
std::unordered_set<ftl::gui2::Tools> active;
std::unordered_set<ftl::gui2::Tools> tools;
};
class ToolPanel : public FixedWindow {
public:
ToolPanel(nanogui::Widget *parent, Camera* ctrl, CameraView* view);
virtual ~ToolPanel();
void setAvailable(const std::unordered_set<ftl::gui2::Tools> &);
void setEnabled(const std::unordered_set<ftl::gui2::Tools> &);
void enable(const std::unordered_set<ftl::gui2::Tools> &);
void disable(const std::unordered_set<ftl::gui2::Tools> &);
void draw(NVGcontext *ctx) override;
//inline ftl::gui2::Tools activeTool() const { return active_; }
bool isActive(ftl::gui2::Tools);
void setTool(ftl::gui2::Tools tool);
inline void addCallback(const std::function<bool(ftl::gui2::Tools)> &cb) { callbacks_.push_back(cb); }
private:
Camera* ctrl_;
CameraView* view_;
nanogui::Widget *container_;
std::unordered_map<ftl::gui2::Tools, nanogui::Button*> buttons_;
std::unordered_map<ftl::gui2::ToolGroup, ftl::gui2::ToolGroupData> group_data_;
std::unordered_map<ftl::gui2::Tools, ftl::gui2::ToolGroup> group_map_;
std::list<std::function<bool(ftl::gui2::Tools)>> callbacks_;
nanogui::Widget *_addGroup(ftl::gui2::ToolGroup group, nanogui::Button::Flags type, const std::unordered_set<ftl::gui2::Tools> &tools);
void _addButton(nanogui::Widget *, ftl::gui2::Tools, int icon, const std::string &tooltip);
void _addButton(ftl::gui2::PopupButton *parent, ftl::gui2::Tools, const std::string &label);
ftl::gui2::PopupButton *_addButton(nanogui::Widget *, std::unordered_set<ftl::gui2::Tools> tools, int icon, const std::string &tooltip);
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
class CameraView : public View {
public:
CameraView(Screen* parent, Camera* ctrl);
virtual ~CameraView();
virtual void draw(NVGcontext* ctx) override;
virtual void performLayout(NVGcontext* ctx) override;
virtual bool mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) override;
virtual bool mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) override;
void refresh();
void setZoom(bool enable);
void setPan(bool enable);
void setStereo(bool v);
protected:
bool enable_zoom_;
bool enable_pan_;
Camera* ctrl_;
MediaPanel* panel_;
ToolPanel* tools_;
FTLImageView* imview_;
nanogui::Window *context_menu_;
private:
StereoImageView* stereoim_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#include "camera3d.hpp"
#include "../modules/camera.hpp"
#include <loguru.hpp>
using ftl::gui2::CameraView3D;
// =============================================================================
static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
Eigen::Affine3d rx =
Eigen::Affine3d(Eigen::AngleAxisd(ax, Eigen::Vector3d(1, 0, 0)));
Eigen::Affine3d ry =
Eigen::Affine3d(Eigen::AngleAxisd(ay, Eigen::Vector3d(0, 1, 0)));
Eigen::Affine3d rz =
Eigen::Affine3d(Eigen::AngleAxisd(az, Eigen::Vector3d(0, 0, 1)));
return rz * rx * ry;
}
// ==== CameraView3D ===========================================================
CameraView3D::CameraView3D(ftl::gui2::Screen *parent, ftl::gui2::Camera *ctrl) :
CameraView(parent, ctrl) {
eye_ = Eigen::Vector3d::Zero();
neye_ = Eigen::Vector4d::Zero();
rotmat_.setIdentity();
rx_ = 0.0;
ry_ = 0.0;
ftime_ = 0.0;
delta_ = 0.0;
lerp_speed_ = 0.999f;
pose_up_to_date_.test_and_set();
tools_->setAvailable({
Tools::SELECT_POINT,
Tools::MOVEMENT,
Tools::OVERLAY,
Tools::INSPECT_POINT,
Tools::CLIPPING,
Tools::MOVE_CURSOR,
Tools::ORIGIN_TO_CURSOR,
Tools::RESET_ORIGIN,
Tools::SAVE_CURSOR,
Tools::ROTATE_X,
Tools::ROTATE_Y,
Tools::ROTATE_Z,
Tools::TRANSLATE_X,
Tools::TRANSLATE_Y,
Tools::TRANSLATE_Z
});
setZoom(false);
setPan(false);
tools_->setTool(Tools::MOVEMENT);
tools_->addCallback([this](ftl::gui2::Tools tool) {
if (tool == Tools::ORIGIN_TO_CURSOR) {
ctrl_->setOriginToCursor();
tools_->setTool(Tools::MOVEMENT);
return true;
} else if (tool == Tools::RESET_ORIGIN) {
ctrl_->resetOrigin();
tools_->setTool(Tools::MOVEMENT);
return true;
} else if (tool == Tools::SAVE_CURSOR) {
ctrl_->saveCursorToPoser();
tools_->setTool(Tools::MOVEMENT);
return true;
} else if (tool == Tools::ROTATE_X || tool == Tools::ROTATE_Y || tool == Tools::ROTATE_Z ||
tool == Tools::TRANSLATE_X || tool == Tools::TRANSLATE_Y || tool == Tools::TRANSLATE_Z) {
LOG(INFO) << "Loading cache pose";
cache_pose_ = ctrl_->getActivePose();
cache_screen_ = ctrl_->getActivePoseScreenCoord();
}
return false;
});
}
bool CameraView3D::keyboardEvent(int key, int scancode, int action, int modifiers) {
if (key == 263 || key == 262) {
float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
float scalar = (key == 263) ? -mag : mag;
neye_ += rotmat_*Eigen::Vector4d(scalar, 0.0, 0.0, 1.0);
pose_up_to_date_.clear();
}
else if (key == 264 || key == 265) {
float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
float scalar = (key == 264) ? -mag : mag;
neye_ += rotmat_*Eigen::Vector4d(0.0, 0.0, scalar, 1.0);
pose_up_to_date_.clear();
}
else if (key == 266 || key == 267) {
float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
float scalar = (key == 266) ? -mag : mag;
neye_ += rotmat_*Eigen::Vector4d(0.0, scalar, 0.0, 1.0);
pose_up_to_date_.clear();
}
else if (key >= '0' && key <= '5' && modifiers == 2) { // Ctrl+NUMBER
}
return true;
}
bool CameraView3D::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) {
if (button == 0 && !down) {
if (tools_->isActive(Tools::MOVE_CURSOR)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
//Eigen::Vector3f world = ctrl_->worldAt(pos.x(), pos.y());
ctrl_->setCursor(pos.x(), pos.y());
tools_->setTool(Tools::ROTATE_CURSOR);
return true;
} else if (tools_->isActive(Tools::ROTATE_CURSOR)) {
tools_->setTool(Tools::MOVEMENT);
} else if (tools_->isActive(Tools::ROTATE_X) || tools_->isActive(Tools::ROTATE_Y) || tools_->isActive(Tools::ROTATE_Z) ||
tools_->isActive(Tools::TRANSLATE_X) || tools_->isActive(Tools::TRANSLATE_Y) || tools_->isActive(Tools::TRANSLATE_Z)) {
tools_->setTool(Tools::MOVEMENT);
}
}
return CameraView::mouseButtonEvent(p, button, down, modifiers);
}
bool CameraView3D::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) {
//if (button != 1) {
// return true;
//}
if (button == 1 && tools_->isActive(Tools::MOVEMENT)) {
rx_ += rel[0];
ry_ += rel[1];
pose_up_to_date_.clear();
return true;
} else if (tools_->isActive(Tools::ROTATE_CURSOR)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
Eigen::Vector3f world = ctrl_->worldAt(pos.x(), pos.y());
ctrl_->setCursorTarget(world);
return true;
} else if (tools_->isActive(Tools::ROTATE_X)) {
auto screen_origin = ctrl_->getActivePoseScreenCoord();
double angle = atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
Eigen::Affine3d rx = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(1, 0, 0)));
ctrl_->setActivePose(rx.matrix() * cache_pose_);
} else if (tools_->isActive(Tools::ROTATE_Y)) {
auto screen_origin = ctrl_->getActivePoseScreenCoord();
double angle = -atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
Eigen::Affine3d ry = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(0, 1, 0)));
ctrl_->setActivePose(ry.matrix() * cache_pose_);
} else if (tools_->isActive(Tools::ROTATE_Z)) {
auto screen_origin = ctrl_->getActivePoseScreenCoord();
double angle = atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
Eigen::Affine3d rz = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(0, 0, 1)));
ctrl_->setActivePose(rz.matrix() * cache_pose_);
} else if (tools_->isActive(Tools::TRANSLATE_X)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
double dx = pos[0] - double(cache_screen_[0]);
//double dy = pos[1] - double(cache_screen_[1]);
double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(dist*0.001, 0.0, 0.0));
ctrl_->setActivePose(rx.matrix() * cache_pose_);
} else if (tools_->isActive(Tools::TRANSLATE_Y)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
double dx = pos[0] - double(cache_screen_[0]);
//double dy = pos[1] - double(cache_screen_[1]);
double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(0.0, dist*0.001, 0.0));
ctrl_->setActivePose(rx.matrix() * cache_pose_);
} else if (tools_->isActive(Tools::TRANSLATE_Z)) {
auto mouse = screen()->mousePos();
auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
double dx = pos[0] - double(cache_screen_[0]);
//double dy = pos[1] - double(cache_screen_[1]);
double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(0.0, 0.0, dist*0.001));
ctrl_->setActivePose(rx.matrix() * cache_pose_);
}
//LOG(INFO) << "New pose: \n" << getUpdatedPose();
//ctrl_->sendPose(getUpdatedPose());
return false;
}
bool CameraView3D::scrollEvent(const Eigen::Vector2i &p, const Eigen::Vector2f &rel) {
return true;
}
bool CameraView3D::keyboardCharacterEvent(unsigned int codepoint) {
LOG(INFO) << "keyboardCharacterEvent: " << codepoint;
return false;
}
Eigen::Matrix4d CameraView3D::getUpdatedPose() {
float rrx = ((float)ry_ * 0.2f * delta_);
float rry = (float)rx_ * 0.2f * delta_;
float rrz = 0.0;
Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
rotmat_ = rotmat_ * r.matrix();
rx_ = 0;
ry_ = 0;
eye_[0] += (neye_[0] - eye_[0]) * lerp_speed_ * delta_;
eye_[1] += (neye_[1] - eye_[1]) * lerp_speed_ * delta_;
eye_[2] += (neye_[2] - eye_[2]) * lerp_speed_ * delta_;
Eigen::Translation3d trans(eye_);
Eigen::Affine3d t(trans);
return t.matrix() * rotmat_;
}
void CameraView3D::processAnimation() {
Eigen::Vector3d diff;
diff[0] = neye_[0] - eye_[0];
diff[1] = neye_[1] - eye_[1];
diff[2] = neye_[2] - eye_[2];
// Only update pose if there is enough motion
if (diff.norm() > 0.01) {
pose_up_to_date_.clear();
}
}
void CameraView3D::draw(NVGcontext* ctx) {
double now = glfwGetTime();
delta_ = now - ftime_;
ftime_ = now;
processAnimation();
// poll from ctrl_ or send on event instead?
if (!pose_up_to_date_.test_and_set()) {
ctrl_->sendPose(getUpdatedPose());
}
CameraView::draw(ctx);
}
#pragma once
#include "../widgets/window.hpp"
#include "../view.hpp"
#include "camera.hpp"
namespace ftl {
namespace gui2 {
class CameraView3D : public CameraView {
public:
CameraView3D(Screen *parent, Camera* ctrl);
virtual bool keyboardEvent(int key, int scancode, int action, int modifiers) override;
virtual bool keyboardCharacterEvent(unsigned int codepoint) override;
virtual bool mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) override;
virtual bool mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) override;
virtual bool scrollEvent(const Eigen::Vector2i &p, const Eigen::Vector2f &rel) override;
virtual void draw(NVGcontext* ctx) override;
Eigen::Matrix4d getUpdatedPose();
protected:
// updates from keyboard
Eigen::Vector4d neye_;
// current
Eigen::Vector3d eye_;
Eigen::Matrix4d rotmat_;
Eigen::Matrix4d cache_pose_;
Eigen::Vector2i cache_screen_;
// updates from mouse
double rx_;
double ry_;
// times for pose update
double ftime_;
double delta_;
double lerp_speed_;
std::atomic_flag pose_up_to_date_;
void processAnimation();
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#include "config_window.hpp"
#include <loguru.hpp>
#include <nanogui/layout.h>
#include <nanogui/label.h>
......@@ -13,7 +14,11 @@
#include <vector>
#include <string>
using ftl::gui::ConfigWindow;
#include "config.hpp"
#include "../screen.hpp"
#include "../widgets/leftbutton.hpp"
using ftl::gui2::ConfigWindow;
using std::string;
using std::vector;
using ftl::config::json_t;
......@@ -36,6 +41,7 @@ private:
}
}
previous = str;
screen()->performLayout();
}
}
......@@ -46,7 +52,7 @@ public:
setPlaceholder("Search");
}
~SearchBox() {
virtual ~SearchBox() {
}
bool keyboardEvent(int key, int scancode, int action, int modifier) {
......@@ -58,6 +64,9 @@ public:
void setButtons(Widget *buttons) {
buttons_ = buttons;
}
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
static std::string titleForURI(const ftl::URI &uri) {
......@@ -73,11 +82,21 @@ static std::string titleForURI(const ftl::URI &uri) {
ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
: nanogui::Window(parent, "Settings"), ctrl_(ctrl) {
LOG(INFO) << __func__ << " (" << this << ")";
using namespace nanogui;
setLayout(new GroupLayout());
setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS);
close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
close->setBackgroundColor(theme()->mWindowHeaderGradientBot);
close->setCallback([this](){ dispose();});
setLayout(new GroupLayout(15, 6, 14, 10));
setFixedWidth(400);
setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
//setModal(true);
auto configurables = ftl::config::list();
const auto size = configurables.size();
......@@ -94,7 +113,37 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
searchBox->setButtons(buttons);
std::vector<std::string> configurable_titles(size);
for (size_t i = 0; i < size; ++i) {
std::set<std::string> sorted_cfgs;
sorted_cfgs.insert(configurables.begin(), configurables.end());
for (auto &c : sorted_cfgs) {
ftl::URI uri(c);
std::string spacing = "";
for (size_t i=0; i<uri.getPathLength(); ++i) {
spacing += " ";
}
//if (uri.getFragment().size() == 0) {
std::string title = spacing + titleForURI(uri);
//configurable_titles[i] = title;
auto itembutton = new ftl::gui2::LeftButton(buttons, title);
/*if (_isEmpty(c)) {
itembutton->setEnabled(false);
}*/
itembutton->setTooltip(c);
//itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
itembutton->setCallback([this,c]() {
_buildForm(c);
setVisible(false);
dispose();
});
//}
}
/*for (size_t i = 0; i < size; ++i) {
ftl::URI uri(configurables[i]);
std::string label = uri.getFragment();
......@@ -123,24 +172,29 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
auto itembutton = new nanogui::Button(buttons, configurable_titles[i]);
std::string c = configurables[i];
if (_isEmpty(c)) {
itembutton->setEnabled(false);
}
itembutton->setTooltip(c);
itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
itembutton->setCallback([this,c]() {
//LOG(INFO) << "Change configurable: " << c;
_buildForm(c);
setVisible(false);
//this->parent()->removeChild(this);
//delete this;
//screen()->removeChild(this);
dispose();
});
}
}*/
}
ConfigWindow::~ConfigWindow() {
LOG(INFO) << __func__ << " (" << this << ")";
}
bool ConfigWindow::_isEmpty(const std::string &uri) {
// $id, $ref and tags always present
return ftl::config::find(uri)->getConfig().size() <= 3;
}
void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &suri) {
void ConfigWindow::__addElements(nanogui::FormHelper *form, const std::string &suri) {
using namespace nanogui;
Configurable *configurable = ftl::config::find(suri);
......@@ -154,52 +208,62 @@ void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &su
if (i.key() == "$id") continue;
if (i.key() == "$ref" && i.value().is_string()) {
//LOG(INFO) << "Follow $ref: " << i.value();
const std::string suri = std::string(i.value().get<string>());
_addElements(form, suri);
__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){
form->addVariable<bool>(i.key(), [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){
form->addVariable<int>(i.key(), [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){
form->addVariable<float>(i.key(), [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){
form->addVariable<string>(i.key(), [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();
string nuri;
// 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);
//if (suri.find('#') != string::npos && exists(suri+string("/")+key)) {
// nuri = suri+string("/")+key;
//} else
if (exists(suri+string("/")+key)) {
nuri = suri+string("/")+key;
}
if (!nuri.empty()) {
nanogui::Window *window = form->window();
auto button = form->addButton(key, [window, nuri]() {
buildForm(window->screen(), nuri);
});
button->setIcon(ENTYPO_ICON_FOLDER);
button->setIconPosition(nanogui::Button::IconPosition::Left);
if (_isEmpty(nuri)) {
button->setEnabled(false);
}
}
}
}
......@@ -208,23 +272,85 @@ void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &su
void ConfigWindow::_buildForm(const std::string &suri) {
using namespace nanogui;
ftl::URI uri(suri);
/*ftl::URI uri(suri);
FormHelper *form = new FormHelper(this->screen());
//form->setWindow(this);
form->addWindow(Vector2i(100,50), uri.getFragment());
form->window()->setTheme(theme());
_addElements(form, suri);
__addElements(form, suri);
auto closebutton = form->addButton("Close", [this,form]() {
form->window()->setVisible(false);
delete form;
// prevent parent window from being destroyed too early
incRef(); // TODO: Is this needed? It isn't a parent window?
auto close = new nanogui::Button(form->window()->buttonPanel(), "", ENTYPO_ICON_CROSS);
close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
close->setBackgroundColor(theme()->mWindowHeaderGradientBot);
auto *window = form->window();
close->setCallback([this, window](){
window->dispose();
decRef();
});
closebutton->setIcon(ENTYPO_ICON_CROSS);
close->setBackgroundColor({80, 255});
form->window()->screen()->performLayout();
delete form;*/
buildForm(screen(), suri);
}
static MUTEX config_mtx;
static std::unordered_map<std::string, nanogui::Window*> existing_configs;
// Static version
void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) {
using namespace nanogui;
{
UNIQUE_LOCK(config_mtx, lk);
auto i = existing_configs.find(suri);
if (i != existing_configs.end()) {
screen->moveWindowToFront(i->second);
return;
}
}
ftl::URI uri(suri);
FormHelper *form = new FormHelper(screen);
form->addWindow(Vector2i(100,50), titleForURI(uri));
//form->window()->setTheme(theme());
{
UNIQUE_LOCK(config_mtx, lk);
existing_configs[suri] = form->window();
}
auto *window = form->window();
window->setTheme(dynamic_cast<ftl::gui2::Screen*>(window->screen())->getTheme("window_dark"));
window->setWidth(200);
__addElements(form, suri);
// prevent parent window from being destroyed too early
//incRef(); // TODO: Is this needed? It isn't a parent window?
auto close = new nanogui::Button(form->window()->buttonPanel(), "", ENTYPO_ICON_CROSS);
close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen)->getTheme("window_dark"));
//close->setBackgroundColor(theme()->mWindowHeaderGradientBot);
close->setCallback([window, suri](){
window->dispose();
//decRef();
UNIQUE_LOCK(config_mtx, lk);
existing_configs.erase(suri);
});
close->setBackgroundColor({80, 255});
form->window()->screen()->performLayout();
delete form;
}
bool ConfigWindow::exists(const std::string &uri) {
return ftl::config::find(uri) != nullptr;
}
#ifndef _FTL_GUI_CFGWINDOW_HPP_
#define _FTL_GUI_CFGWINDOW_HPP_
#pragma once
#include <nanogui/window.h>
#include <nanogui/formhelper.h>
......@@ -9,7 +8,7 @@
#include <ftl/net_configurable.hpp>
namespace ftl {
namespace gui {
namespace gui2 {
/**
* Allow configurable editing.
......@@ -17,17 +16,21 @@ namespace gui {
class ConfigWindow : public nanogui::Window {
public:
ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl);
~ConfigWindow();
virtual ~ConfigWindow();
static void buildForm(nanogui::Screen *screen, const std::string &uri);
private:
ftl::ctrl::Master *ctrl_;
static bool _isEmpty(const std::string &uri);
void _buildForm(const std::string &uri);
void _addElements(nanogui::FormHelper *form, const std::string &suri);
bool exists(const std::string &uri);
static void __addElements(nanogui::FormHelper *form, const std::string &suri);
static bool exists(const std::string &uri);
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#endif // _FTL_GUI_CFGWINDOW_HPP_
#include "statistics.hpp"
#include "../modules/statistics.hpp"
#include <ftl/streams/builder.hpp>
#include <ftl/streams/netstream.hpp>
#include <ftl/render/colouriser.hpp>
#include <ftl/utility/string.hpp>
#include <nanogui/screen.h>
#include <nanogui/opengl.h>
#include <loguru.hpp>
using ftl::gui2::StatisticsWidget;
using std::string;
StatisticsWidget::StatisticsWidget(nanogui::Widget* parent, ftl::gui2::Statistics* ctrl) :
nanogui::Window(parent,""), ctrl_(ctrl), last_stats_count_(0) {
setWidth(parent->width()/2);
}
void StatisticsWidget::draw(NVGcontext *ctx) {
int margin = 20;
const auto &screenSize = screen()->size();
float rowh = 10.0;
int count = 0;
setPosition({screenSize[0] - width() - margin, 0});
setHeight(screenSize[1]);
const auto pos = absolutePosition();
auto panels = ctrl_->get();
for (unsigned int i = 0; i < panels.size(); i++) {
if (panels[i].second.is_structured()) {
for (auto j : panels[i].second.items()) {
std::string msg = j.key();
auto colour = nanogui::Color(244, 244, 244, 255);
int fsize = 15;
int entypo = 0;
if (j.value().is_object()) {
const auto &val = j.value()["value"];
if (j.value().contains("nokey")) {
msg = "";
}
if (j.value().contains("colour")) {
uchar4 cucol = ftl::render::parseCUDAColour(j.value()["colour"].get<std::string>());
colour = nanogui::Color(cucol.x, cucol.y, cucol.z, 255);
}
if (j.value().contains("size")) {
fsize = j.value()["size"].get<int>();
}
if (j.value().contains("icon")) {
entypo = j.value()["icon"].get<int>();
}
if (val.is_string()) {
if (msg.size() > 0) msg += std::string(": ");
msg += val.get<std::string>();
} else if (val.is_number()) {
if (msg.size() > 0) msg += std::string(": ");
msg += std::string(": ") + to_string_with_precision(val.get<float>(),2);
}
} else if (j.value().is_string()) {
msg += std::string(": ") + j.value().get<std::string>();
} else if (j.value().is_number()) {
msg += std::string(": ") + to_string_with_precision(j.value().get<float>(),2);
} else if (j.value().is_boolean()) {
}
rowh += float(fsize)+5.0f;
nvgFontSize(ctx, fsize);
nvgTextAlign(ctx, NVG_ALIGN_RIGHT);
float tw = 0.0f;
if (msg.size() > 0) {
if (panels[i].first == ftl::gui2::StatisticsPanel::LOGGING) nvgFontFace(ctx, "sans");
else nvgFontFace(ctx, "sans-bold");
nvgFillColor(ctx, nanogui::Color(8, 8, 8, 255)); // shadow
tw = nvgTextBounds(ctx, pos[0] + width(), rowh, msg.c_str(), nullptr, nullptr);
nvgText(ctx, pos[0] + width(), rowh, msg.c_str(), nullptr);
nvgFillColor(ctx, colour);
nvgText(ctx, pos[0] + width() - 1, rowh - 1, msg.c_str(), nullptr);
tw += 10;
}
if (entypo > 0) {
auto icon = nanogui::utf8(entypo);
nvgFontFace(ctx, "icons");
nvgFontSize(ctx, float(fsize)*0.8f);
nvgFillColor(ctx, nanogui::Color(8, 8, 8, 255)); // shadow
nvgText(ctx, pos[0] + width() - tw, rowh, icon.data(), nullptr);
nvgFillColor(ctx, colour);
nvgText(ctx, pos[0] + width() - 1 - tw, rowh - 1, icon.data(), nullptr);
}
++count;
}
}
}
}
#pragma once
#include <nanogui/widget.h>
#include <nanogui/window.h>
namespace ftl
{
namespace gui2
{
class Statistics;
class StatisticsWidget : public nanogui::Window {
public:
StatisticsWidget(nanogui::Widget *parent, Statistics* ctrl);
virtual void draw(NVGcontext *ctx);
bool mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) override { return false; }
private:
Statistics* ctrl_;
int last_stats_count_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#include "thumbnails.hpp"
#include "../modules/thumbnails.hpp"
#include <ftl/utility/gltexture.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 <ftl/rgbd/frame.hpp>
#include <nanogui/label.h>
#include <nanogui/tabwidget.h>
#include <nanogui/vscrollpanel.h>
#include <nanogui/layout.h>
#include <nanogui/popup.h>
#include <loguru.hpp>
using ftl::gui2::ThumbView;
using ftl::gui2::Thumbnails;
using ftl::utility::GLTexture;
using ftl::gui2::ThumbnailsController;
using ftl::codecs::Channel;
using ftl::data::FrameID;
class ThumbView : public ftl::gui2::ImageView {
public:
ThumbView(nanogui::Widget *parent, ThumbnailsController *control, ftl::data::FrameID id, const std::string &name);
virtual ~ThumbView() {}
virtual bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override;
virtual void draw(NVGcontext *ctx) override;
void setName(const std::string &str) { name_ = str; }
void update(ftl::rgbd::Frame& frame, Channel c);
private:
ThumbnailsController *ctrl_;
GLTexture texture_;
const ftl::data::FrameID id_;
std::string name_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
ThumbView::ThumbView(nanogui::Widget *parent, ThumbnailsController *control, ftl::data::FrameID id, const std::string &name) :
ftl::gui2::ImageView(parent), ctrl_(control), id_(id), name_(name) {
setCursor(nanogui::Cursor::Hand);
setFixedOffset(true);
setFixedScale(true);
}
bool ThumbView::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
if (button == 0) {
if (!down) {
ctrl_->show_camera(id_);
}
}
return true;
}
void ThumbView::update(ftl::rgbd::Frame &frame, Channel c) {
if (!frame.hasChannel(c)) {
return;
}
const auto &vf = frame.get<ftl::rgbd::VideoFrame>(c);
if (vf.isGPU()) {
texture_.copyFrom(vf.getGPU());
} else {
texture_.copyFrom(vf.getCPU());
}
if (texture_.isValid()) {
bindImage(texture_.texture());
}
}
void ThumbView::draw(NVGcontext *ctx) {
fit();
// Image
ftl::gui2::ImageView::draw(ctx);
// Label
nvgScissor(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y());
nvgFontSize(ctx, 14);
nvgFontFace(ctx, "sans-bold");
nvgTextAlign(ctx, NVG_ALIGN_CENTER);
nvgText(ctx, mPos.x() + mSize.x()/2.0f, mPos.y()+mSize.y() - 18, name_.c_str(), NULL);
nvgResetScissor(ctx);
}
////////////////////////////////////////////////////////////////////////////////
Thumbnails::Thumbnails(ftl::gui2::Screen *parent, ftl::gui2::ThumbnailsController *control) :
View(parent), ctrl_(control), tabwidget_(nullptr) {
tabwidget_ = new nanogui::TabWidget(this);
tabwidget_->setFixedSize(size());
context_menu_ = new nanogui::Window(parent, "");
context_menu_->setVisible(false);
context_menu_->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical));
auto *button = new nanogui::Button(context_menu_, "Remove");
button->setCallback([this]() {
int ix = tabwidget_->activeTab();
LOG(INFO) << "REMOVE FSID " << ix;
tabwidget_->removeTab(ix);
thumbnails_.erase(ix);
context_menu_->setVisible(false);
ctrl_->removeFrameset(ix);
//screen()->performLayout();
});
}
Thumbnails::~Thumbnails() {
if (context_menu_->parent()->getRefCount() > 0) {
context_menu_->setVisible(false);
context_menu_->dispose();
}
}
bool Thumbnails::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
bool r = View::mouseButtonEvent(p, button, down, modifiers);
if (!r) {
if (button == 1) {
if (!down) {
context_menu_->setPosition(p - mPos);
context_menu_->setVisible(true);
return true;
}
} else {
context_menu_->setVisible(false);
}
}
return true;
}
void Thumbnails::updateThumbnails() {
const Channel channel = Channel::Colour;
bool perform_layout = false;
auto framesets = ctrl_->getFrameSets();
for (auto& fs : framesets) {
unsigned int fsid = fs->frameset();
// create new tab if necessary
if (thumbnails_.count(fsid) == 0) {
if (fs->frames.size() == 0) {
// setting layout to widget without any children will crash
// nanogui, skip
continue;
}
auto* tab = tabwidget_->createTab(fs->name());
tab->setLayout(new nanogui::BoxLayout
(nanogui::Orientation::Vertical, nanogui::Alignment::Middle, 40));
auto* panel = new nanogui::Widget(tab);
panel->setLayout(
new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3,
nanogui::Alignment::Middle, 0, 10));
thumbnails_[fsid] = {0, panel, {}};
perform_layout = true;
}
auto& thumbs = thumbnails_[fsid];
while (thumbs.thumbnails.size() < fs->frames.size()) {
int source = thumbs.thumbnails.size();
auto &frame = fs->frames[source];
perform_layout = true;
std::string name = frame.name();
auto* thumbnail = new ThumbView(thumbs.panel, ctrl_, FrameID(fsid, source), name);
thumbnail->setFixedSize(thumbsize_);
thumbs.thumbnails.push_back(thumbnail);
}
if (fs->timestamp() > thumbs.timestamp) {
for(size_t i = 0; i < fs->frames.size(); i++) {
thumbs.thumbnails[i]->update((*fs)[i].cast<ftl::rgbd::Frame>(), channel);
}
thumbs.timestamp = fs->timestamp();
}
}
if (perform_layout) {
screen()->performLayout();
}
}
void Thumbnails::draw(NVGcontext *ctx) {
tabwidget_->setFixedSize(size());
updateThumbnails();
View::draw(ctx);
}
#pragma once
#include "../view.hpp"
#include "../widgets/imageview.hpp"
#include <nanogui/glcanvas.h>
#include <nanogui/glutil.h>
#include <nanogui/imageview.h>
namespace ftl {
namespace gui2 {
class ThumbnailsController;
class ThumbView;
class Thumbnails : public View {
public:
Thumbnails(Screen *parent, ThumbnailsController *controller);
virtual ~Thumbnails();
virtual void draw(NVGcontext *ctx) override;
bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override;
private:
void updateThumbnails();
void addTab(unsigned int fsid);
struct FSThumbnails {
int64_t timestamp;
nanogui::Widget* panel;
std::vector<ThumbView*> thumbnails;
};
std::mutex mtx_;
ftl::gui2::ThumbnailsController *ctrl_;
nanogui::TabWidget* tabwidget_;
std::map<unsigned int, FSThumbnails> thumbnails_;
nanogui::Vector2i thumbsize_ = nanogui::Vector2i(320,180);
nanogui::Window *context_menu_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
/*
src/combobox.cpp -- simple combo box widget based on a popup button
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
#include "combobox.hpp"
#include <nanogui/layout.h>
#include <nanogui/serializer/core.h>
#include <cassert>
using nanogui::Vector2i;
using nanogui::Vector2f;
using nanogui::GroupLayout;
using nanogui::Serializer;
using ftl::gui2::ComboBox;
using ftl::gui2::PopupButton;
ComboBox::ComboBox(Widget *parent) : PopupButton(parent), mSelectedIndex(0) {
}
ComboBox::ComboBox(Widget *parent, const std::vector<std::string> &items)
: PopupButton(parent), mSelectedIndex(0) {
setItems(items);
}
ComboBox::ComboBox(Widget *parent, const std::vector<std::string> &items, const std::vector<std::string> &itemsShort)
: PopupButton(parent), mSelectedIndex(0) {
setItems(items, itemsShort);
}
void ComboBox::setSelectedIndex(int idx) {
if (mItemsShort.empty())
return;
const std::vector<Widget *> &children = popup()->children();
((Button *) children[mSelectedIndex])->setPushed(false);
((Button *) children[idx])->setPushed(true);
mSelectedIndex = idx;
setCaption(mItemsShort[idx]);
}
void ComboBox::setItems(const std::vector<std::string> &items, const std::vector<std::string> &itemsShort) {
assert(items.size() == itemsShort.size());
mItems = items;
mItemsShort = itemsShort;
if (mSelectedIndex < 0 || mSelectedIndex >= (int) items.size())
mSelectedIndex = 0;
while (mPopup->childCount() != 0)
mPopup->removeChild(mPopup->childCount()-1);
mPopup->setLayout(new GroupLayout(10));
int index = 0;
for (const auto &str: items) {
Button *button = new Button(mPopup, str);
button->setFlags(Button::RadioButton);
button->setCallback([&, index] {
mSelectedIndex = index;
setCaption(mItemsShort[index]);
setPushed(false);
popup()->setVisible(false);
if (mCallback)
mCallback(index);
});
index++;
}
setSelectedIndex(mSelectedIndex);
}
bool ComboBox::scrollEvent(const Vector2i &p, const Vector2f &rel) {
if (rel.y() < 0) {
setSelectedIndex(std::min(mSelectedIndex+1, (int)(items().size()-1)));
if (mCallback)
mCallback(mSelectedIndex);
return true;
} else if (rel.y() > 0) {
setSelectedIndex(std::max(mSelectedIndex-1, 0));
if (mCallback)
mCallback(mSelectedIndex);
return true;
}
return Widget::scrollEvent(p, rel);
}
void ComboBox::save(Serializer &s) const {
Widget::save(s);
s.set("items", mItems);
s.set("itemsShort", mItemsShort);
s.set("selectedIndex", mSelectedIndex);
}
bool ComboBox::load(Serializer &s) {
if (!Widget::load(s)) return false;
if (!s.get("items", mItems)) return false;
if (!s.get("itemsShort", mItemsShort)) return false;
if (!s.get("selectedIndex", mSelectedIndex)) return false;
return true;
}
/*
Modification: Inherits from ftl::gui2::PopupButton
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The nanogui::Widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
/**
* \file nanogui/combobox.h
*
* \brief Simple combo box nanogui::Widget based on a popup button.
*/
#pragma once
#include "popupbutton.hpp"
namespace ftl {
namespace gui2 {
/**
* \class ComboBox combobox.h nanogui/combobox.h
*
* \brief Simple combo box nanogui::Widget based on a popup button.
*/
class NANOGUI_EXPORT ComboBox : public PopupButton {
public:
/// Create an empty combo box
ComboBox(nanogui::Widget *parent);
/// Create a new combo box with the given items
ComboBox(nanogui::Widget *parent, const std::vector<std::string> &items);
/**
* \brief Create a new combo box with the given items, providing both short and
* long descriptive labels for each item
*/
ComboBox(nanogui::Widget *parent, const std::vector<std::string> &items,
const std::vector<std::string> &itemsShort);
/// The callback to execute for this ComboBox.
std::function<void(int)> callback() const { return mCallback; }
/// Sets the callback to execute for this ComboBox.
void setCallback(const std::function<void(int)> &callback) { mCallback = callback; }
/// The current index this ComboBox has selected.
int selectedIndex() const { return mSelectedIndex; }
/// Sets the current index this ComboBox has selected.
void setSelectedIndex(int idx);
/// Sets the items for this ComboBox, providing both short and long descriptive lables for each item.
void setItems(const std::vector<std::string> &items, const std::vector<std::string> &itemsShort);
/// Sets the items for this ComboBox.
void setItems(const std::vector<std::string> &items) { setItems(items, items); }
/// The items associated with this ComboBox.
const std::vector<std::string> &items() const { return mItems; }
/// The short descriptions associated with this ComboBox.
const std::vector<std::string> &itemsShort() const { return mItemsShort; }
/// Handles mouse scrolling events for this ComboBox.
virtual bool scrollEvent(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) override;
/// Saves the state of this ComboBox to the specified nanogui::Serializer.
virtual void save(nanogui::Serializer &s) const override;
/// Sets the state of this ComboBox from the specified nanogui::Serializer.
virtual bool load(nanogui::Serializer &s) override;
protected:
/// The items associated with this ComboBox.
std::vector<std::string> mItems;
/// The short descriptions of items associated with this ComboBox.
std::vector<std::string> mItemsShort;
/// The callback for this ComboBox.
std::function<void(int)> mCallback;
/// The current index this ComboBox has selected.
int mSelectedIndex;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
/*
nanogui/imageview.cpp -- Widget used to display images.
The image view widget was contributed by Stefan Ivanov.
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
#include <nanogui/window.h>
#include <nanogui/screen.h>
#include <nanogui/theme.h>
#include <cmath>
#include <ftl/utility/gltexture.hpp>
#include "imageview.hpp"
using namespace nanogui;
using ftl::gui2::ImageView;
using ftl::gui2::FTLImageView;
using ftl::utility::GLTexture;
namespace {
std::vector<std::string> tokenize(const std::string &string,
const std::string &delim = "\n",
bool includeEmpty = false) {
std::string::size_type lastPos = 0, pos = string.find_first_of(delim, lastPos);
std::vector<std::string> tokens;
while (lastPos != std::string::npos) {
std::string substr = string.substr(lastPos, pos - lastPos);
if (!substr.empty() || includeEmpty)
tokens.push_back(std::move(substr));
lastPos = pos;
if (lastPos != std::string::npos) {
lastPos += 1;
pos = string.find_first_of(delim, lastPos);
}
}
return tokens;
}
constexpr char const *const defaultImageViewVertexShader =
R"(#version 330
uniform vec2 scaleFactor;
uniform vec2 position;
uniform float flip_y;
in vec2 vertex;
out vec2 uv;
void main() {
uv = vertex;
vec2 scaledVertex = (vertex * scaleFactor) + position;
gl_Position = vec4(2.0*scaledVertex.x - 1.0,
flip_y*(1.0 - 2.0*scaledVertex.y),
0.0, 1.0);
})";
constexpr char const *const defaultImageViewFragmentShader =
R"(#version 330
uniform sampler2D image;
out vec4 color;
in vec2 uv;
void main() {
color = texture(image, uv);
color.w = 1;
})";
}
ftl::gui2::ImageView::ImageView(Widget* parent, GLuint imageID)
: Widget(parent), mImageID(imageID), mScale(1.0f), mOffset(Vector2f::Zero()),
mFixedScale(false), mFixedOffset(false), mPixelInfoCallback(nullptr) {
mImageSize = {0, 0};
if (imageID != unsigned(-1)) {
updateImageParameters();
}
mShader.init("ImageViewShader", defaultImageViewVertexShader,
defaultImageViewFragmentShader);
MatrixXu indices(3, 2);
indices.col(0) << 0, 1, 2;
indices.col(1) << 2, 3, 1;
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);
}
ftl::gui2::ImageView::~ImageView() {
mShader.free();
}
void ftl::gui2::ImageView::bindImage(GLuint imageId) {
if (imageId == unsigned(-1)) {
return;
}
mImageID = imageId;
updateImageParameters();
}
Vector2f ftl::gui2::ImageView::imageCoordinateAt(const Vector2f& position) const {
auto imagePosition = position - mOffset;
return imagePosition / mScale;
}
Vector2f ftl::gui2::ImageView::clampedImageCoordinateAt(const Vector2f& position) const {
auto imageCoordinate = imageCoordinateAt(position);
return imageCoordinate.cwiseMax(Vector2f::Zero()).cwiseMin(imageSizeF());
}
Vector2f ftl::gui2::ImageView::positionForCoordinate(const Vector2f& imageCoordinate) const {
return mScale*imageCoordinate + mOffset;
}
void ftl::gui2::ImageView::setImageCoordinateAt(const Vector2f& position, const Vector2f& imageCoordinate) {
// Calculate where the new offset must be in order to satisfy the image position equation.
// Round the floating point values to balance out the floating point to integer conversions.
mOffset = position - (imageCoordinate * mScale);
// Clamp offset so that the image remains near the screen.
mOffset = mOffset.cwiseMin(sizeF()).cwiseMax(-scaledImageSizeF());
}
void ftl::gui2::ImageView::center() {
mOffset = (sizeF() - scaledImageSizeF()) / 2;
}
void ftl::gui2::ImageView::fit() {
// Calculate the appropriate scaling factor.
mScale = (sizeF().cwiseQuotient(imageSizeF())).minCoeff();
center();
}
void ftl::gui2::ImageView::setScaleCentered(float scale) {
auto centerPosition = sizeF() / 2;
auto p = imageCoordinateAt(centerPosition);
mScale = scale;
setImageCoordinateAt(centerPosition, p);
}
void ftl::gui2::ImageView::moveOffset(const Vector2f& delta) {
// Apply the delta to the offset.
mOffset += delta;
// Prevent the image from going out of bounds.
auto scaledSize = scaledImageSizeF();
if (mOffset.x() + scaledSize.x() < 0)
mOffset.x() = -scaledSize.x();
if (mOffset.x() > sizeF().x())
mOffset.x() = sizeF().x();
if (mOffset.y() + scaledSize.y() < 0)
mOffset.y() = -scaledSize.y();
if (mOffset.y() > sizeF().y())
mOffset.y() = sizeF().y();
}
void ftl::gui2::ImageView::zoom(int amount, const Vector2f& focusPosition) {
auto focusedCoordinate = imageCoordinateAt(focusPosition);
float scaleFactor = std::pow(mZoomSensitivity, amount);
mScale = std::max(0.01f, scaleFactor * mScale);
setImageCoordinateAt(focusPosition, focusedCoordinate);
}
bool ftl::gui2::ImageView::mouseDragEvent(const Vector2i& p, const Vector2i& rel, int button, int /*modifiers*/) {
if ((button & (1 << GLFW_MOUSE_BUTTON_RIGHT)) != 0 && !mFixedOffset) {
setImageCoordinateAt((p + rel).cast<float>(), imageCoordinateAt(p.cast<float>()));
return true;
}
return false;
}
bool ftl::gui2::ImageView::gridVisible() const {
return (mGridThreshold != -1) && (mScale > mGridThreshold);
}
bool ftl::gui2::ImageView::pixelInfoVisible() const {
return mPixelInfoCallback && (mPixelInfoThreshold != -1) && (mScale > mPixelInfoThreshold);
}
bool ftl::gui2::ImageView::helpersVisible() const {
return gridVisible() || pixelInfoVisible();
}
bool ftl::gui2::ImageView::scrollEvent(const Vector2i& p, const Vector2f& rel) {
if (mFixedScale)
return false;
float v = rel.y();
if (std::abs(v) < 1)
v = std::copysign(1.f, v);
zoom(v, (p - position()).cast<float>());
return true;
}
bool ftl::gui2::ImageView::keyboardEvent(int key, int /*scancode*/, int action, int modifiers) {
if (action) {
switch (key) {
case GLFW_KEY_LEFT:
if (!mFixedOffset) {
if (GLFW_MOD_CONTROL & modifiers)
moveOffset(Vector2f(30, 0));
else
moveOffset(Vector2f(10, 0));
return true;
}
break;
case GLFW_KEY_RIGHT:
if (!mFixedOffset) {
if (GLFW_MOD_CONTROL & modifiers)
moveOffset(Vector2f(-30, 0));
else
moveOffset(Vector2f(-10, 0));
return true;
}
break;
case GLFW_KEY_DOWN:
if (!mFixedOffset) {
if (GLFW_MOD_CONTROL & modifiers)
moveOffset(Vector2f(0, -30));
else
moveOffset(Vector2f(0, -10));
return true;
}
break;
case GLFW_KEY_UP:
if (!mFixedOffset) {
if (GLFW_MOD_CONTROL & modifiers)
moveOffset(Vector2f(0, 30));
else
moveOffset(Vector2f(0, 10));
return true;
}
break;
}
}
return false;
}
bool ftl::gui2::ImageView::keyboardCharacterEvent(unsigned int codepoint) {
switch (codepoint) {
case '-':
if (!mFixedScale) {
zoom(-1, sizeF() / 2);
return true;
}
break;
case '+':
if (!mFixedScale) {
zoom(1, sizeF() / 2);
return true;
}
break;
case 'c':
if (!mFixedOffset) {
center();
return true;
}
break;
case 'f':
if (!mFixedOffset && !mFixedScale) {
fit();
return true;
}
break;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
if (!mFixedScale) {
setScaleCentered(1 << (codepoint - '1'));
return true;
}
break;
default:
return false;
}
return false;
}
Vector2i ftl::gui2::ImageView::preferredSize(NVGcontext* /*ctx*/) const {
return mImageSize;
}
void ftl::gui2::ImageView::performLayout(NVGcontext* ctx) {
Widget::performLayout(ctx);
}
void ftl::gui2::ImageView::draw(NVGcontext* ctx) {
Widget::draw(ctx);
if (mImageID != unsigned(-1)) {
nvgEndFrame(ctx); // Flush the NanoVG draw stack, not necessary to call nvgBeginFrame afterwards.
//drawImageBorder(ctx);
// Calculate several variables that need to be send to OpenGL in order for the image to be
// properly displayed inside the widget.
const Screen* screen = dynamic_cast<const Screen*>(this->screen());
Vector2f screenSize = screen->size().cast<float>();
Vector2f scaleFactor = mScale * imageSizeF().cwiseQuotient(screenSize);
Vector2f positionInScreen = absolutePosition().cast<float>();
Vector2f positionAfterOffset = positionInScreen + mOffset;
Vector2f imagePosition = positionAfterOffset.cwiseQuotient(screenSize);
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);
mShader.bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mImageID);
mShader.setUniform("image", 0);
mShader.setUniform("flip_y", (flipped_) ? -1.0f : 1.0f);
mShader.setUniform("scaleFactor", scaleFactor);
mShader.setUniform("position", imagePosition);
mShader.drawIndexed(GL_TRIANGLES, 0, 2);
glDisable(GL_SCISSOR_TEST);
}
if (helpersVisible())
drawHelpers(ctx);
//drawWidgetBorder(ctx);
}
void ftl::gui2::ImageView::updateImageParameters() {
// Query the width of the OpenGL texture.
glBindTexture(GL_TEXTURE_2D, mImageID);
GLint w, h;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
mImageSize = Vector2i(w, h);
}
void ftl::gui2::ImageView::drawWidgetBorder(NVGcontext* ctx) const {
nvgBeginPath(ctx);
nvgStrokeWidth(ctx, 1);
nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
mSize.y() - 1, 0);
nvgStrokeColor(ctx, mTheme->mWindowPopup);
nvgStroke(ctx);
nvgBeginPath(ctx);
nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
mSize.y() - 1, mTheme->mButtonCornerRadius);
nvgStrokeColor(ctx, mTheme->mBorderDark);
nvgStroke(ctx);
}
void ftl::gui2::ImageView::drawImageBorder(NVGcontext* ctx) const {
nvgSave(ctx);
nvgBeginPath(ctx);
nvgScissor(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y());
nvgStrokeWidth(ctx, 1.0f);
Vector2i borderPosition = mPos + mOffset.cast<int>();
Vector2i borderSize = scaledImageSizeF().cast<int>();
nvgRect(ctx, borderPosition.x() - 0.5f, borderPosition.y() - 0.5f,
borderSize.x() + 1, borderSize.y() + 1);
nvgStrokeColor(ctx, Color(1.0f, 1.0f, 1.0f, 1.0f));
nvgStroke(ctx);
nvgResetScissor(ctx);
nvgRestore(ctx);
}
void ftl::gui2::ImageView::drawHelpers(NVGcontext* ctx) const {
// We need to apply mPos after the transformation to account for the position of the widget
// relative to the parent.
Vector2f upperLeftCorner = positionForCoordinate(Vector2f::Zero()) + positionF();
Vector2f lowerRightCorner = positionForCoordinate(imageSizeF()) + positionF();
if (gridVisible())
drawPixelGrid(ctx, upperLeftCorner, lowerRightCorner, mScale);
if (pixelInfoVisible())
drawPixelInfo(ctx, mScale);
}
void ftl::gui2::ImageView::drawPixelGrid(NVGcontext* ctx, const Vector2f& upperLeftCorner,
const Vector2f& lowerRightCorner, float stride) {
nvgBeginPath(ctx);
// Draw the vertical grid lines
float currentX = upperLeftCorner.x();
while (currentX <= lowerRightCorner.x()) {
nvgMoveTo(ctx, std::round(currentX), std::round(upperLeftCorner.y()));
nvgLineTo(ctx, std::round(currentX), std::round(lowerRightCorner.y()));
currentX += stride;
}
// Draw the horizontal grid lines
float currentY = upperLeftCorner.y();
while (currentY <= lowerRightCorner.y()) {
nvgMoveTo(ctx, std::round(upperLeftCorner.x()), std::round(currentY));
nvgLineTo(ctx, std::round(lowerRightCorner.x()), std::round(currentY));
currentY += stride;
}
nvgStrokeWidth(ctx, 1.0f);
nvgStrokeColor(ctx, Color(1.0f, 1.0f, 1.0f, 0.2f));
nvgStroke(ctx);
}
void ftl::gui2::ImageView::drawPixelInfo(NVGcontext* ctx, float stride) const {
// Extract the image coordinates at the two corners of the widget.
Vector2i topLeft = clampedImageCoordinateAt(Vector2f::Zero())
.unaryExpr([](float x) { return std::floor(x); })
.cast<int>();
Vector2i bottomRight = clampedImageCoordinateAt(sizeF())
.unaryExpr([](float x) { return std::ceil(x); })
.cast<int>();
// Extract the positions for where to draw the text.
Vector2f currentCellPosition =
(positionF() + positionForCoordinate(topLeft.cast<float>()));
float xInitialPosition = currentCellPosition.x();
int xInitialIndex = topLeft.x();
// Properly scale the pixel information for the given stride.
auto fontSize = stride * mFontScaleFactor;
static constexpr float maxFontSize = 30.0f;
fontSize = fontSize > maxFontSize ? maxFontSize : fontSize;
nvgBeginPath(ctx);
nvgFontSize(ctx, fontSize);
nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
nvgFontFace(ctx, "sans");
while (topLeft.y() != bottomRight.y()) {
while (topLeft.x() != bottomRight.x()) {
writePixelInfo(ctx, currentCellPosition, topLeft, stride, fontSize);
currentCellPosition.x() += stride;
++topLeft.x();
}
currentCellPosition.x() = xInitialPosition;
currentCellPosition.y() += stride;
++topLeft.y();
topLeft.x() = xInitialIndex;
}
}
void ftl::gui2::ImageView::writePixelInfo(NVGcontext* ctx, const Vector2f& cellPosition,
const Vector2i& pixel, float stride, float fontSize) const {
auto pixelData = mPixelInfoCallback(pixel);
auto pixelDataRows = tokenize(pixelData.first);
// If no data is provided for this pixel then simply return.
if (pixelDataRows.empty())
return;
nvgFillColor(ctx, pixelData.second);
float yOffset = (stride - fontSize * pixelDataRows.size()) / 2;
for (size_t i = 0; i != pixelDataRows.size(); ++i) {
nvgText(ctx, cellPosition.x() + stride / 2, cellPosition.y() + yOffset,
pixelDataRows[i].data(), nullptr);
yOffset += fontSize;
}
}
////////////////////////////////////////////////////////////////////////////////
FTLImageView::~FTLImageView() {
}
void FTLImageView::draw(NVGcontext* ctx) {
if (texture_.isValid()) {
if (!was_valid_) { fit(); }
ImageView::draw(ctx);
}
was_valid_ = texture_.isValid();
}
GLTexture& FTLImageView::texture() {
return texture_;
}
void FTLImageView::copyFrom(const ftl::cuda::TextureObject<uchar4> &buf, cudaStream_t stream ) {
texture_.copyFrom(buf, stream);
bindImage(texture_.texture());
}
void FTLImageView::copyFrom(const cv::Mat &im, cudaStream_t stream) {
texture_.copyFrom(im, stream);
bindImage(texture_.texture());
}
void FTLImageView::copyFrom(const cv::cuda::GpuMat &im, cudaStream_t stream) {
texture_.copyFrom(im, stream);
bindImage(texture_.texture());
}
void FTLImageView::copyFrom(ftl::rgbd::Frame& frame, ftl::codecs::Channel channel) {
if (frame.hasOpenGL(channel)) {
bindImage(frame.getOpenGL(channel));
if (texture_.isValid()) {
texture_.free();
}
}
else if (frame.isGPU(channel)) {
copyFrom(frame.get<cv::cuda::GpuMat>(channel));
}
else {
copyFrom(frame.get<cv::Mat>(channel));
}
}
nanogui::Vector2i ftl::gui2::FTLImageView::preferredSize(NVGcontext* /*ctx*/) const {
/** this avoids issues if layout not set to fill/maximum */
return mSize;
}
// ==== StereoImageView ========================================================
using ftl::gui2::StereoImageView;
StereoImageView::StereoImageView(nanogui::Widget* parent, nanogui::Orientation orientation) :
nanogui::Widget(parent), orientation_(orientation) {
setLayout(new nanogui::BoxLayout(orientation_, nanogui::Alignment::Fill));
left_ = new FTLImageView(this);
right_ = new FTLImageView(this);
// disables mouse/keyboard events in widgets
left_->setFixedOffset(true);
left_->setFixedScale(true);
right_->setFixedOffset(true);
right_->setFixedScale(true);
}
nanogui::Vector2f StereoImageView::imageCoordinateAt(const nanogui::Vector2f& p) const {
nanogui::Vector2f pos = position().cast<float>();
nanogui::Vector2f posr = pos + right_->position().cast<float>();
bool is_right =
((p.x() >= posr.x()) && (orientation_ == nanogui::Orientation::Horizontal)) ||
((p.y() >= posr.y()) && (orientation_ == nanogui::Orientation::Vertical));
if (is_right) {
return right_->imageCoordinateAt(p - right_->position().cast<float>());
}
else {
return left_->imageCoordinateAt(p);
}
}
bool StereoImageView::mouseMotionEvent(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) {
if ((button & (1 << GLFW_MOUSE_BUTTON_RIGHT)) != 0) {
nanogui::Vector2f posl = left_->imageCoordinateAt(p.cast<float>());
nanogui::Vector2f posr = right_->imageCoordinateAt(p.cast<float>());
if (posl.minCoeff() > 0) {
left_->setImageCoordinateAt((p + rel).cast<float>(), posl);
right_->setImageCoordinateAt((p + rel).cast<float>(), posl);
}
if (posr.minCoeff() > 0) {
left_->setImageCoordinateAt((p + rel).cast<float>(), posr);
right_->setImageCoordinateAt((p + rel).cast<float>(), posr);
}
return true;
}
return false;
}
bool StereoImageView::scrollEvent(const nanogui::Vector2i& p, const nanogui::Vector2f& rel) {
// synchronized zoom
float v = rel.y();
nanogui::Vector2f pos = position().cast<float>();
nanogui::Vector2f posl = pos + left_->position().cast<float>();
nanogui::Vector2f posr = pos + right_->position().cast<float>();
nanogui::Vector2f pf = p.cast<float>();
// zooming on right image?
bool zoom_right =
((p.x() >= posr.x()) && (orientation_ == nanogui::Orientation::Horizontal)) ||
((p.y() >= posr.y()) && (orientation_ == nanogui::Orientation::Vertical));
if (orientation_ == nanogui::Orientation::Horizontal) {
if (zoom_right) {
left_->zoom(v, pf - nanogui::Vector2f{float(left_->width()), 0.0f} - posl);
right_->zoom(v, pf - posr);
}
else {
left_->zoom(v, pf - posl);
right_->zoom(v, nanogui::Vector2f{float(right_->width()), 0.0f} + pf - posr);
}
}
else { // same as above, flip x/y
if (zoom_right) {
left_->zoom(v, pf - nanogui::Vector2f{0.0f, float(left_->height())} - posl);
right_->zoom(v, pf - posr);
}
else {
left_->zoom(v, pf - posl);
right_->zoom(v, nanogui::Vector2f{0.0f, float(right_->height())} + pf - posr);
}
}
return true;
}
bool StereoImageView::keyboardEvent(int key, int /*scancode*/, int action, int modifiers) {
return true; // copy code from above (ImageView)?
}
void StereoImageView::fit() {
left()->fit();
right()->fit();
}
bool StereoImageView::keyboardCharacterEvent(unsigned int codepoint) {
switch (codepoint) {
case 'c':
left_->center();
right_->center();
return true;
case 'f':
left_->fit();
right_->fit();
return true;
default:
return true;
}
}
void StereoImageView::performLayout(NVGcontext *ctx) {
if (orientation_ == nanogui::Orientation::Horizontal) {
left_->setSize({width()/2, height()});
right_->setSize({width()/2, height()});
}
else { // Orientation::Vertical
left_->setSize({width(), height()/2});
right_->setSize({width(), height()/2});
}
Widget::performLayout(ctx);
}
/*
nanogui/imageview.h -- Widget used to display images.
The image view widget was contributed by Stefan Ivanov.
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
/** \file */
#pragma once
#include <nanogui/widget.h>
#include <nanogui/glutil.h>
#include <nanogui/layout.h>
#include <functional>
#include <ftl/rgbd/frame.hpp>
#include <ftl/codecs/channels.hpp>
#include <ftl/utility/gltexture.hpp>
namespace ftl
{
namespace gui2 {
/**
* \class ImageView imageview.h nanogui/imageview.h
*
* \brief Widget used to display images.
*/
class NANOGUI_EXPORT ImageView : public nanogui::Widget {
public:
ImageView(nanogui::Widget* parent, GLuint imageID = -1);
virtual ~ImageView();
void bindImage(GLuint imageId);
nanogui::GLShader& imageShader() { return mShader; }
nanogui::Vector2f positionF() const { return mPos.cast<float>(); }
nanogui::Vector2f sizeF() const { return mSize.cast<float>(); }
const nanogui::Vector2i& imageSize() const { return mImageSize; }
nanogui::Vector2i scaledImageSize() const { return (mScale * mImageSize.cast<float>()).cast<int>(); }
nanogui::Vector2f imageSizeF() const { return mImageSize.cast<float>(); }
nanogui::Vector2f scaledImageSizeF() const { return (mScale * mImageSize.cast<float>()); }
const nanogui::Vector2f& offset() const { return mOffset; }
void setOffset(const nanogui::Vector2f& offset) { mOffset = offset; }
float scale() const { return mScale; }
void setScale(float scale) { mScale = scale > 0.01f ? scale : 0.01f; }
inline void setFlipped(bool flipped) { flipped_ = flipped; }
bool fixedOffset() const { return mFixedOffset; }
void setFixedOffset(bool fixedOffset) { mFixedOffset = fixedOffset; }
bool fixedScale() const { return mFixedScale; }
void setFixedScale(bool fixedScale) { mFixedScale = fixedScale; }
float zoomSensitivity() const { return mZoomSensitivity; }
void setZoomSensitivity(float zoomSensitivity) { mZoomSensitivity = zoomSensitivity; }
float gridThreshold() const { return mGridThreshold; }
void setGridThreshold(float gridThreshold) { mGridThreshold = gridThreshold; }
float pixelInfoThreshold() const { return mPixelInfoThreshold; }
void setPixelInfoThreshold(float pixelInfoThreshold) { mPixelInfoThreshold = pixelInfoThreshold; }
#ifndef DOXYGEN_SHOULD_SKIP_THIS
void setPixelInfoCallback(const std::function<std::pair<std::string, nanogui::Color>(const nanogui::Vector2i&)>& callback) {
mPixelInfoCallback = callback;
}
const std::function<std::pair<std::string, nanogui::Color>(const nanogui::Vector2i&)>& pixelInfoCallback() const {
return mPixelInfoCallback;
}
#endif // DOXYGEN_SHOULD_SKIP_THIS
void setFontScaleFactor(float fontScaleFactor) { mFontScaleFactor = fontScaleFactor; }
float fontScaleFactor() const { return mFontScaleFactor; }
// Image transformation functions.
/// Calculates the image coordinates of the given pixel position on the widget.
nanogui::Vector2f imageCoordinateAt(const nanogui::Vector2f& position) const;
/**
* Calculates the image coordinates of the given pixel position on the widget.
* If the position provided corresponds to a coordinate outside the range of
* the image, the coordinates are clamped to edges of the image.
*/
nanogui::Vector2f clampedImageCoordinateAt(const nanogui::Vector2f& position) const;
/// Calculates the position inside the widget for the given image coordinate. Origin?
nanogui::Vector2f positionForCoordinate(const nanogui::Vector2f& imageCoordinate) const;
/**
* Modifies the internal state of the image viewer widget so that the pixel at the provided
* position on the widget has the specified image coordinate. Also clamps the values of offset
* to the sides of the widget.
*/
void setImageCoordinateAt(const nanogui::Vector2f& position, const nanogui::Vector2f& imageCoordinate);
/// Centers the image without affecting the scaling factor.
void center();
/// Centers and scales the image so that it fits inside the widgets.
void fit();
/// Set the scale while keeping the image centered
void setScaleCentered(float scale);
/// Moves the offset by the specified amount. Does bound checking.
void moveOffset(const nanogui::Vector2f& delta);
/**
* Changes the scale factor by the provided amount modified by the zoom sensitivity member variable.
* The scaling occurs such that the image coordinate under the focused position remains in
* the same position before and after the scaling.
*/
void zoom(int amount, const nanogui::Vector2f& focusPosition);
bool keyboardEvent(int key, int scancode, int action, int modifiers) override;
bool keyboardCharacterEvent(unsigned int codepoint) override;
//bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override;
bool mouseDragEvent(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) override;
bool scrollEvent(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) override;
/// Function indicating whether the grid is currently visible.
bool gridVisible() const;
/// Function indicating whether the pixel information is currently visible.
bool pixelInfoVisible() const;
/// Function indicating whether any of the overlays are visible.
bool helpersVisible() const;
nanogui::Vector2i preferredSize(NVGcontext* ctx) const override;
void performLayout(NVGcontext* ctx) override;
void draw(NVGcontext* ctx) override;
protected:
// Helper image methods.
void updateImageParameters();
// Helper drawing methods.
void drawWidgetBorder(NVGcontext* ctx) const;
void drawImageBorder(NVGcontext* ctx) const;
void drawHelpers(NVGcontext* ctx) const;
static void drawPixelGrid(NVGcontext* ctx, const nanogui::Vector2f& upperLeftCorner,
const nanogui::Vector2f& lowerRightCorner, float stride);
void drawPixelInfo(NVGcontext* ctx, float stride) const;
void writePixelInfo(NVGcontext* ctx, const nanogui::Vector2f& cellPosition,
const nanogui::Vector2i& pixel, float stride, float fontSize) const;
// Image parameters.
nanogui::GLShader mShader;
GLuint mImageID;
nanogui::Vector2i mImageSize;
// Image display parameters.
float mScale;
nanogui::Vector2f mOffset;
bool mFixedScale;
bool mFixedOffset;
bool flipped_ = false;
// Fine-tuning parameters.
float mZoomSensitivity = 1.1f;
// Image info parameters.
float mGridThreshold = -1;
float mPixelInfoThreshold = -1;
// Image pixel data display members.
std::function<std::pair<std::string, nanogui::Color>(const nanogui::Vector2i&)> mPixelInfoCallback;
float mFontScaleFactor = 0.2f;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
/**
* Simple wrapper for drawing FTLImageView.
*/
class FTLImageView : public ImageView {
public:
using ImageView::ImageView;
FTLImageView(nanogui::Widget* parent, GLuint imageID = -1) : ImageView(parent, imageID), was_valid_(false) {}
virtual ~FTLImageView();
virtual void draw(NVGcontext* ctx) override;
virtual nanogui::Vector2i preferredSize(NVGcontext* ctx) const override;
/** Get GLTexture instance */
ftl::utility::GLTexture& texture();
/** Copy&Bind */
void copyFrom(const ftl::cuda::TextureObject<uchar4> &buf, cudaStream_t stream = cudaStreamDefault);
void copyFrom(const cv::Mat &im, cudaStream_t stream = cudaStreamDefault);
void copyFrom(const cv::cuda::GpuMat &im, cudaStream_t stream = cudaStreamDefault);
/** From frame, use OpenGL if available (no copy), otherwise copy from GPU/CPU */
void copyFrom(ftl::rgbd::Frame& frame, ftl::codecs::Channel channel);
private:
ftl::utility::GLTexture texture_;
bool was_valid_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
/** Two ImageViews with synchronized zoom and pan. Widget split in two equal
* size sections (left and right). With vertical orientation right is the lower
* image.
*/
class StereoImageView : public nanogui::Widget {
public:
StereoImageView(nanogui::Widget* parent, nanogui::Orientation orientation = nanogui::Orientation::Horizontal);
virtual void performLayout(NVGcontext* ctx) override;
bool keyboardEvent(int key, int scancode, int action, int modifiers) override;
bool keyboardCharacterEvent(unsigned int codepoint) override;
bool mouseMotionEvent(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) override;
bool scrollEvent(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) override;
FTLImageView* left() { return left_; }
FTLImageView* right() { return right_; }
/** get image coordinate at given widget coordinate */
nanogui::Vector2f imageCoordinateAt(const nanogui::Vector2f& position) const;
nanogui::Orientation orientation() { return orientation_; }
void fit();
void bindLeft(GLuint id) { left_->texture().free(); left_->bindImage(id); }
void bindRight(GLuint id) { right_->texture().free(); right_->bindImage(id); }
private:
nanogui::Orientation orientation_;
FTLImageView* left_;
FTLImageView* right_;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#include "leftbutton.hpp"
#include <nanogui/button.h>
#include <nanogui/theme.h>
#include <nanogui/opengl.h>
void ftl::gui2::LeftButton::draw(NVGcontext* ctx) {
using namespace nanogui;
Widget::draw(ctx);
NVGcolor gradTop = mTheme->mButtonGradientTopUnfocused;
NVGcolor gradBot = mTheme->mButtonGradientBotUnfocused;
if (mPushed) {
gradTop = mTheme->mButtonGradientTopPushed;
gradBot = mTheme->mButtonGradientBotPushed;
} else if (mMouseFocus && mEnabled) {
gradTop = mTheme->mButtonGradientTopFocused;
gradBot = mTheme->mButtonGradientBotFocused;
}
nvgBeginPath(ctx);
nvgRoundedRect(ctx, mPos.x() + 1, mPos.y() + 1.0f, mSize.x() - 2,
mSize.y() - 2, mTheme->mButtonCornerRadius - 1);
if (mBackgroundColor.w() != 0) {
nvgFillColor(ctx, Color(mBackgroundColor.head<3>(), 1.f));
nvgFill(ctx);
if (mPushed) {
gradTop.a = gradBot.a = 0.8f;
} else {
double v = 1 - mBackgroundColor.w();
gradTop.a = gradBot.a = mEnabled ? v : v * .5f + .5f;
}
}
NVGpaint bg = nvgLinearGradient(ctx, mPos.x(), mPos.y(), mPos.x(),
mPos.y() + mSize.y(), gradTop, gradBot);
nvgFillPaint(ctx, bg);
nvgFill(ctx);
nvgBeginPath(ctx);
nvgStrokeWidth(ctx, 1.0f);
nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + (mPushed ? 0.5f : 1.5f), mSize.x() - 1,
mSize.y() - 1 - (mPushed ? 0.0f : 1.0f), mTheme->mButtonCornerRadius);
nvgStrokeColor(ctx, mTheme->mBorderLight);
nvgStroke(ctx);
nvgBeginPath(ctx);
nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
mSize.y() - 2, mTheme->mButtonCornerRadius);
nvgStrokeColor(ctx, mTheme->mBorderDark);
nvgStroke(ctx);
int fontSize = mFontSize == -1 ? mTheme->mButtonFontSize : mFontSize;
nvgFontSize(ctx, fontSize);
nvgFontFace(ctx, "sans-bold");
float tw = nvgTextBounds(ctx, 0,0, mCaption.c_str(), nullptr, nullptr);
Vector2f center = mPos.cast<float>() + mSize.cast<float>() * 0.5f;
Vector2f textPos(mPos.x() + 8, center.y() - 1);
NVGcolor textColor =
mTextColor.w() == 0 ? mTheme->mTextColor : mTextColor;
if (!mEnabled)
textColor = mTheme->mDisabledTextColor;
if (mIcon) {
auto icon = utf8(mIcon);
float iw, ih = fontSize;
if (nvgIsFontIcon(mIcon)) {
ih *= icon_scale();
nvgFontSize(ctx, ih);
nvgFontFace(ctx, "icons");
iw = nvgTextBounds(ctx, 0, 0, icon.data(), nullptr, nullptr);
} else {
int w, h;
ih *= 0.9f;
nvgImageSize(ctx, mIcon, &w, &h);
iw = w * ih / h;
}
if (mCaption != "")
iw += mSize.y() * 0.15f;
nvgFillColor(ctx, textColor);
nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
Vector2f iconPos = center;
iconPos.y() -= 1;
if (mIconPosition == IconPosition::LeftCentered) {
iconPos.x() -= (tw + iw) * 0.5f;
textPos.x() += iw * 0.5f;
} else if (mIconPosition == IconPosition::RightCentered) {
textPos.x() -= iw * 0.5f;
iconPos.x() += tw * 0.5f;
} else if (mIconPosition == IconPosition::Left) {
iconPos.x() = mPos.x() + 8;
} else if (mIconPosition == IconPosition::Right) {
iconPos.x() = mPos.x() + mSize.x() - iw - 8;
}
if (nvgIsFontIcon(mIcon)) {
nvgText(ctx, iconPos.x(), iconPos.y()+1, icon.data(), nullptr);
} else {
NVGpaint imgPaint = nvgImagePattern(ctx,
iconPos.x(), iconPos.y() - ih/2, iw, ih, 0, mIcon, mEnabled ? 0.5f : 0.25f);
nvgFillPaint(ctx, imgPaint);
nvgFill(ctx);
}
}
nvgFontSize(ctx, fontSize);
nvgFontFace(ctx, "sans-bold");
nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgFillColor(ctx, mTheme->mTextColorShadow);
nvgText(ctx, textPos.x(), textPos.y(), mCaption.c_str(), nullptr);
nvgFillColor(ctx, textColor);
nvgText(ctx, textPos.x(), textPos.y() + 1, mCaption.c_str(), nullptr);
}
\ No newline at end of file
#pragma once
#include <nanogui/button.h>
namespace ftl {
namespace gui2 {
/**
* Allow left aligned button text.
*/
class LeftButton : public nanogui::Button {
public:
LeftButton(nanogui::Widget *parent, const std::string &caption = "",
int buttonIcon = 0) : nanogui::Button(parent, caption, buttonIcon) {};
virtual ~LeftButton() {};
virtual void draw(NVGcontext* ctx) override;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
/*
src/popupbutton.cpp -- Button which launches a popup widget
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
#include "popupbutton.hpp"
#include <nanogui/theme.h>
#include <nanogui/opengl.h>
#include <nanogui/serializer/core.h>
#include <nanogui/popup.h>
using nanogui::Widget;
using nanogui::Window;
using nanogui::Button;
using nanogui::Popup;
using nanogui::Serializer;
using nanogui::utf8;
using nanogui::Vector2i;
using nanogui::Vector2f;
using ftl::gui2::PopupButton;
PopupButton::PopupButton(Widget *parent, const std::string &caption, int buttonIcon)
: Button(parent, caption, buttonIcon) {
mChevronIcon = mTheme->mPopupChevronRightIcon;
setFlags(Flags::ToggleButton | Flags::PopupButton);
Window *parentWindow = window();
mPopup = new Popup(parentWindow->parent(), window());
mPopup->setSize(Vector2i(320, 250));
mPopup->setVisible(false);
mIconExtraScale = 0.8f;// widget override
}
PopupButton::~PopupButton() {
if (mPopup->parent()->getRefCount() > 0) {
mPopup->setVisible(false);
mPopup->dispose();
}
}
Vector2i PopupButton::preferredSize(NVGcontext *ctx) const {
return Button::preferredSize(ctx) + Vector2i(15, 0);
}
void PopupButton::draw(NVGcontext* ctx) {
if (!mEnabled && mPushed)
mPushed = false;
mPopup->setVisible(mPushed);
Button::draw(ctx);
if (mChevronIcon) {
auto icon = utf8(mChevronIcon);
NVGcolor textColor =
mTextColor.w() == 0 ? mTheme->mTextColor : mTextColor;
nvgFontSize(ctx, (mFontSize < 0 ? mTheme->mButtonFontSize : mFontSize) * icon_scale());
nvgFontFace(ctx, "icons");
nvgFillColor(ctx, mEnabled ? textColor : mTheme->mDisabledTextColor);
nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
float iw = nvgTextBounds(ctx, 0, 0, icon.data(), nullptr, nullptr);
Vector2f iconPos(0, mPos.y() + mSize.y() * 0.5f - 1);
if (mPopup->side() == Popup::Right)
iconPos[0] = mPos.x() + mSize.x() - iw - 8;
else
iconPos[0] = mPos.x() + 8;
nvgText(ctx, iconPos.x(), iconPos.y(), icon.data(), nullptr);
}
}
void PopupButton::performLayout(NVGcontext *ctx) {
Widget::performLayout(ctx);
const Window *parentWindow = window();
int posY = absolutePosition().y() - parentWindow->position().y() + mSize.y() /2;
if (mPopup->side() == Popup::Right)
mPopup->setAnchorPos(Vector2i(parentWindow->width() + 15, posY));
else
mPopup->setAnchorPos(Vector2i(0 - 15, posY));
}
void PopupButton::setSide(Popup::Side side) {
if (mPopup->side() == Popup::Right &&
mChevronIcon == mTheme->mPopupChevronRightIcon)
setChevronIcon(mTheme->mPopupChevronLeftIcon);
else if (mPopup->side() == Popup::Left &&
mChevronIcon == mTheme->mPopupChevronLeftIcon)
setChevronIcon(mTheme->mPopupChevronRightIcon);
mPopup->setSide(side);
}
void PopupButton::save(Serializer &s) const {
Button::save(s);
s.set("chevronIcon", mChevronIcon);
}
bool PopupButton::load(Serializer &s) {
if (!Button::load(s))
return false;
if (!s.get("chevronIcon", mChevronIcon))
return false;
return true;
}
#pragma once
#include <nanogui/button.h>
#include <nanogui/popup.h>
namespace ftl {
namespace gui2 {
/**
* Patched version of nanogui::PopopButton with destructor which also removes
* popup window on destruction.
*
* \class PopupButton popupbutton.h nanogui/popupbutton.h
*
* \brief Button which launches a popup widget.
*
* \remark
* This class overrides \ref nanogui::Widget::mIconExtraScale to be ``0.8f``,
* which affects all subclasses of this Widget. Subclasses must explicitly
* set a different value if needed (e.g., in their constructor).
*/
class PopupButton : public nanogui::Button {
public:
PopupButton(nanogui::Widget *parent, const std::string &caption = "",
int buttonIcon = 0);
virtual ~PopupButton();
void setChevronIcon(int icon) { mChevronIcon = icon; }
int chevronIcon() const { return mChevronIcon; }
void setSide(nanogui::Popup::Side popupSide);
nanogui::Popup::Side side() const { return mPopup->side(); }
nanogui::Popup *popup() { return mPopup; }
const nanogui::Popup *popup() const { return mPopup; }
virtual void draw(NVGcontext* ctx) override;
virtual nanogui::Vector2i preferredSize(NVGcontext *ctx) const override;
virtual void performLayout(NVGcontext *ctx) override;
virtual void save(nanogui::Serializer &s) const override;
virtual bool load(nanogui::Serializer &s) override;
protected:
nanogui::Popup *mPopup;
int mChevronIcon;
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
}
}
#include <nanogui/layout.h>
#include <nanogui/label.h>
#include <nanogui/slider.h>
#include "soundctrl.hpp"
#include "../screen.hpp"
using ftl::gui2::PopupButton;
using ftl::gui2::VolumeButton;
using ftl::gui2::Screen;
VolumeButton::VolumeButton(nanogui::Widget *parent, ftl::audio::StereoMixerF<100> *mixer) :
ftl::gui2::PopupButton(parent, "", ENTYPO_ICON_SOUND), mixer_(mixer) {
setChevronIcon(-1);
muted_ = false;
mPopup->setLayout(new nanogui::GroupLayout(15, 6, 14, 0));
new nanogui::Label(mPopup, "Volume");
slider_ = new nanogui::Slider(mPopup);
slider_->setHighlightColor(dynamic_cast<Screen*>(screen())->getColor("highlight1"));
slider_->setHeight(20);
mPopup->setFixedWidth(200);
slider_->setCallback([this](float value) {
setValue(value);
if (cb_) { cb_(value); }
});
if (mixer) {
auto *mixbut = new nanogui::Button(mPopup, "Mixer", ENTYPO_ICON_SOUND_MIX);
mPopup->setAnchorHeight(70);
auto *mixer_widget = new nanogui::Widget(mPopup);
mixer_widget->setLayout(new nanogui::GroupLayout(0, 6, 14, 0));
mixer_widget->setVisible(false);
// Add mixer slider for each track in mixer.
for (int t=0; t<mixer->tracks(); ++t) {
auto *label = new nanogui::Label(mixer_widget, mixer->name(t));
label->setFontSize(12);
auto *mixslider = new nanogui::Slider(mixer_widget);
mixslider->setHighlightColor(dynamic_cast<Screen*>(screen())->getColor("highlight1"));
mixslider->setHeight(20);
mixslider->setValue(mixer->gain(t));
mixslider->setHighlightedRange({0.0f, mixer->gain(t)});
mixslider->setCallback([this,t,mixslider](float value) {
mixslider->setValue(value);
mixslider->setHighlightedRange({0.0f, value});
mixer_->setGain(t, value);
});
}
mixbut->setCallback([this,mixer_widget]() {
mixer_widget->setVisible(!mixer_widget->visible());
if (mixer_widget->visible()) {
mPopup->setAnchorHeight(70+mixer_widget->childCount()*20);
} else {
mPopup->setAnchorHeight(70);
}
screen()->performLayout();
});
}
}
VolumeButton::~VolumeButton() {
}
void VolumeButton::setCallback(std::function<void(float)> cb) {
cb_ = cb;
}
void VolumeButton::update() {
slider_->setValue(value_);
slider_->setHighlightedRange({0.0f, value_});
if (muted_ || value_ == 0.0f) {
setIcon(ICON_MUTED);
}
else if (value_ < 0.33){
setIcon(ICON_VOLUME_1);
}
else if (value_ >= 0.67) {
setIcon(ICON_VOLUME_3);
}
else {
setIcon(ICON_VOLUME_2);
}
}
void VolumeButton::setValue(float v) {
value_ = v;
setMuted(false);
update();
}
float VolumeButton::value() {
return muted_ ? 0.0f : value_;
}
void VolumeButton::setMuted(bool v) {
if (muted_ == v) {
return;
}
muted_ = v;
if (muted_) {
slider_->setHighlightColor(
dynamic_cast<Screen*>(screen())->getColor("highlight1_disabled"));
}
else {
slider_->setHighlightColor(
dynamic_cast<Screen*>(screen())->getColor("highlight1"));
}
update();
}
bool VolumeButton::muted() {
return muted_;
}
bool VolumeButton::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
parent()->setFocused(true);
if (down && button == GLFW_MOUSE_BUTTON_2) {
setMuted(!muted_);
if (cb_) { cb_(value()); }
return true;
}
else {
return PopupButton::mouseButtonEvent(p, button, down, modifiers);
}
}
bool VolumeButton::scrollEvent(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) {
setValue(std::min(std::max(0.0f, value_ + rel[1]*scroll_step_), 1.0f));
if (cb_) { cb_(value()); }
return true;
}