From 33c222555f97229a960ec97e580cdca9d55960fa Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sun, 17 Sep 2023 00:51:34 +0200 Subject: [PATCH] New Playlists and new main panel Favorite and custom playlists of Sessions. Main panel separate control of current session (with preview) and selection of session in playlists. Bugfix in history of files. --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes src/CMakeLists.txt | 1 + src/DialogToolkit.cpp | 272 ++++- src/DialogToolkit.h | 54 +- src/ImGuiToolkit.cpp | 18 +- src/ImGuiToolkit.h | 2 +- src/ImGuiVisitor.cpp | 4 +- src/Playlist.cpp | 162 +++ src/Playlist.h | 40 + src/Settings.cpp | 25 +- src/Settings.h | 18 +- src/SourceControlWindow.cpp | 2 +- src/UserInterfaceManager.cpp | 1925 ++++++++++++++++++++-------------- src/UserInterfaceManager.h | 19 +- src/defines.h | 9 +- 15 files changed, 1697 insertions(+), 854 deletions(-) create mode 100644 src/Playlist.cpp create mode 100644 src/Playlist.h diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index a69be9d07f1bd14e0e6716b06c692659a80f4723..f9aafa8a338d9d453b2de2b49e19b7454a297391 100644 GIT binary patch delta 15463 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πLPkye0UaD})|Q_;;FA4`#{*;{&v2Ni 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 hxAn@}so-8 selectSessionsFileDialog(const std::string &label,const std::string &path); +void DialogToolkit::MultipleSessionsDialog::open() +{ + if ( !busy_ && promisedlist_.empty() ) { + promisedlist_.emplace_back( std::async(std::launch::async, selectSessionsFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + busy_ = true; + } +} + +bool DialogToolkit::MultipleSessionsDialog::closed() +{ + if ( !promisedlist_.empty() ) { + // check that file dialog thread finished + if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) { + // get the filename from this file dialog + std::list list = promisedlist_.back().get(); + if (!list.empty()) { + // selected a filenames + pathlist_ = list; + path_ = list.front(); + // save path location + Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_); + } + else { + pathlist_.clear(); + path_.clear(); + } + // done with this file dialog + promisedlist_.pop_back(); + busy_ = false; + return true; + } + } + return false; +} + + +std::string openPlaylistFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::OpenPlaylistDialog::open() +{ + if ( !busy_ && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, openPlaylistFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + busy_ = true; + } +} + std::string openMediaFileDialog(const std::string &label, const std::string &path); void DialogToolkit::OpenMediaDialog::open() { @@ -165,6 +213,21 @@ void DialogToolkit::SaveSessionDialog::setFolder(std::string path) Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename( path ); } +std::string savePlaylistFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::SavePlaylistDialog::open() +{ + if ( !busy_ && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, savePlaylistFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + busy_ = true; + } +} + +void DialogToolkit::SavePlaylistDialog::setFolder(std::string path) +{ + Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename( path ); +} + std::string openFolderDialog(const std::string &label, const std::string &path); void DialogToolkit::OpenFolderDialog::open() { @@ -227,7 +290,7 @@ std::string saveSessionFileDialog(const std::string &label, const std::string &p #if USE_TINYFILEDIALOG char const * save_file_name; - save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session"); + save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix (MIX)"); if (save_file_name) filename = std::string(save_file_name); @@ -244,7 +307,7 @@ std::string saveSessionFileDialog(const std::string &label, const std::string &p gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); // set file filters - add_filter_file_dialog(dialog, 1, save_pattern, "vimix session"); + add_filter_file_dialog(dialog, 1, save_pattern, "vimix (MIX)"); add_filter_any_file_dialog(dialog); // Set the default path @@ -288,7 +351,7 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p #if USE_TINYFILEDIALOG char const * open_file_name; - open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0); + open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix (MIX)", 0); if (open_file_name) filename = std::string(open_file_name); @@ -304,7 +367,7 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p "_Open", GTK_RESPONSE_ACCEPT, NULL ); // set file filters - add_filter_file_dialog(dialog, 1, open_pattern, "vimix session"); + add_filter_file_dialog(dialog, 1, open_pattern, "vimix (MIX)"); add_filter_any_file_dialog(dialog); // Set the default path @@ -337,6 +400,205 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p } +std::list selectSessionsFileDialog(const std::string &label,const std::string &path) +{ + std::list files; + + std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); + char const * open_pattern[1] = { VIMIX_FILE_PATTERN }; + +#if USE_TINYFILEDIALOG + char const * open_file_names; + open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 1, open_pattern, "vimix (MIX)", 1); + + if (open_file_names) { + + const std::string& str (open_file_names); + const std::string& delimiters = "|"; + // Skip delimiters at beginning. + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); + + // Find first non-delimiter. + std::string::size_type pos = str.find_first_of(delimiters, lastPos); + + while (std::string::npos != pos || std::string::npos != lastPos) { + // Found a token, add it to the vector. + files.push_back(str.substr(lastPos, pos - lastPos)); + + // Skip delimiters. + lastPos = str.find_first_not_of(delimiters, pos); + + // Find next non-delimiter. + pos = str.find_first_of(delimiters, lastPos); + } + } +#else + + if (!gtk_init()) { + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); + return files; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, NULL ); + + // set file filters + add_filter_file_dialog(dialog, 1, open_pattern, "vimix (MIX)"); + add_filter_any_file_dialog(dialog); + + // multiple files + gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true ); + + // Set the default path + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); + // ensure front and centered + gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); + if (window_x > 0 && window_y > 0) + gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); + + // display and get filename + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) ); + while (open_file_names) { + files.push_back( (char *) open_file_names->data ); + open_file_names = open_file_names->next; + } + g_slist_free( open_file_names ); + } + + // remember position + gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); + + // done + gtk_widget_destroy(dialog); + wait_for_event(); +#endif + + return files; +} + + +std::string openPlaylistFileDialog(const std::string &label, const std::string &path) +{ + std::string filename = ""; + std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); + char const * open_pattern[1] = { VIMIX_PLAYLIST_FILE_PATTERN }; + +#if USE_TINYFILEDIALOG + char const * open_file_name; + open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix playlist", 0); + + if (open_file_name) + filename = std::string(open_file_name); +#else + if (!gtk_init()) { + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); + return filename; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, NULL ); + + // set file filters + add_filter_file_dialog(dialog, 1, open_pattern, "vimix playlist"); + add_filter_any_file_dialog(dialog); + + // Set the default path + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); + + // ensure front and centered + gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); + if (window_x > 0 && window_y > 0) + gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); + + // display and get filename + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + + char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + if (open_file_name) { + filename = std::string(open_file_name); + g_free( open_file_name ); + } + } + + // remember position + gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); + + // done + gtk_widget_destroy(dialog); + wait_for_event(); +#endif + + return filename; +} + + +std::string savePlaylistFileDialog(const std::string &label, const std::string &path) +{ + std::string filename = ""; + char const * save_pattern[1] = { VIMIX_PLAYLIST_FILE_PATTERN }; + +#if USE_TINYFILEDIALOG + char const * save_file_name; + + save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix playlist"); + + if (save_file_name) + filename = std::string(save_file_name); +#else + if (!gtk_init()) { + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); + return filename; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, NULL ); + gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); + + // set file filters + add_filter_file_dialog(dialog, 1, save_pattern, "vimix playlist"); + add_filter_any_file_dialog(dialog); + + // Set the default path + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() ); + + // ensure front and centered + gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); + if (window_x > 0 && window_y > 0) + gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); + + // display and get filename + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + + char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + if (save_file_name) { + filename = std::string(save_file_name); + g_free( save_file_name ); + } + } + + // remember position + gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); + + // done + gtk_widget_destroy(dialog); + wait_for_event(); +#endif + + if (!filename.empty() && !SystemToolkit::has_extension(filename, VIMIX_PLAYLIST_FILE_EXT ) ) + filename += std::string(".") + VIMIX_PLAYLIST_FILE_EXT; + + return filename; +} + + + std::string openMediaFileDialog(const std::string &label, const std::string &path) { std::string filename = ""; @@ -511,7 +773,7 @@ std::list selectImagesFileDialog(const std::string &label,const std std::list files; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); - char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif" }; + char const * open_pattern[3] = { "*.jpg", "*.png", "*.tif" }; #if USE_TINYFILEDIALOG char const * open_file_names; diff --git a/src/DialogToolkit.h b/src/DialogToolkit.h index 22b6cc8..9432939 100644 --- a/src/DialogToolkit.h +++ b/src/DialogToolkit.h @@ -30,13 +30,6 @@ public: static bool busy() { return busy_; } }; -class OpenImageDialog : public FileDialog -{ -public: - OpenImageDialog(const std::string &name) : FileDialog(name) {} - void open(); -}; - class OpenSessionDialog : public FileDialog { public: @@ -44,13 +37,6 @@ public: void open(); }; -class OpenMediaDialog : public FileDialog -{ -public: - OpenMediaDialog(const std::string &name) : FileDialog(name) {} - void open(); -}; - class SaveSessionDialog : public FileDialog { public: @@ -59,6 +45,39 @@ public: void open(); }; +class MultipleSessionsDialog : public FileDialog +{ + std::list pathlist_; + std::vector< std::future< std::list > > promisedlist_; +public: + MultipleSessionsDialog(const std::string &name) : FileDialog(name) {} + void open() override; + bool closed() override; + inline std::list files() const { return pathlist_; } +}; + +class OpenPlaylistDialog : public FileDialog +{ +public: + OpenPlaylistDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class SavePlaylistDialog : public FileDialog +{ +public: + SavePlaylistDialog(const std::string &name) : FileDialog(name) {} + void setFolder(std::string path); + void open(); +}; + +class OpenMediaDialog : public FileDialog +{ +public: + OpenMediaDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + class OpenFolderDialog : public FileDialog { public: @@ -66,6 +85,13 @@ public: void open(); }; +class OpenImageDialog : public FileDialog +{ +public: + OpenImageDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + class MultipleImagesDialog : public FileDialog { std::list pathlist_; diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index 7e32083..a425a03 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -100,7 +100,7 @@ void ImGuiToolkit::ButtonDisabled(const char* label, const ImVec2 &size_arg) ImGui::PopStyleColor(1); } -bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* shortcut) +bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* tooltip, bool rightalign) { bool ret = false; @@ -119,7 +119,7 @@ bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* sho float radius = height * 0.50f; // toggle action : operate on the whole area - ImGui::InvisibleButton(label, ImVec2(frame_width - frame_height, frame_height)); + ImGui::InvisibleButton(label, ImVec2(frame_width, frame_height)); if (ImGui::IsItemClicked()) { *toggle = !*toggle; ret = true; @@ -143,19 +143,19 @@ bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* sho col_bg = ImGui::GetColorU32(ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_TabActive], t)); // draw help text if present - if (shortcut) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.9f)); - ImGui::RenderText(draw_pos, shortcut); - ImGui::PopStyleColor(1); - } + if (tooltip != nullptr && ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip(tooltip); + + // right alignment + float alignment = rightalign ? width + g.Style.FramePadding.x : 3.5f * ImGui::GetTextLineHeightWithSpacing(); // draw the label right aligned const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - ImVec2 text_pos = draw_pos + ImVec2(frame_width -3.8f * ImGui::GetTextLineHeightWithSpacing() -label_size.x, 0.f); + ImVec2 text_pos = draw_pos + ImVec2(frame_width -alignment - g.Style.ItemSpacing.x -label_size.x, 0.f); ImGui::RenderText(text_pos, label); // draw switch after the text - ImVec2 p = draw_pos + ImVec2(frame_width -3.5f * ImGui::GetTextLineHeightWithSpacing(), 0.f); + ImVec2 p = draw_pos + ImVec2(frame_width -alignment, 0.f); draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f); draw_list->AddCircleFilled(ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 250)); diff --git a/src/ImGuiToolkit.h b/src/ImGuiToolkit.h index c3c81a5..01fb4dc 100644 --- a/src/ImGuiToolkit.h +++ b/src/ImGuiToolkit.h @@ -34,7 +34,7 @@ namespace ImGuiToolkit // buttons bool ButtonToggle (const char* label, bool* toggle, const char *tooltip = nullptr); - bool ButtonSwitch (const char* label, bool* toggle, const char *shortcut = nullptr); + bool ButtonSwitch (const char* label, bool* toggle, const char *tooltip = nullptr, bool rightalign = false); void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0)); void ButtonDisabled(const char* label, const ImVec2& size_arg = ImVec2(0,0)); bool TextButton (const char* text, const char *tooltip = nullptr, const char *shortcut = nullptr); diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index 8b29d1f..71e7b04 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -526,8 +526,8 @@ void ImGuiVisitor::visit (Source& s) ImGuiToolkit::Indication(workspaces_[s.workspace()].second.c_str(), workspaces_[s.workspace()].first, 16); // locking - ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 3.f * ImGui::GetFrameHeightWithSpacing()) ); - const char *tooltip[2] = {"Unlocked", "Locked"}; + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + preview_height - ImGui::GetFrameHeightWithSpacing()) ); + static const char *tooltip[2] = {"Unlocked", "Locked"}; bool l = s.locked(); if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) { s.setLocked(l); diff --git a/src/Playlist.cpp b/src/Playlist.cpp new file mode 100644 index 0000000..d1ddfc2 --- /dev/null +++ b/src/Playlist.cpp @@ -0,0 +1,162 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +**/ + +#include + +#include +#include "tinyxml2Toolkit.h" +using namespace tinyxml2; + +#include "Playlist.h" + +Playlist::Playlist() : current_index_(UINTMAX_MAX) +{ + +} + +Playlist& Playlist::operator = (const Playlist& b) +{ + if (this != &b) { + path_.clear(); + for(auto it = b.path_.cbegin(); it != b.path_.cend(); ++it) + path_.push_back(*it); + } + return *this; +} + +void Playlist::clear() +{ + path_.clear(); +} + +void Playlist::load(const std::string &filename) +{ + // try to load playlist file + XMLDocument xmlDoc; + XMLError eResult = xmlDoc.LoadFile(filename.c_str()); + + // do not warn if non existing file + if (eResult == XML_ERROR_FILE_NOT_FOUND) + return; + // warn and return on other error + else if (XMLResultError(eResult)) + return; + + // first element should be called by the application name + XMLElement *pRoot = xmlDoc.FirstChildElement("vimixplaylist"); + if (pRoot == nullptr) + return; + + // all good, can clear previous list + filename_ = filename; + path_.clear(); + + // Then it should contain a list of path + XMLElement* pathNode = pRoot->FirstChildElement("path"); + for( ; pathNode ; pathNode = pathNode->NextSiblingElement()) { + const char *p = pathNode->GetText(); + if (p) + add( std::string(p) ); + } +} + +bool Playlist::save() +{ + if ( filename_.empty() ) + return false; + + saveAs(filename_); + return true; +} + +void Playlist::saveAs(const std::string &filename) +{ + XMLDocument xmlDoc; + XMLDeclaration *pDec = xmlDoc.NewDeclaration(); + xmlDoc.InsertFirstChild(pDec); + + XMLElement *pRoot = xmlDoc.NewElement("vimixplaylist"); + xmlDoc.InsertEndChild(pRoot); + + for(auto it = path_.cbegin(); it != path_.cend(); ++it) { + XMLElement *pathNode = xmlDoc.NewElement("path"); + XMLText *text = xmlDoc.NewText( it->c_str() ); + pathNode->InsertEndChild( text ); + pRoot->InsertEndChild(pathNode); + } + + XMLError eResult = xmlDoc.SaveFile(filename.c_str()); + if ( !XMLResultError(eResult)) + filename_ = filename; +} + +bool Playlist::add(const std::string &path) +{ + if (has(path)) + return false; + path_.push_back(path); + return true; +} + +size_t Playlist::add(const std::list &list) +{ + size_t before = path_.size(); + for (auto it = list.begin(); it != list.end(); ++it) { + if (!has( *it )) + path_.push_back( *it ); + } + return path_.size()-before; +} + +void Playlist::remove(const std::string &path) +{ + std::deque::const_iterator it = std::find(path_.begin(), path_.end(), path); + + if (it != path_.end()) + path_.erase(it); +} + +bool Playlist::has(const std::string &path) const +{ + std::deque::const_iterator it = std::find(path_.begin(), path_.end(), path); + + return (it != path_.end()); +} + +void Playlist::remove(size_t index) +{ + if ( index < path_.size() ) { + path_.erase (path_.begin()+index); + } +} + +void Playlist::move(size_t from_index, size_t to_index) +{ + if ( from_index < path_.size() && to_index < path_.size() && from_index != to_index ) { + if ( from_index < to_index ) { + path_.insert(path_.begin() + to_index + 1, path_[from_index]); + path_.erase(path_.begin() + from_index); + } + else { + path_.insert(path_.begin() + to_index, path_[from_index]); + path_.erase(path_.begin() + from_index + 1); + } + } +} + diff --git a/src/Playlist.h b/src/Playlist.h new file mode 100644 index 0000000..2bc9a33 --- /dev/null +++ b/src/Playlist.h @@ -0,0 +1,40 @@ +#ifndef PLAYLIST_H +#define PLAYLIST_H + +#include +#include +#include + +class Playlist +{ +public: + Playlist(); + Playlist& operator = (const Playlist& b); + + // load / save XML + void clear (); + void load (const std::string &filename); + void saveAs (const std::string &filename); + bool save (); + + // add / remove / test by path + bool add (const std::string &path); + size_t add (const std::list &list); + void remove (const std::string &path); + bool has (const std::string &path) const; + + // access by index, from 0 to size() + inline size_t size () const { return path_.size(); } + inline std::string at (size_t index) const { return path_.at(index); } + void remove (size_t index); + void move (size_t from_index, size_t to_index); + inline size_t current() const { return current_index_; } + +private: + + std::deque< std::string > path_; + std::string filename_; + size_t current_index_; +}; + +#endif // PLAYLIST_H diff --git a/src/Settings.cpp b/src/Settings.cpp index 5a0cc60..3840ead 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -41,6 +41,7 @@ XMLElement *save_history(Settings::History &h, const char *nodename, XMLDocument pElement->SetAttribute("autoload", h.load_at_start); pElement->SetAttribute("autosave", h.save_on_exit); pElement->SetAttribute("valid", h.front_is_valid); + pElement->SetAttribute("ordering", h.ordering); for(auto it = h.filenames.cbegin(); it != h.filenames.cend(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); @@ -135,6 +136,8 @@ void Settings::Save(uint64_t runtime) applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view); applicationNode->SetAttribute("show_tooptips", application.show_tooptips); applicationNode->SetAttribute("accept_connections", application.accept_connections); + applicationNode->SetAttribute("pannel_main_mode", application.pannel_main_mode); + applicationNode->SetAttribute("pannel_playlist_mode", application.pannel_playlist_mode); applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode); applicationNode->SetAttribute("pannel_always_visible", application.pannel_always_visible); applicationNode->SetAttribute("stream_protocol", application.stream_protocol); @@ -261,6 +264,9 @@ void Settings::Save(uint64_t runtime) // recent session filenames recent->InsertEndChild( save_history(application.recentSessions, "Session", xmlDoc)); + // recent session folders + recent->InsertEndChild( save_history(application.recentPlaylists, "Playlist", xmlDoc)); + // recent session folders recent->InsertEndChild( save_history(application.recentFolders, "Folder", xmlDoc)); @@ -353,6 +359,9 @@ void load_history(Settings::History &h, const char *nodename, XMLElement *root) pElement->QueryBoolAttribute("autoload", &h.load_at_start); pElement->QueryBoolAttribute("autosave", &h.save_on_exit); pElement->QueryBoolAttribute("valid", &h.front_is_valid); + pElement->QueryIntAttribute("ordering", &h.ordering); + + h.changed = true; } } @@ -403,11 +412,11 @@ void Settings::Load() else if (XMLResultError(eResult)) return; + // first element should be called by the application name XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str()); if (pRoot == nullptr) return; - // runtime pRoot->QueryUnsigned64Attribute("runtime", &application.total_runtime); @@ -422,6 +431,8 @@ void Settings::Load() applicationNode->QueryBoolAttribute("show_tooptips", &application.show_tooptips); applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections); applicationNode->QueryBoolAttribute("pannel_always_visible", &application.pannel_always_visible); + applicationNode->QueryIntAttribute("pannel_main_mode", &application.pannel_main_mode); + applicationNode->QueryIntAttribute("pannel_playlist_mode", &application.pannel_playlist_mode); applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode); applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol); applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port); @@ -618,6 +629,9 @@ void Settings::Load() // recent session filenames load_history(application.recentSessions, "Session", pElement); + // recent session playlist + load_history(application.recentPlaylists, "Playlist", pElement); + // recent session folders load_history(application.recentFolders, "Folder", pElement); @@ -686,6 +700,12 @@ void Settings::Load() } +void Settings::History::assign(const string &filename) +{ + path.assign(filename); + changed = true; +} + void Settings::History::push(const string &filename) { if (filename.empty()) { @@ -718,6 +738,9 @@ void Settings::History::validate() else fit = filenames.erase(fit); } + if (!path.empty() && !SystemToolkit::file_exists( path )) { + path = ""; + } } void Settings::KnownHosts::push(const string &ip, const string &port) diff --git a/src/Settings.h b/src/Settings.h index 87aefc9..f60f4bc 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -129,16 +129,19 @@ struct History bool load_at_start; bool save_on_exit; bool changed; + int ordering; History() { - path = IMGUI_LABEL_RECENT_FILES; + path = ""; front_is_valid = false; load_at_start = true; save_on_exit = true; changed = false; + ordering = 3; } - void push(const std::string &filename); - void remove(const std::string &filename); + void push (const std::string &filename); + void remove (const std::string &filename); + void assign (const std::string &filename); void validate(); }; @@ -278,6 +281,8 @@ struct Application bool action_history_follow_view; bool show_tooptips; + int pannel_main_mode; + int pannel_playlist_mode; int pannel_current_session_mode; bool pannel_always_visible; @@ -321,12 +326,11 @@ struct Application std::vector windows; // recent files histories - int orderingSessions; History recentSessions; + History recentPlaylists; History recentFolders; History recentImport; History recentImportFolders; - int orderingImportFolder; History recentRecordings; std::map< std::string, std::string > dialogRecentFolder; @@ -355,6 +359,8 @@ struct Application loopback_camera = 0; shm_method = 0; shm_socket_path = ""; + pannel_main_mode = 0; + pannel_playlist_mode = 0; pannel_current_session_mode = 0; current_view = 1; current_workspace= 3; @@ -363,8 +369,6 @@ struct Application windows = std::vector(1+MAX_OUTPUT_WINDOW); windows[0].w = 1600; windows[0].h = 900; - orderingSessions = 3; - orderingImportFolder = 3; } }; diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 36dd45e..90428ce 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -912,7 +912,7 @@ void SourceControlWindow::RenderSelection(size_t i) ImGui::SameLine(); ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f); - if (ImGui::Button(ICON_FA_MINUS_CIRCLE)) { + if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) { resetActiveSelection(); Mixer::manager().session()->deleteBatch(i); } diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 82a1133..0b1d8e0 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -77,6 +77,7 @@ #include "VideoBroadcast.h" #include "MultiFileRecorder.h" #include "MousePointer.h" +#include "Playlist.h" #include "UserInterfaceManager.h" @@ -201,6 +202,14 @@ bool UserInterface::Init() std::snprintf(inifilepath, 2048, "%s", inifile.c_str() ); io.IniFilename = inifilepath; + // load favorites + favorites.load( SystemToolkit::full_filename(SystemToolkit::settings_path(), "favorites.lix") ); + playlists_path = SystemToolkit::full_filename(SystemToolkit::settings_path(), "playlists"); + if ( !SystemToolkit::file_exists(playlists_path)) { + if ( !SystemToolkit::create_directory(playlists_path) ) + playlists_path = SystemToolkit::home_path(); + } + // init dialogs sessionopendialog = new DialogToolkit::OpenSessionDialog("Open Session"); sessionsavedialog = new DialogToolkit::SaveSessionDialog("Save Session"); @@ -957,6 +966,9 @@ void UserInterface::Render() void UserInterface::Terminate() { + // save favorites + favorites.save(); + // restore windows position for saving WorkspaceWindow::restoreWorkspace(true); @@ -1018,6 +1030,33 @@ void UserInterface::showMenuEdit() } } +void UserInterface::showMenuWindows() +{ + if ( ImGui::MenuItem( MENU_OUTPUT, SHORTCUT_OUTPUT, &Settings::application.widget.preview) ) + UserInterface::manager().outputcontrol.setVisible(Settings::application.widget.preview); + + if ( ImGui::MenuItem( MENU_PLAYER, SHORTCUT_PLAYER, &Settings::application.widget.media_player) ) + UserInterface::manager().sourcecontrol.setVisible(Settings::application.widget.media_player); + + if ( ImGui::MenuItem( MENU_TIMER, SHORTCUT_TIMER, &Settings::application.widget.timer) ) + UserInterface::manager().timercontrol.setVisible(Settings::application.widget.timer); + + if ( ImGui::MenuItem( MENU_INPUTS, SHORTCUT_INPUTS, &Settings::application.widget.inputs) ) + UserInterface::manager().inputscontrol.setVisible(Settings::application.widget.inputs); + + ImGui::Separator(); + + // Show Help + ImGui::MenuItem( MENU_HELP, SHORTCUT_HELP, &Settings::application.widget.help ); + // Show Logs + ImGui::MenuItem( MENU_LOGS, SHORTCUT_LOGS, &Settings::application.widget.logs ); + // Enable / disable source toolbar + ImGui::MenuItem( MENU_SOURCE_TOOL, NULL, &Settings::application.widget.source_toolbar ); + // Enable / disable metrics toolbar + ImGui::MenuItem( MENU_METRICS, NULL, &Settings::application.widget.stats ); + +} + void UserInterface::showMenuFile() { // NEW @@ -2409,9 +2448,6 @@ void UserInterface::RenderHelp() ImGui::Text(ICON_FA_TACHOMETER_ALT " Metrics"); ImGui::NextColumn(); ImGui::Text ("Monitoring of metrics on the system (e.g. FPS, RAM) and runtime (e.g. session duration)."); ImGui::NextColumn(); - ImGui::Text(ICON_FA_STICKY_NOTE " Sticky note"); ImGui::NextColumn(); - ImGui::Text ("Place sticky notes into your session. Does nothing, just keeps notes and reminders."); - ImGui::NextColumn(); ImGui::Text(IMGUI_TITLE_LOGS); ImGui::NextColumn(); ImGui::Text ("History of program logs, with information on success and failure of commands."); ImGui::NextColumn(); @@ -2663,7 +2699,7 @@ Navigator::Navigator() padding_width_ = 100; // clean start - show_config_ = false; + pannel_main_mode_ = Settings::application.pannel_main_mode; pannel_visible_ = false; pannel_alpha_ = 0.85f; view_pannel_visible = false; @@ -2692,7 +2728,7 @@ void Navigator::applyButtonSelection(int index) // set visible if button is active pannel_visible_ = status; - show_config_ = false; + pannel_main_mode_ = Settings::application.pannel_main_mode; } void Navigator::clearNewPannel() @@ -2740,7 +2776,7 @@ void Navigator::showConfig() { selected_button[NAV_MENU] = true; applyButtonSelection(NAV_MENU); - show_config_ = true; + pannel_main_mode_ = 2; } void Navigator::togglePannelMenu() @@ -2834,7 +2870,7 @@ void Navigator::discardPannel() pannel_visible_ = false; view_pannel_visible = false; - show_config_ = false; + pannel_main_mode_ = Settings::application.pannel_main_mode; } void Navigator::Render() @@ -2874,9 +2910,12 @@ void Navigator::Render() if (Settings::application.current_view != View::TRANSITION) { - // the "=" icon for menu - if (ImGui::Selectable( ICON_FA_BARS, &selected_button[NAV_MENU], 0, iconsize)) + // the vimix icon for menu + if (ImGuiToolkit::SelectableIcon(2, 16, "", selected_button[NAV_MENU], iconsize)) { + selected_button[NAV_MENU] = true; +// if (ImGui::Selectable( ICON_FA_BARS, &selected_button[NAV_MENU], 0, iconsize)) { applyButtonSelection(NAV_MENU); + } if (ImGui::IsItemHovered()) tooltip = {TOOLTIP_MAIN, SHORTCUT_MAIN}; @@ -3105,17 +3144,17 @@ void Navigator::Render() // pannel menu if (selected_button[NAV_MENU]) { - RenderMainPannel(); + RenderMainPannel(iconsize); } // pannel to manage transition else if (selected_button[NAV_TRANS]) { - RenderTransitionPannel(); + RenderTransitionPannel(iconsize); } // pannel to create a source else if (selected_button[NAV_NEW]) { - RenderNewPannel(); + RenderNewPannel(iconsize); } // pannel to configure a selected source else @@ -3124,12 +3163,12 @@ void Navigator::Render() showPannelSource(NAV_MENU); // most often, render current sources else if ( selected_index == Mixer::manager().indexCurrentSource()) - RenderSourcePannel(Mixer::manager().currentSource()); + RenderSourcePannel(Mixer::manager().currentSource(), iconsize); // rarely its not the current source that is selected else { SourceList::iterator cs = Mixer::manager().session()->at( selected_index ); if (cs != Mixer::manager().session()->end() ) - RenderSourcePannel( *cs ); + RenderSourcePannel(*cs, iconsize); } } } @@ -3174,30 +3213,32 @@ void Navigator::RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 } // Source pannel : *s was checked before -void Navigator::RenderSourcePannel(Source *s) +void Navigator::RenderSourcePannel(Source *s, const ImVec2 &iconsize) { if (s == nullptr || Settings::application.current_view == View::TRANSITION) return; // Next window is a side pannel + const ImGuiStyle& style = ImGui::GetStyle(); ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always ); ImGui::SetNextWindowSize( ImVec2(pannel_width_, height_), ImGuiCond_Always ); ImGui::SetNextWindowBgAlpha( pannel_alpha_ ); // Transparent background if (ImGui::Begin("##navigatorSource", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { // TITLE - ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::SetCursorPosY(0.5f * (iconsize.y - ImGui::GetTextLineHeight())); ImGui::Text("Source"); - ImGui::PopFont(); // index indicator - ImGui::SetCursorPos(ImVec2(pannel_width_ - 2 * ImGui::GetTextLineHeight(), 15.f)); + ImGui::SetCursorPos(ImVec2(pannel_width_ - 2 * ImGui::GetTextLineHeight(), IMGUI_TOP_ALIGN)); ImGui::TextDisabled("#%d", Mixer::manager().indexCurrentSource()); + ImGui::PopFont(); + // name std::string sname = s->name(); - ImGui::SetCursorPosY(width_); + ImGui::SetCursorPosY(width_ - style.WindowPadding.x); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); if (ImGuiToolkit::InputText("Name", &sname) ){ Mixer::manager().renameSource(s, sname); @@ -3297,14 +3338,12 @@ void Navigator::setNewMedia(MediaCreateMode mode, std::string path) new_source_preview_.setSource(); } -void Navigator::RenderNewPannel() +void Navigator::RenderNewPannel(const ImVec2 &iconsize) { if (Settings::application.current_view == View::TRANSITION) return; const ImGuiStyle& style = ImGui::GetStyle(); - const float icon_width = width_ - 2.f * style.WindowPadding.x; - const ImVec2 iconsize(icon_width, icon_width); // Next window is a side pannel ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always ); @@ -3313,8 +3352,8 @@ void Navigator::RenderNewPannel() if (ImGui::Begin("##navigatorNewSource", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { // TITLE - ImGui::SetCursorPosY(10); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::SetCursorPosY(0.5f * (iconsize.y - ImGui::GetTextLineHeight())); if (source_to_replace != nullptr) ImGui::Text("Replace"); else @@ -3355,7 +3394,7 @@ void Navigator::RenderNewPannel() ImGui::PopFont(); // Edit menu - ImGui::SetCursorPosY(width_ * 1.9f); + ImGui::SetCursorPosY(2.f * width_ - style.WindowPadding.x); // File Source creation if (Settings::application.source.new_type == SOURCE_FILE) { @@ -3417,7 +3456,7 @@ void Navigator::RenderNewPannel() } } // Add a folder for MEDIA_FOLDER - if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ) { + if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " List directory") ) { folderimportdialog.open(); } ImGui::EndCombo(); @@ -3429,36 +3468,8 @@ void Navigator::RenderNewPannel() setNewMedia(MEDIA_FOLDER, folderimportdialog.path()); } - // icons to clear lists or discarc folder + // position on top of list ImVec2 pos_top = ImGui::GetCursorPos(); - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - if ( new_media_mode == MEDIA_FOLDER ) { - if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_MINUS, "Discard folder")) { - Settings::application.recentImportFolders.filenames.remove(Settings::application.recentImportFolders.path); - if (Settings::application.recentImportFolders.filenames.empty()) - // revert mode RECENT - setNewMedia(MEDIA_RECENT); - else - setNewMedia(MEDIA_FOLDER, Settings::application.recentImportFolders.filenames.front()); - } - } - else if ( new_media_mode == MEDIA_RECORDING ) { - if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) { - Settings::application.recentRecordings.filenames.clear(); - Settings::application.recentRecordings.front_is_valid = false; - setNewMedia(MEDIA_RECORDING); - } - } - else if ( new_media_mode == MEDIA_RECENT ) { - if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) { - Settings::application.recentImport.filenames.clear(); - Settings::application.recentImport.front_is_valid = false; - setNewMedia(MEDIA_RECENT); - } - } - ImGui::PopStyleVar(); - ImGui::SetCursorPos(pos_top); // change session list if changed if (new_media_mode_changed || Settings::application.recentImport.changed || Settings::application.recentRecordings.changed) { @@ -3491,7 +3502,7 @@ void Navigator::RenderNewPannel() else if ( new_media_mode == MEDIA_FOLDER) { // show list of media files in folder sourceMediaFiles = SystemToolkit::list_directory( Settings::application.recentImportFolders.path, { MEDIA_FILES_PATTERN }, - (SystemToolkit::Ordering) Settings::application.orderingImportFolder); + (SystemToolkit::Ordering) Settings::application.recentImportFolders.ordering); } // indicate the list changed (do not change at every frame) new_media_mode_changed = false; @@ -3537,8 +3548,15 @@ void Navigator::RenderNewPannel() // Supplementary icons to manage the list ImVec2 pos_bot = ImGui::GetCursorPos(); - // Bottom Right side of the list: helper and options of Recent Recordings if (new_media_mode == MEDIA_RECORDING) { + // Clear list + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y) ); + if (ImGuiToolkit::IconButton( 12, 14, "Clear list")) { + Settings::application.recentRecordings.filenames.clear(); + Settings::application.recentRecordings.front_is_valid = false; + setNewMedia(MEDIA_RECORDING); + } + // Bottom Right side of the list: helper and options of Recent Recordings ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); ImGuiToolkit::HelpToolTip("Recently recorded videos (lastest on top). Clic on a filename to open.\n\n" ICON_FA_CHEVRON_CIRCLE_RIGHT " Auto-preload prepares this panel with the " @@ -3554,15 +3572,33 @@ void Navigator::RenderNewPannel() } } } - // Top right of Media folder list else if (new_media_mode == MEDIA_FOLDER) { - // ordering list + // close list ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y) ); + if (ImGuiToolkit::IconButton( 4, 5, "Close directory")) { + Settings::application.recentImportFolders.filenames.remove(Settings::application.recentImportFolders.path); + if (Settings::application.recentImportFolders.filenames.empty()) + // revert mode RECENT + setNewMedia(MEDIA_RECENT); + else + setNewMedia(MEDIA_FOLDER, Settings::application.recentImportFolders.filenames.front()); + } + // ordering list + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetFrameHeightWithSpacing()) ); ImGui::PushID("##new_media_mode_changed"); - if ( ImGuiToolkit::IconMultistate(icons_ordering_files, &Settings::application.orderingImportFolder, tooltips_ordering_files) ) + if ( ImGuiToolkit::IconMultistate(icons_ordering_files, &Settings::application.recentImportFolders.ordering, tooltips_ordering_files) ) new_media_mode_changed = true; ImGui::PopID(); } + else if ( new_media_mode == MEDIA_RECENT ) { + // Clear list + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y) ); + if (ImGuiToolkit::IconButton( 12, 14, "Clear list")) { + Settings::application.recentImport.filenames.clear(); + Settings::application.recentImport.front_is_valid = false; + setNewMedia(MEDIA_RECENT); + } + } // come back... ImGui::SetCursorPos(pos_bot); @@ -4014,723 +4050,6 @@ void Navigator::RenderNewPannel() } } -void Navigator::RenderMainPannelVimix() -{ - // TITLE - ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text(APP_NAME); - ImGui::PopFont(); - - // MENU - ImGui::SameLine(); - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN) ); - if (ImGui::BeginMenu("File")) - { - UserInterface::manager().showMenuFile(); - ImGui::EndMenu(); - } - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN + ImGui::GetTextLineHeightWithSpacing()) ); - if (ImGui::BeginMenu("Edit")) - { - UserInterface::manager().showMenuEdit(); - ImGui::EndMenu(); - } - - ImGui::SetCursorPosY(width_); - - // - // SESSION panel - // - ImGui::Text("Sessions"); - static bool selection_session_mode_changed = true; - static int selection_session_mode = (Settings::application.recentFolders.path == IMGUI_LABEL_RECENT_FILES) ? 0 : 1; - static DialogToolkit::OpenFolderDialog customFolder("Open Folder"); - - // Show combo box of quick selection modes - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##SelectionSession", BaseToolkit::truncated(Settings::application.recentFolders.path, 25).c_str() )) { - - // Mode 0 : recent files - if (ImGui::Selectable( ICON_FA_LIST_OL IMGUI_LABEL_RECENT_FILES) ) { - Settings::application.recentFolders.path = IMGUI_LABEL_RECENT_FILES; - selection_session_mode = 0; - selection_session_mode_changed = true; - } - // Mode 1 : known folders - for(auto foldername = Settings::application.recentFolders.filenames.begin(); - foldername != Settings::application.recentFolders.filenames.end(); foldername++) { - std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated( *foldername, 40); - if (ImGui::Selectable( f.c_str() )) { - // remember which path was selected - Settings::application.recentFolders.path.assign(*foldername); - // set mode - selection_session_mode = 1; - selection_session_mode_changed = true; - } - } - // Add a folder - if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Open Folder") ) - customFolder.open(); - ImGui::EndCombo(); - } - - // return from thread for folder openning - if (customFolder.closed() && !customFolder.path().empty()) { - Settings::application.recentFolders.push(customFolder.path()); - Settings::application.recentFolders.path.assign(customFolder.path()); - selection_session_mode = 1; - selection_session_mode_changed = true; - } - - // icon to clear list - ImVec2 pos_top = ImGui::GetCursorPos(); - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - if ( selection_session_mode == 1) { - if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_MINUS, "Discard folder")) { - Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path); - if (Settings::application.recentFolders.filenames.empty()) { - Settings::application.recentFolders.path.assign(IMGUI_LABEL_RECENT_FILES); - selection_session_mode = 0; - } - else - Settings::application.recentFolders.path = Settings::application.recentFolders.filenames.front(); - // reload the list next time - selection_session_mode_changed = true; - } - } - else { - if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list of recent files")) { - Settings::application.recentSessions.filenames.clear(); - Settings::application.recentSessions.front_is_valid = false; - // reload the list next time - selection_session_mode_changed = true; - } - } - ImGui::PopStyleVar(); - ImGui::SetCursorPos(pos_top); - - // fill the session list depending on the mode - static std::list sessions_list; - static std::list::iterator _file_over = sessions_list.end(); - static std::list::iterator _displayed_over = sessions_list.end(); - - // change session list if changed - if (selection_session_mode_changed || Settings::application.recentSessions.changed || Settings::application.recentFolders.changed) { - - // selection MODE 0 ; RECENT sessions - if ( selection_session_mode == 0) { - // show list of recent sessions - Settings::application.recentSessions.validate(); - sessions_list = Settings::application.recentSessions.filenames; - SystemToolkit::reorder_file_list( sessions_list, (SystemToolkit::Ordering) Settings::application.orderingSessions); - } - // selection MODE 1 : LIST FOLDER - else if ( selection_session_mode == 1) { - // show list of vimix files in folder - sessions_list = SystemToolkit::list_directory( Settings::application.recentFolders.path, { VIMIX_FILE_PATTERN }, - (SystemToolkit::Ordering) Settings::application.orderingSessions); - } - - // indicate the list changed (do not change at every frame) - Settings::application.recentSessions.changed = false; - Settings::application.recentFolders.changed = false; - selection_session_mode_changed = false; - _file_over = sessions_list.end(); - _displayed_over = sessions_list.end(); - } - - { - static uint _tooltip = 0; - ++_tooltip; - - // display the sessions list and detect if one was selected (double clic) - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::ListBoxHeader("##Sessions", sessions_list.size(), CLAMP(sessions_list.size(), 4, 8)) ) { - - bool done = false; - int count_over = 0; - ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); - - for(auto it = sessions_list.begin(); it != sessions_list.end(); ++it) { - - if (it->empty()) - continue; - - std::string shortname = SystemToolkit::filename(*it); - if (ImGui::Selectable( shortname.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, size )) { - // open on double clic - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) /*|| file_selected == it*/) { - Mixer::manager().open( *it, Settings::application.smooth_transition ); - if (Settings::application.smooth_transition) - WorkspaceWindow::clearWorkspace(); - done = true; - } - else - // show tooltip on clic - _tooltip = 100; - - } - if (ImGui::IsItemHovered()) - _file_over = it; - - if (_tooltip > 60 && _file_over != sessions_list.end() && count_over < 1) { - - static std::string _file_info = ""; - static Thumbnail _file_thumbnail; - static bool with_tag_ = false; - - // load info only if changed from the one already displayed - if (_displayed_over != _file_over) { - _displayed_over = _file_over; - SessionInformation info = SessionCreator::info(*_displayed_over); - _file_info = info.description; - if (info.thumbnail) { - // set image content to thumbnail display - _file_thumbnail.fill( info.thumbnail ); - with_tag_ = info.user_thumbnail_; - delete info.thumbnail; - } else - _file_thumbnail.reset(); - } - - if ( !_file_info.empty()) { - - ImGui::BeginTooltip(); - ImVec2 p_ = ImGui::GetCursorScreenPos(); - _file_thumbnail.Render(size.x); - ImGui::Text("%s", _file_info.c_str()); - if (with_tag_) { - ImGui::SetCursorScreenPos(p_ + ImVec2(6, 6)); - ImGui::Text(ICON_FA_TAG); - } - ImGui::EndTooltip(); - } - else - selection_session_mode_changed = true; - - ++count_over; // prevents display twice on item overlap - } - } - ImGui::ListBoxFooter(); - - // done the selection ! - if (done) { - discardPannel(); - _tooltip = 0; - _displayed_over = _file_over = sessions_list.end(); - // reload the list next time - selection_session_mode_changed = true; - } - } - // cancel tooltip and mouse over on mouse exit - if ( !ImGui::IsItemHovered()) { - _tooltip = 0; - _displayed_over = _file_over = sessions_list.end(); - } - } - - ImVec2 pos_bot = ImGui::GetCursorPos(); - - // Right side of the list: helper and options - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y)); - if ( ImGuiToolkit::IconButton( ICON_FA_FILE " +" )) { - Mixer::manager().close(Settings::application.smooth_transition ); - if (Settings::application.smooth_transition) - WorkspaceWindow::clearWorkspace(); - discardPannel(); - } - if (ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip("New session", SHORTCUT_NEW_FILE); - - // ordering list - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetFrameHeightWithSpacing()) ); - ImGui::PushID("##selection_session_mode_changed"); - if ( ImGuiToolkit::IconMultistate(icons_ordering_files, &Settings::application.orderingSessions, tooltips_ordering_files) ) - selection_session_mode_changed = true; - ImGui::PopID(); - - // help indicator - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); - ImGuiToolkit::HelpToolTip("Here are listed either the recent files or all the sessions files (*.mix) in a selected folder.\n\n" - "Double-clic on a filename to open the session.\n\n" - ICON_FA_ARROW_CIRCLE_RIGHT " Smooth transition " - "performs cross fading to the opened session."); - // toggle button for smooth transition - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); - ImGuiToolkit::ButtonToggle(ICON_FA_ARROW_CIRCLE_RIGHT, &Settings::application.smooth_transition, "Smooth transition"); - // come back... - ImGui::SetCursorPos(pos_bot); - - // - // Status - // - ImGuiToolkit::Spacing(); - ImGui::Text("Current session"); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("##Selectpanelsession", &Settings::application.pannel_current_session_mode, - ICON_FA_CODE_BRANCH " Versions\0" ICON_FA_HISTORY " Undo history\0" ICON_FA_FILE_ALT " Properties\0"); - pos_bot = ImGui::GetCursorPos(); - - // - // Current 2. PROPERTIES - // - if (Settings::application.pannel_current_session_mode > 1) { - - std::string sessionfilename = Mixer::manager().session()->filename(); - - // Information and resolution - const FrameBuffer *output = Mixer::manager().session()->frame(); - if (output) - { - // Show info text bloc (dark background) - ImGuiTextBuffer info; - if (sessionfilename.empty()) - info.appendf(""); - else - info.appendf("%s", SystemToolkit::filename(sessionfilename).c_str()); - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::InputText("##Info", (char *)info.c_str(), info.size(), ImGuiInputTextFlags_ReadOnly); - ImGui::PopStyleColor(1); - - // change resolution (height only) - // get parameters to edit resolution - glm::ivec2 preset = RenderView::presetFromResolution(output->resolution()); - glm::ivec2 custom = glm::ivec2(output->resolution()); - if (preset.x > -1) { - // cannot change resolution when recording - if ( UserInterface::manager().outputcontrol.isRecording() ) { - // show static info (same size than combo) - static char dummy_str[512]; - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - snprintf(dummy_str, 512, "%s", RenderView::ratio_preset_name[preset.x]); - ImGui::InputText("Ratio", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); - if (preset.x < RenderView::AspectRatio_Custom) { - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - snprintf(dummy_str, 512, "%s", RenderView::height_preset_name[preset.y]); - ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); - } else { - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - snprintf(dummy_str, 512, "%d", custom.x); - ImGui::InputText("Width", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - snprintf(dummy_str, 512, "%d", custom.y); - ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); - } - ImGui::PopStyleColor(1); - } - // offer to change filename, ratio and resolution - else { - ImVec2 draw_pos = ImGui::GetCursorScreenPos(); - ImGui::SameLine(); - if ( ImGuiToolkit::IconButton(ICON_FA_FILE_DOWNLOAD, "Save as" )) { - UserInterface::manager().selectSaveFilename(); - } - if (!sessionfilename.empty()) { - - ImGui::SameLine(); - if (ImGuiToolkit::IconButton(ICON_FA_FOLDER_OPEN, "Show in finder")) - SystemToolkit::open(SystemToolkit::path_filename(sessionfilename)); - } - ImGui::SetCursorScreenPos(draw_pos); - // combo boxes to select aspect rario - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::Combo("Ratio", &preset.x, RenderView::ratio_preset_name, IM_ARRAYSIZE(RenderView::ratio_preset_name) ) ) - { - // change to custom aspect ratio: propose 1:1 - glm::vec3 res = glm::vec3(custom.y, custom.y, 0.f); - // else, change to preset aspect ratio - if (preset.x < RenderView::AspectRatio_Custom) - res = RenderView::resolutionFromPreset(preset.x, preset.y); - // change resolution - Mixer::manager().setResolution(res); - } - // - preset aspect ratio : propose preset height - if (preset.x < RenderView::AspectRatio_Custom) { - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::Combo("Height", &preset.y, RenderView::height_preset_name, IM_ARRAYSIZE(RenderView::height_preset_name) ) ) - { - glm::vec3 res = RenderView::resolutionFromPreset(preset.x, preset.y); - Mixer::manager().setResolution(res); - } - } - // - custom aspect ratio : input width and height - else { - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::InputInt("Width", &custom.x, 100, 500); - if (ImGui::IsItemDeactivatedAfterEdit()) - Mixer::manager().setResolution( glm::vec3(custom, 0.f)); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::InputInt("Height", &custom.y, 100, 500); - if (ImGui::IsItemDeactivatedAfterEdit()) - Mixer::manager().setResolution( glm::vec3(custom, 0.f)); - - } - } - } - } - - // the session file exists - if (!sessionfilename.empty()) - { - // Thumbnail - static Thumbnail _file_thumbnail; - static FrameBufferImage *thumbnail = nullptr; - if ( ImGui::Button( ICON_FA_TAG " Set thumbnail", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) { - Mixer::manager().session()->setThumbnail(); - thumbnail = nullptr; - } - pos_bot = ImGui::GetCursorPos(); - if (ImGui::IsItemHovered()){ - // thumbnail changed - if (thumbnail != Mixer::manager().session()->thumbnail()) { - _file_thumbnail.reset(); - thumbnail = Mixer::manager().session()->thumbnail(); - if (thumbnail != nullptr) - _file_thumbnail.fill( thumbnail ); - } - if (_file_thumbnail.filled()) { - ImGui::BeginTooltip(); - _file_thumbnail.Render(230); - ImGui::Text("Thumbnail used in the\nlist of Sessions above."); - ImGui::EndTooltip(); - } - } - if (Mixer::manager().session()->thumbnail()) { - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - ImGui::SameLine(); - if (ImGuiToolkit::IconButton(ICON_FA_BACKSPACE, "Clear thumbnail")) { - Mixer::manager().session()->resetThumbnail(); - _file_thumbnail.reset(); - thumbnail = nullptr; - } - ImGui::PopStyleVar(); - } - ImGui::SetCursorPos( pos_bot ); - } - - } - // - // Current 1. UNDO History - // - else if (Settings::application.pannel_current_session_mode > 0) { - - static uint _over = 0; - static uint64_t _displayed_over = 0; - static bool _tooltip = 0; - - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear history")) { - Action::manager().init(); - } - ImGui::PopStyleVar(); - // come back... - ImGui::SetCursorPos(pos_bot); - - pos_top = ImGui::GetCursorPos(); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if ( ImGui::ListBoxHeader("##UndoHistory", Action::manager().max(), CLAMP(Action::manager().max(), 4, 8)) ) { - - int count_over = 0; - ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); - - for (uint i = Action::manager().max(); i > 0; --i) { - - if (ImGui::Selectable( Action::manager().label(i).c_str(), i == Action::manager().current(), ImGuiSelectableFlags_AllowDoubleClick, size )) { - // go to on double clic - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - Action::manager().stepTo(i); - else - // show tooltip on clic - _tooltip = true; - } - // mouse over - if (ImGui::IsItemHovered()) - _over = i; - - // if mouse over (only once) - if (_tooltip && _over > 0 && count_over < 1) { - static std::string text = ""; - static Thumbnail _undo_thumbnail; - // load label and thumbnail only if current changed - if (_displayed_over != _over) { - _displayed_over = _over; - text = Action::manager().label(_over); - if (text.find_first_of(':') < text.size()) - text = text.insert( text.find_first_of(':') + 1, 1, '\n'); - FrameBufferImage *im = Action::manager().thumbnail(_over); - if (im) { - // set image content to thumbnail display - _undo_thumbnail.fill( im ); - delete im; - } - else - _undo_thumbnail.reset(); - } - // draw thumbnail in tooltip - ImGui::BeginTooltip(); - _undo_thumbnail.Render(size.x); - ImGui::Text("%s", text.c_str()); - ImGui::EndTooltip(); - ++count_over; // prevents display twice on item overlap - } - - } - ImGui::ListBoxFooter(); - } - // cancel tooltip and mouse over on mouse exit - if ( !ImGui::IsItemHovered()) { - _tooltip = false; - _displayed_over = _over = 0; - } - - pos_bot = ImGui::GetCursorPos(); - - // right buttons - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); - if ( Action::manager().current() > 1 ) { - if ( ImGuiToolkit::IconButton( ICON_FA_UNDO ) ) - Action::manager().undo(); - } else - ImGui::TextDisabled( ICON_FA_UNDO ); - - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetTextLineHeightWithSpacing() + 4)); - if ( Action::manager().current() < Action::manager().max() ) { - if ( ImGuiToolkit::IconButton( ICON_FA_REDO )) - Action::manager().redo(); - } else - ImGui::TextDisabled( ICON_FA_REDO ); - - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); - ImGuiToolkit::ButtonToggle(ICON_FA_MAP_MARKED_ALT, &Settings::application.action_history_follow_view, "Show in view"); - } - // - // Current 0. VERSIONS - // - else { - static uint64_t _over = 0; - static bool _tooltip = 0; - - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear versions")) { - Action::manager().clearSnapshots(); - } - ImGui::PopStyleVar(); - // come back... - ImGui::SetCursorPos(pos_bot); - - // list snapshots - std::list snapshots = Action::manager().snapshots(); - pos_top = ImGui::GetCursorPos(); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if ( ImGui::ListBoxHeader("##Snapshots", snapshots.size(), CLAMP(snapshots.size(), 4, 8)) ) { - - static uint64_t _selected = 0; - static Thumbnail _snap_thumbnail; - static std::string _snap_label = ""; - static std::string _snap_date = ""; - - int count_over = 0; - ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); - for (auto snapit = snapshots.rbegin(); snapit != snapshots.rend(); ++snapit) - { - // entry - ImVec2 pos = ImGui::GetCursorPos(); - - // context menu icon on currently hovered item - if ( _over == *snapit ) { - // open context menu - ImGui::SetCursorPos(ImVec2(size.x-ImGui::GetTextLineHeight()/2.f, pos.y)); - if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) ) { - // current list item - Action::manager().open(*snapit); - // open menu - ImGui::OpenPopup( "MenuSnapshot" ); - } - // show tooltip and select on mouse over menu icon - if (ImGui::IsItemHovered()) { - _selected = *snapit; - _tooltip = true; - } - ImGui::SetCursorPos(pos); - } - - // snapshot item - if (ImGui::Selectable( Action::manager().label(*snapit).c_str(), (*snapit == _selected), ImGuiSelectableFlags_AllowDoubleClick, size )) { - // shot tooltip on clic - _tooltip = true; - // trigger snapshot on double clic - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - Action::manager().restore(*snapit); - } - // mouse over - if (ImGui::IsItemHovered()) { - _over = *snapit; - _selected = 0; - } - - // if mouse over (only once) - if (_tooltip && _over > 0 && count_over < 1) { - static uint64_t current_over = 0; - // load label and thumbnail only if current changed - if (current_over != _over) { - _snap_label = Action::manager().label(_over); - _snap_date = "Version of " + readable_date_time_string(Action::manager().date(_over)); - FrameBufferImage *im = Action::manager().thumbnail(_over); - if (im) { - // set image content to thumbnail display - _snap_thumbnail.fill( im ); - delete im; - } - else - _snap_thumbnail.reset(); - current_over = _over; - } - // draw thumbnail in tooltip - ImGui::BeginTooltip(); - _snap_thumbnail.Render(size.x); - ImGui::Text("%s", _snap_date.c_str()); - ImGui::EndTooltip(); - ++count_over; // prevents display twice on item overlap - } - } - - // context menu on currently open snapshot - uint64_t current = Action::manager().currentSnapshot(); - if (ImGui::BeginPopup( "MenuSnapshot" ) && current > 0 ) - { - _selected = current; - // snapshot thumbnail - _snap_thumbnail.Render(size.x); - // snapshot editable label - ImGui::SetNextItemWidth(size.x); - if ( ImGuiToolkit::InputText("##Rename", &_snap_label ) ) - Action::manager().setLabel( current, _snap_label); - // snapshot actions - if (ImGui::Selectable( ICON_FA_ANGLE_DOUBLE_RIGHT " Restore", false, 0, size )) - Action::manager().restore(); - if (ImGui::Selectable( ICON_FA_CODE_BRANCH "- Remove", false, 0, size )) - Action::manager().remove(); - // export option if possible - std::string filename = Mixer::manager().session()->filename(); - if (filename.size()>0) { - if (ImGui::Selectable( ICON_FA_FILE_DOWNLOAD " Export", false, 0, size )) { - Action::manager().saveas(filename); - } - } - ImGui::EndPopup(); - } - else - _selected = 0; - - // end list snapshots - ImGui::ListBoxFooter(); - } - // cancel tooltip and mouse over on mouse exit - if ( !ImGui::IsItemHovered()) { - _tooltip = false; - _over = 0; - } - - // Right panel buton - pos_bot = ImGui::GetCursorPos(); - - // right buttons - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); - if ( ImGuiToolkit::IconButton( ICON_FA_FILE_DOWNLOAD " +")) { - UserInterface::manager().saveOrSaveAs(true); - } - if (ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip("Save & Keep version"); - - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); - ImGuiToolkit::HelpToolTip("Previous versions of the session (latest on top). " - "Double-clic on a version to restore it.\n\n" - ICON_FA_CODE_BRANCH " With iterative saving on, a new version " - "is kept each time the session is saved."); - // toggle button for versioning - ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); - ImGuiToolkit::ButtonToggle(" " ICON_FA_CODE_BRANCH " ", &Settings::application.save_version_snapshot,"Iterative saving"); - - ImGui::SetCursorPos( pos_bot ); - } - - // - // Buttons to show WINDOWS - // - ImGuiToolkit::Spacing(); - ImGui::Text("Windows"); - ImGui::Spacing(); - - - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - bool on = false; - - ImGui::SameLine(0, 0.5f * ImGui::GetTextLineHeight()); - on = Settings::application.widget.preview; - if (ImGuiToolkit::IconToggle( ICON_FA_DESKTOP, &on, TOOLTIP_OUTPUT, SHORTCUT_OUTPUT)) - UserInterface::manager().outputcontrol.setVisible(on); - - ImGui::SameLine(0, ImGui::GetTextLineHeight()); - on = Settings::application.widget.media_player; - if (ImGuiToolkit::IconToggle( ICON_FA_PLAY_CIRCLE, &on, TOOLTIP_PLAYER, SHORTCUT_PLAYER)) - UserInterface::manager().sourcecontrol.setVisible(on); - - ImGui::SameLine(0, ImGui::GetTextLineHeight()); - on = Settings::application.widget.timer; - if (ImGuiToolkit::IconToggle( ICON_FA_CLOCK, &on, TOOLTIP_TIMER, SHORTCUT_TIMER)) - UserInterface::manager().timercontrol.setVisible(on); - - ImGui::SameLine(0, ImGui::GetTextLineHeight()); - on = Settings::application.widget.inputs; - if (ImGuiToolkit::IconToggle( ICON_FA_HAND_PAPER, &on, TOOLTIP_INPUTS, SHORTCUT_INPUTS)) - UserInterface::manager().inputscontrol.setVisible(on); - - ImGui::SameLine(0, ImGui::GetTextLineHeight() - IMGUI_SAME_LINE); - static uint counter_menu_timeout = 0; - const ImVec4* colors = ImGui::GetStyle().Colors; - ImGui::PushStyleColor( ImGuiCol_Text, ImGui::IsPopupOpen("MenuToolboxWindows") ? colors[ImGuiCol_DragDropTarget] : colors[ImGuiCol_Text] ); - if ( ImGuiToolkit::IconButton( " " ICON_FA_ELLIPSIS_V " " ) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) ) { - counter_menu_timeout=0; - ImGui::OpenPopup( "MenuToolboxWindows" ); - } - ImGui::PopStyleColor(1); - - ImGui::PopFont(); - - if (ImGui::BeginPopup( "MenuToolboxWindows" )) - { - // Enable / disable source toolbar - ImGui::MenuItem( MENU_SOURCE_TOOL, NULL, &Settings::application.widget.source_toolbar ); - // Enable / disable metrics toolbar - ImGui::MenuItem( MENU_METRICS, NULL, &Settings::application.widget.stats ); - // Add sticky note - if (ImGui::MenuItem( MENU_NOTE )) - Mixer::manager().session()->addNote(); - // Show help - if (ImGui::MenuItem( MENU_HELP, SHORTCUT_HELP) ) - Settings::application.widget.help = true; - // Show Logs - if (ImGui::MenuItem( MENU_LOGS, SHORTCUT_LOGS) ) - Settings::application.widget.logs = true; - // timer to close menu like a tooltip - if (ImGui::IsWindowHovered()) - counter_menu_timeout=0; - else if (++counter_menu_timeout > 10) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } - - -} - - void Navigator::RenderMousePointerSelector(const ImVec2 &size) { ImGuiContext& g = *GImGui; @@ -4852,19 +4171,968 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size) } +void Navigator::RenderMainPannelSession() +{ + const float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN; + const float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing(); + const float space = ImGui::GetStyle().ItemSpacing.y; + + // + // Session + // + ImGui::Text("Session"); + + std::string sessions_current = Mixer::manager().session()->filename(); + if (sessions_current.empty()) + sessions_current = ""; + else + sessions_current = SystemToolkit::filename(sessions_current); + + // + // Show combo box of recent files + // + static std::list sessions_list; + // get list of recent sessions when it changed, not at every frame + if (Settings::application.recentSessions.changed) { + Settings::application.recentSessions.changed = false; + Settings::application.recentSessions.validate(); + sessions_list = Settings::application.recentSessions.filenames; + } + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::BeginCombo("##RecentSessions", sessions_current.c_str() )) { + // list all sessions in recent list + for(auto it = sessions_list.begin(); it != sessions_list.end(); ++it) { + if (ImGui::Selectable( SystemToolkit::filename(*it).c_str() ) ) { + Mixer::manager().open( *it ); + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text( "%s", (*it).c_str() ); + ImGui::EndTooltip(); + } + } + ImGui::EndCombo(); + } + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SameLine(); + if ( Mixer::manager().session()->filename().empty()) { + if ( ImGuiToolkit::IconButton(ICON_FA_FILE_DOWNLOAD, "Save as")) + UserInterface::manager().saveOrSaveAs(); + } else { + if (ImGuiToolkit::IconButton(ICON_FA_FOLDER_OPEN, "Show in finder")) + SystemToolkit::open(SystemToolkit::path_filename(Mixer::manager().session()->filename())); + } + ImGui::SetCursorPos(pos); + + // + // Preview session + // + Session *se = Mixer::manager().session(); + float width = preview_width; + float height = se->frame()->projectionArea().y * width / ( se->frame()->projectionArea().x * se->frame()->aspectRatio()); + if (height > preview_height - space) { + height = preview_height - space; + width = height * se->frame()->aspectRatio() * ( se->frame()->projectionArea().x / se->frame()->projectionArea().y); + } + // centered image + ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) ); + ImGui::Image((void*)(uintptr_t) se->frame()->texture(), ImVec2(width, height)); + + // right side options for session + if (!Mixer::manager().session()->filename().empty()) { + + // + // Right align icon top : heart to add to favorites + // + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + space) ); + // if session is in favorites + if ( UserInterface::manager().favorites.has( Mixer::manager().session()->filename() ) > 0 ) { + // offer to remove from favorites + if ( ImGuiToolkit::IconButton( 15, 4 , "Remove from favorites")) { + UserInterface::manager().favorites.remove( Mixer::manager().session()->filename() ); + } + } + // else session is not in favorites, offer to add + else if ( ImGuiToolkit::IconButton( 16, 4 , "Add to favorites")) { + UserInterface::manager().favorites.add( Mixer::manager().session()->filename() ); + } + + // + // Right align icon middle : sticky note + // + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + preview_height - 2.f * ImGui::GetFrameHeightWithSpacing()) ); + if ( ImGuiToolkit::IconButton( ICON_FA_STICKY_NOTE " +", "Add a sticky note")) { + Mixer::manager().session()->addNote(); + } + + // + // Right align bottom icon : thumbnail of session file, on/off + // + static Thumbnail _session_thumbnail; + static FrameBufferImage *_thumbnail = nullptr; + bool _user_thumbnail = Mixer::manager().session()->thumbnail() != nullptr; + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + preview_height - ImGui::GetFrameHeightWithSpacing()) ); + if (ImGuiToolkit::IconToggle(2, 8, 7, 8, &_user_thumbnail)) { + if (_user_thumbnail) + Mixer::manager().session()->setThumbnail(); + else { + Mixer::manager().session()->resetThumbnail(); + _session_thumbnail.reset(); + } + _thumbnail = nullptr; + } + if (ImGui::IsItemHovered()){ + // thumbnail changed + if (_thumbnail != Mixer::manager().session()->thumbnail()) { + _session_thumbnail.reset(); + _thumbnail = Mixer::manager().session()->thumbnail(); + if (_thumbnail != nullptr) + _session_thumbnail.fill( _thumbnail ); + } + ImGui::BeginTooltip(); + if (_session_thumbnail.filled()) { + _session_thumbnail.Render(230); + ImGui::Text(" Custom thumbnail"); + } + else { + ImGui::Text(" No thumbnail "); + } + ImGui::EndTooltip(); + } + } + + // + // Menu for actions on current session + ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("##Selectpanelsession", &Settings::application.pannel_current_session_mode, + ICON_FA_CODE_BRANCH " Versions\0" ICON_FA_HISTORY " Undo history\0" ICON_FA_BORDER_STYLE " Resolution\0"); + ImVec2 pos_bot = ImGui::GetCursorPos(); + + // + // Current 2. RESOLUTION + // + if (Settings::application.pannel_current_session_mode > 1) { + + // Information and resolution + const FrameBuffer *output = Mixer::manager().session()->frame(); + if (output) { + // change resolution (height only) + // get parameters to edit resolution + glm::ivec2 preset = RenderView::presetFromResolution(output->resolution()); + glm::ivec2 custom = glm::ivec2(output->resolution()); + if (preset.x > -1) { + // cannot change resolution when recording + if ( UserInterface::manager().outputcontrol.isRecording() ) { + // show static info (same size than combo) + static char dummy_str[512]; + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + snprintf(dummy_str, 512, "%s", RenderView::ratio_preset_name[preset.x]); + ImGui::InputText("Ratio", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + if (preset.x < RenderView::AspectRatio_Custom) { + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + snprintf(dummy_str, 512, "%s", RenderView::height_preset_name[preset.y]); + ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + } else { + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + snprintf(dummy_str, 512, "%d", custom.x); + ImGui::InputText("Width", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + snprintf(dummy_str, 512, "%d", custom.y); + ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + } + ImGui::PopStyleColor(1); + } + // offer to change filename, ratio and resolution + else { + // combo boxes to select aspect rario + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::Combo("Ratio", &preset.x, RenderView::ratio_preset_name, IM_ARRAYSIZE(RenderView::ratio_preset_name) ) ) { + // change to custom aspect ratio: propose 1:1 + glm::vec3 res = glm::vec3(custom.y, custom.y, 0.f); + // else, change to preset aspect ratio + if (preset.x < RenderView::AspectRatio_Custom) + res = RenderView::resolutionFromPreset(preset.x, preset.y); + // change resolution + Mixer::manager().setResolution(res); + } + // - preset aspect ratio : propose preset height + if (preset.x < RenderView::AspectRatio_Custom) { + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::Combo("Height", &preset.y, RenderView::height_preset_name, IM_ARRAYSIZE(RenderView::height_preset_name) ) ) { + glm::vec3 res = RenderView::resolutionFromPreset(preset.x, preset.y); + Mixer::manager().setResolution(res); + } + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + static char dummy_str[512]; + snprintf(dummy_str, 512, "%d", custom.x ); + ImGui::InputText("Width", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); + } + // - custom aspect ratio : input width and height + else { + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::InputInt("Height", &custom.y, 100, 500); + if (ImGui::IsItemDeactivatedAfterEdit()) + Mixer::manager().setResolution( glm::vec3(custom, 0.f)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::InputInt("Width", &custom.x, 100, 500); + if (ImGui::IsItemDeactivatedAfterEdit()) + Mixer::manager().setResolution( glm::vec3(custom, 0.f)); + } + } + } + } + } + // + // Current 1. UNDO History + // + else if (Settings::application.pannel_current_session_mode > 0) { + + static uint _over = 0; + static uint64_t _displayed_over = 0; + static bool _tooltip = 0; + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y -ImGui::GetFrameHeight() )); + if ( Action::manager().current() > 1 ) { + if ( ImGuiToolkit::IconButton( ICON_FA_UNDO, "Undo" ) ) + Action::manager().undo(); + } else + ImGui::TextDisabled( ICON_FA_UNDO ); + ImGui::SameLine(); + if ( Action::manager().current() < Action::manager().max() ) { + if ( ImGuiToolkit::IconButton( ICON_FA_REDO, "Redo" )) + Action::manager().redo(); + } else + ImGui::TextDisabled( ICON_FA_REDO ); + + // come back... + ImGui::SetCursorPos(pos_bot); + + ImVec2 pos_top = ImGui::GetCursorPos(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::ListBoxHeader("##UndoHistory", Action::manager().max(), CLAMP(Action::manager().max(), 4, 8)) ) { + + int count_over = 0; + ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); + + for (uint i = Action::manager().max(); i > 0; --i) { + + if (ImGui::Selectable( Action::manager().label(i).c_str(), i == Action::manager().current(), ImGuiSelectableFlags_AllowDoubleClick, size )) { + // go to on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Action::manager().stepTo(i); + else + // show tooltip on clic + _tooltip = true; + } + // mouse over + if (ImGui::IsItemHovered()) + _over = i; + + // if mouse over (only once) + if (_tooltip && _over > 0 && count_over < 1) { + static std::string text = ""; + static Thumbnail _undo_thumbnail; + // load label and thumbnail only if current changed + if (_displayed_over != _over) { + _displayed_over = _over; + text = Action::manager().label(_over); + if (text.find_first_of(':') < text.size()) + text = text.insert( text.find_first_of(':') + 1, 1, '\n'); + FrameBufferImage *im = Action::manager().thumbnail(_over); + if (im) { + // set image content to thumbnail display + _undo_thumbnail.fill( im ); + delete im; + } + else + _undo_thumbnail.reset(); + } + // draw thumbnail in tooltip + ImGui::BeginTooltip(); + _undo_thumbnail.Render(size.x); + ImGui::Text("%s", text.c_str()); + ImGui::EndTooltip(); + ++count_over; // prevents display twice on item overlap + } + + } + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) { + _tooltip = false; + _displayed_over = _over = 0; + } + + pos_bot = ImGui::GetCursorPos(); + + // right buttons + if ( Action::manager().max() > 1 ) { + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); + if (ImGuiToolkit::IconButton( 12, 14, "Clear history")) + Action::manager().init(); + } + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); + ImGuiToolkit::HelpToolTip("History of actions (latest on top). " + "Double-clic on an action to restore its status.\n\n" + ICON_FA_MAP_MARKED_ALT " Enable Show in view to automatically " + "navigate to the view when the action is undone/redone."); + // toggle button for shhow in view + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); + ImGuiToolkit::ButtonToggle(ICON_FA_MAP_MARKED_ALT, &Settings::application.action_history_follow_view, "Show in view"); + } + // + // Current 0. VERSIONS + // + else { + static uint64_t _over = 0; + static bool _tooltip = 0; + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y -ImGui::GetFrameHeight() )); + if ( ImGuiToolkit::IconButton( ICON_FA_FILE_DOWNLOAD " +")) { + UserInterface::manager().saveOrSaveAs(true); + } + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Save & Keep version"); + // come back... + ImGui::SetCursorPos(pos_bot); + + // list snapshots + std::list snapshots = Action::manager().snapshots(); + ImVec2 pos_top = ImGui::GetCursorPos(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::ListBoxHeader("##Snapshots", snapshots.size(), CLAMP(snapshots.size(), 4, 8)) ) { + + static uint64_t _selected = 0; + static Thumbnail _snap_thumbnail; + static std::string _snap_label = ""; + static std::string _snap_date = ""; + + int count_over = 0; + ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); + for (auto snapit = snapshots.rbegin(); snapit != snapshots.rend(); ++snapit) + { + // entry + ImVec2 pos = ImGui::GetCursorPos(); + + // context menu icon on currently hovered item + if ( _over == *snapit ) { + // open context menu + ImGui::SetCursorPos(ImVec2(size.x-ImGui::GetTextLineHeight()/2.f, pos.y)); + if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) ) { + // current list item + Action::manager().open(*snapit); + // open menu + ImGui::OpenPopup( "MenuSnapshot" ); + } + // show tooltip and select on mouse over menu icon + if (ImGui::IsItemHovered()) { + _selected = *snapit; + _tooltip = true; + } + ImGui::SetCursorPos(pos); + } + + // snapshot item + if (ImGui::Selectable( Action::manager().label(*snapit).c_str(), (*snapit == _selected), ImGuiSelectableFlags_AllowDoubleClick, size )) { + // shot tooltip on clic + _tooltip = true; + // trigger snapshot on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Action::manager().restore(*snapit); + } + // mouse over + if (ImGui::IsItemHovered()) { + _over = *snapit; + _selected = 0; + } + + // if mouse over (only once) + if (_tooltip && _over > 0 && count_over < 1) { + static uint64_t current_over = 0; + // load label and thumbnail only if current changed + if (current_over != _over) { + _snap_label = Action::manager().label(_over); + _snap_date = "Version of " + readable_date_time_string(Action::manager().date(_over)); + FrameBufferImage *im = Action::manager().thumbnail(_over); + if (im) { + // set image content to thumbnail display + _snap_thumbnail.fill( im ); + delete im; + } + else + _snap_thumbnail.reset(); + current_over = _over; + } + // draw thumbnail in tooltip + ImGui::BeginTooltip(); + _snap_thumbnail.Render(size.x); + ImGui::Text("%s", _snap_date.c_str()); + ImGui::EndTooltip(); + ++count_over; // prevents display twice on item overlap + } + } + + // context menu on currently open snapshot + uint64_t current = Action::manager().currentSnapshot(); + if (ImGui::BeginPopup( "MenuSnapshot" ) && current > 0 ) + { + _selected = current; + // snapshot thumbnail + _snap_thumbnail.Render(size.x); + // snapshot editable label + ImGui::SetNextItemWidth(size.x); + if ( ImGuiToolkit::InputText("##Rename", &_snap_label ) ) + Action::manager().setLabel( current, _snap_label); + // snapshot actions + if (ImGui::Selectable( ICON_FA_ANGLE_DOUBLE_RIGHT " Restore", false, 0, size )) + Action::manager().restore(); + if (ImGui::Selectable( ICON_FA_CODE_BRANCH "- Remove", false, 0, size )) + Action::manager().remove(); + // export option if possible + std::string filename = Mixer::manager().session()->filename(); + if (filename.size()>0) { + if (ImGui::Selectable( ICON_FA_FILE_DOWNLOAD " Export", false, 0, size )) { + Action::manager().saveas(filename); + } + } + ImGui::EndPopup(); + } + else + _selected = 0; + + // end list snapshots + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) { + _tooltip = false; + _over = 0; + } + + // Right panel buton + pos_bot = ImGui::GetCursorPos(); + + // right buttons + if (!snapshots.empty()) { + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); + if (ImGuiToolkit::IconButton( 12, 14, "Clear list")) + Action::manager().clearSnapshots(); + } + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); + ImGuiToolkit::HelpToolTip("Previous versions of the session (latest on top). " + "Double-clic on a version to restore it.\n\n" + ICON_FA_CODE_BRANCH " With iterative saving enabled, a new version " + "is kept each time the session is saved."); + // toggle button for versioning + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); + ImGuiToolkit::ButtonToggle(" " ICON_FA_CODE_BRANCH " ", &Settings::application.save_version_snapshot,"Iterative saving"); + + ImGui::SetCursorPos( pos_bot ); + } + +} + +#define PLAYLIST_FAVORITES ICON_FA_HEART " Favorites" + +void Navigator::RenderMainPannelPlaylist() +{ + // + // SESSION panel + // + ImGui::Text("Playlists"); + + // currently active playlist and folder + static std::string playlist_header = PLAYLIST_FAVORITES; + static Playlist active_playlist; + static std::list folder_session_files; + + // file dialogs to open / save playlist files and folders + static DialogToolkit::OpenFolderDialog customFolder("Open Folder"); + static DialogToolkit::MultipleSessionsDialog selectSessions("Select vimix sessions"); + + // static DialogToolkit::OpenPlaylistDialog openPlaylist("Open Playlist"); + // static DialogToolkit::SavePlaylistDialog savePlaylist("Save Playlist"); + + // // return from thread for playlist file openning + // if (openPlaylist.closed() && !openPlaylist.path().empty()) { + // Settings::application.recentPlaylists.push(openPlaylist.path()); + // Settings::application.recentPlaylists.assign(openPlaylist.path()); + // Settings::application.pannel_playlist_mode = 1; + // } + + // ImGui::SameLine(); + // ImGui::SetCursorPosX( pannel_width_ IMGUI_RIGHT_ALIGN); + // if ( ImGuiToolkit::IconButton( 16, 3, "Create playlist")) { + // savePlaylist.open(); + // } + // if (savePlaylist.closed() && !savePlaylist.path().empty()) { + // Settings::application.recentPlaylists.push(savePlaylist.path()); + // Settings::application.recentPlaylists.assign(savePlaylist.path()); + // Settings::application.pannel_playlist_mode = 1; + // } + + + // return from thread for folder openning + if (customFolder.closed() && !customFolder.path().empty()) { + Settings::application.recentFolders.push(customFolder.path()); + Settings::application.recentFolders.assign(customFolder.path()); + Settings::application.pannel_playlist_mode = 2; + } + + // load the list of session in playlist, only once when list changed + if (Settings::application.recentPlaylists.changed) { + Settings::application.recentPlaylists.changed = false; + Settings::application.recentPlaylists.validate(); + // load list + if ( !Settings::application.recentPlaylists.path.empty()) + active_playlist.load( Settings::application.recentPlaylists.path ); + } + + // get list of vimix files in folder, only once when list changed + if (Settings::application.recentFolders.changed) { + Settings::application.recentFolders.changed = false; + Settings::application.recentFolders.validate(); + // list directory + if ( !Settings::application.recentFolders.path.empty()) + folder_session_files = SystemToolkit::list_directory( Settings::application.recentFolders.path, { VIMIX_FILE_PATTERN }, + (SystemToolkit::Ordering) Settings::application.recentFolders.ordering); + } + + // + // Show combo box of quick selection of recent playlist / directory + // + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::BeginCombo("##SelectionPlaylist", playlist_header.c_str(), ImGuiComboFlags_HeightLarge )) { + + // Mode 0 : Favorite playlist + if (ImGuiToolkit::SelectableIcon( 16, 4, "Favorites", false) ) { + Settings::application.pannel_playlist_mode = 0; + } + // Mode 1 : Playlists + for(auto playlistname = Settings::application.recentPlaylists.filenames.begin(); + playlistname != Settings::application.recentPlaylists.filenames.end(); playlistname++) { + if (ImGuiToolkit::SelectableIcon( 12, 3, SystemToolkit::base_filename( *playlistname ).c_str(), false )) { + // remember which path was selected + Settings::application.recentPlaylists.assign(*playlistname); + // set mode + Settings::application.pannel_playlist_mode = 1; + } + } + // Mode 2 : known folders + for(auto foldername = Settings::application.recentFolders.filenames.begin(); + foldername != Settings::application.recentFolders.filenames.end(); foldername++) { + if (ImGuiToolkit::SelectableIcon( 6, 5, BaseToolkit::truncated( *foldername, 40).c_str(), false) ) { + // remember which path was selected + Settings::application.recentFolders.assign(*foldername); + // set mode + Settings::application.pannel_playlist_mode = 2; + } + } + ImGui::EndCombo(); + } + + // + // icon to create new playlist + // + ImVec2 pos_top = ImGui::GetCursorPos(); + ImVec2 pos_right = ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y - ImGui::GetFrameHeight()); + ImGui::SetCursorPos( pos_right ); + if (ImGuiToolkit::IconButton( 13, 3, "Create playlist")) { + ImGui::OpenPopup("new_playlist_popup"); + } + + // + // icon to list directory + // + pos_right.x += ImGui::GetTextLineHeightWithSpacing() + IMGUI_SAME_LINE; + ImGui::SetCursorPos( pos_right ); + if (ImGuiToolkit::IconButton( 5, 5, "List directory")) { + customFolder.open(); + } + + ImGui::SetCursorPos(pos_top); + + const ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 list_size = ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN -2.f * style.WindowPadding.x, + 7.f * (ImGui::GetTextLineHeightWithSpacing() + style.FramePadding.y ) + style.FramePadding.y); + ImVec2 item_size = ImVec2( list_size.x -2.f * style.FramePadding.x, ImGui::GetTextLineHeightWithSpacing()); + + std::string session_hovered_ = ""; + std::string session_triggered_ = ""; + static uint session_tooltip_ = 0; + ++session_tooltip_; + + // + // Show session list depending on the mode + // + // selection MODE 0 ; FAVORITES + // + if ( Settings::application.pannel_playlist_mode == 0) { + + // set header + playlist_header = PLAYLIST_FAVORITES; + + // how many session files in favorite playlist + size_t index_max = UserInterface::manager().favorites.size(); + item_size.x -= index_max > 7 ? style.ScrollbarSize : 0.f; + + // display the sessions list and detect if one was selected (double clic) + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::ListBoxHeader("##Favorites", list_size) ) { + + // list session files in favorite playlist + for (size_t index = 0; index < index_max; ++index) { + // get name of session file at index + std::string session_file = UserInterface::manager().favorites.at(index); + + // unique ID for item (filename can be at different index) + ImGui::PushID( session_file.c_str() ); + + // item to select + ImGui::BeginGroup(); + if (ImGui::Selectable( SystemToolkit::filename(session_file).c_str(), false, + ImGuiSelectableFlags_AllowDoubleClick, item_size )) { + // trigger on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + session_triggered_ = session_file; + } + // show tooltips on single clic + else + session_tooltip_ = 100; + } + if (ImGui::IsItemActive()) { + ImGui::SameLine( item_size.x - 2.f * style.ScrollbarSize ); + ImGuiToolkit::Icon( 8, 15 ); + } + ImGui::EndGroup(); + ImGui::PopID(); + // what item is hovered for tooltip + if (ImGui::IsItemHovered()) + session_hovered_ = session_file; + // simple drag to reorder + else if (ImGui::IsItemActive()) + { + size_t index_next = index + (ImGui::GetMouseDragDelta(0).y < -2.f * style.ItemSpacing.y ? -1 : ImGui::GetMouseDragDelta(0).y > 2.f * style.ItemSpacing.y ? 1 : 0); + if ( index_next < index_max && index != index_next ) { + // reorder in list + UserInterface::manager().favorites.move(index, index_next); + UserInterface::manager().favorites.save(); + // cancel tooltip during drag + session_tooltip_ = 0; + // reset drag + ImGui::ResetMouseDragDelta(); + } + } + } + + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) + session_tooltip_ = 0; + + } + // + // selection MODE 1 : PLAYLISTS + // + else if ( Settings::application.pannel_playlist_mode == 1) { + + // set header + if (Settings::application.recentPlaylists.path.empty()) + Settings::application.pannel_playlist_mode = 0; + else + playlist_header = std::string(ICON_FA_STAR) + " " + SystemToolkit::base_filename(Settings::application.recentPlaylists.path); + + // how many session files in favorite playlist + size_t index_max = active_playlist.size(); + size_t index_to_remove = index_max; + item_size.x -= ImGui::GetTextLineHeight() + style.ItemSpacing.x ; + item_size.x -= index_max > 6 ? style.ScrollbarSize : 0.f; + + // display the sessions list and detect if one was selected (double clic) + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::ListBoxHeader("##Playlist", list_size) ) { + + // list session files in favorite playlist + for (size_t index = 0; index < index_max; ++index) { + + // get name of session file at index + std::string session_file = active_playlist.at(index); + + // unique ID for item (filename can be at different index) + ImGui::PushID( session_file.c_str() ); + + // item to select + ImGui::BeginGroup(); + if (ImGui::Selectable( SystemToolkit::filename(session_file).c_str(), false, + ImGuiSelectableFlags_AllowDoubleClick, item_size )) { + // trigger on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + session_triggered_ = session_file; + } + // show tooltips on single clic + else + session_tooltip_ = 100; + } + ImGui::SameLine(); + if (ImGui::IsItemActive()) { + ImGuiToolkit::IconButton( 8, 15 ); + } + else { + if ( ImGuiToolkit::IconButton( 19, 4, "Remove") ) + index_to_remove = index; + } + ImGui::EndGroup(); + ImGui::PopID(); + + // what item is hovered for tooltip + if (ImGui::IsItemHovered()) + session_hovered_ = session_file; + // simple drag to reorder + else if (ImGui::IsItemActive()) + { + size_t index_next = index + (ImGui::GetMouseDragDelta(0).y < -2.f * style.ItemSpacing.y ? -1 : ImGui::GetMouseDragDelta(0).y > 2.f * style.ItemSpacing.y ? 1 : 0); + if ( index_next < index_max && index != index_next ) { + // reorder in list and save new status + active_playlist.move(index, index_next); + active_playlist.save(); + // cancel tooltip during drag + session_tooltip_ = 0; + // reset drag + ImGui::ResetMouseDragDelta(); + } + } + } + + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) + session_tooltip_ = 0; + + // Remove + if ( index_to_remove < index_max ) { + active_playlist.remove( index_to_remove ); + active_playlist.save(); + } + + // Right side of the list : close and save + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y)); + if (ImGuiToolkit::IconButton( 14, 3, "Delete playlist")) { + ImGui::OpenPopup("delete_playlist_popup"); + } + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + 1.5f * ImGui::GetTextLineHeightWithSpacing())); + if ( ImGuiToolkit::IconButton( 18, 4, "Add sessions")) { + selectSessions.open(); + } + + // return from thread for sessions multiple selection + if (selectSessions.closed() && !selectSessions.files().empty()) { + active_playlist.add(selectSessions.files()); + active_playlist.save(); + } + + } + // + // selection MODE 2 : LIST FOLDER + // + else if ( Settings::application.pannel_playlist_mode == 2) { + + // set header + if (Settings::application.recentFolders.path.empty()) + Settings::application.pannel_playlist_mode = 0; + else + playlist_header = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated(Settings::application.recentFolders.path, 40); + + // how many listed + item_size.x -= folder_session_files.size() > 7 ? style.ScrollbarSize : 0.f; + + // display the sessions list and detect if one was selected (double clic) + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::ListBoxHeader("##FolderList", list_size) ) { + + // list session files in folder + for(auto it = folder_session_files.begin(); it != folder_session_files.end(); ++it) { + // item to select + if (ImGui::Selectable( SystemToolkit::filename(*it).c_str(), false, + ImGuiSelectableFlags_AllowDoubleClick, item_size )) { + // trigger on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + session_triggered_ = *it; + } + // show tooltips on clic + else + session_tooltip_ = 100; + } + if (ImGui::IsItemHovered()) + session_hovered_ = *it; + } + + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) + session_tooltip_ = 0; + + // ordering list + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y) ); + if (ImGuiToolkit::IconButton( 4, 5, "Close directory")) { + Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path); + if (Settings::application.recentFolders.filenames.empty()) + Settings::application.pannel_playlist_mode = 0; + else + Settings::application.recentFolders.assign( Settings::application.recentFolders.filenames.front() ); + } + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetTextLineHeightWithSpacing())); + ImGui::PushID("##folder_ordering_changed"); + if ( ImGuiToolkit::IconMultistate(icons_ordering_files, &Settings::application.recentFolders.ordering, tooltips_ordering_files) ) + Settings::application.recentFolders.changed = true; + ImGui::PopID(); + + } + + // + // Tooltip to show Session thumbnail + // + if (session_tooltip_ > 60 && !session_hovered_.empty()) { + + static std::string _current_hovered = ""; + static std::string _file_info = ""; + static Thumbnail _file_thumbnail; + static bool with_tag_ = false; + + // load info only if changed from the one already displayed + if (session_hovered_ != _current_hovered) { + _current_hovered = session_hovered_; + SessionInformation info = SessionCreator::info(_current_hovered); + _file_info = info.description; + if (info.thumbnail) { + // set image content to thumbnail display + _file_thumbnail.fill( info.thumbnail ); + with_tag_ = info.user_thumbnail_; + delete info.thumbnail; + } else + _file_thumbnail.reset(); + } + + if ( !_file_info.empty()) { + + ImGui::BeginTooltip(); + ImVec2 p_ = ImGui::GetCursorScreenPos(); + _file_thumbnail.Render(240); + ImGui::Text("%s", _file_info.c_str()); + if (with_tag_) { + ImGui::SetCursorScreenPos(p_ + ImVec2(6, 6)); + ImGui::Text(ICON_FA_TAG); + } + ImGui::EndTooltip(); + } + } + + // + // Double clic to trigger openning of session + // + if (!session_triggered_.empty()) { + Mixer::manager().open( session_triggered_, Settings::application.smooth_transition ); + if (Settings::application.smooth_transition) + WorkspaceWindow::clearWorkspace(); + } + // help indicator + pos_top.y += list_size.y; + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y - 2.f * ImGui::GetFrameHeightWithSpacing())); + ImGuiToolkit::HelpToolTip("Double-clic on a filename to open the session.\n\n" + ICON_FA_ARROW_CIRCLE_RIGHT " enable Smooth transition " + "to perform a cross fading with the current session."); + // toggle button for smooth transition + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y - ImGui::GetFrameHeightWithSpacing()) ); + ImGuiToolkit::ButtonToggle(ICON_FA_ARROW_CIRCLE_RIGHT, &Settings::application.smooth_transition, "Smooth transition"); + + // + // Popup window to create playlist + // + if (ImGui::BeginPopup("new_playlist_popup", ImGuiWindowFlags_NoMove)) + { + static bool withcopy = false; + char text_buf[64] = ""; + ImGui::SetNextItemWidth(200); + if ( ImGui::InputTextWithHint("Name", "[Enter] to validate", text_buf, 64, ImGuiInputTextFlags_EnterReturnsTrue) ) { + + std::string filename = std::string(text_buf); + + if ( !filename.empty() ) { + filename += "." VIMIX_PLAYLIST_FILE_EXT; + filename = SystemToolkit::full_filename( UserInterface::manager().playlists_path, filename); + + // create and fill the playlist + Playlist tmp; + if (withcopy) { + if (Settings::application.pannel_playlist_mode == 0) + tmp = UserInterface::manager().favorites; + else if (Settings::application.pannel_playlist_mode == 1) + tmp = active_playlist; + else if (Settings::application.pannel_playlist_mode == 2) + tmp.add(folder_session_files); + } + tmp.saveAs( filename ); + + // set mode to Playlist mode + Settings::application.recentPlaylists.push(filename); + Settings::application.recentPlaylists.assign(filename); + Settings::application.pannel_playlist_mode = 1; + + ImGui::CloseCurrentPopup(); + } + } + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); + ImGuiToolkit::ButtonSwitch("Copy content", &withcopy, NULL, true ); + ImGui::PopFont(); + + ImGui::EndPopup(); + } + + // + // Popup window to delete playlist + // + if (ImGui::BeginPopup("delete_playlist_popup", ImGuiWindowFlags_NoMove)) + { + std::string question = "Yes, delete '"; + question += SystemToolkit::base_filename(Settings::application.recentPlaylists.path) + "' "; + if ( ImGui::Button( question.c_str() )) { + // delete the file + SystemToolkit::remove_file(Settings::application.recentPlaylists.path); + + // remove from the list + Settings::application.recentPlaylists.filenames.remove(Settings::application.recentPlaylists.path); + if (Settings::application.recentPlaylists.filenames.empty()) + Settings::application.pannel_playlist_mode = 0; + else + Settings::application.recentPlaylists.assign( Settings::application.recentPlaylists.filenames.front() ); + + ImGui::CloseCurrentPopup(); + } + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); + ImGui::Text("This cannot be undone"); + ImGui::PopFont(); + + ImGui::EndPopup(); + } + +} + void Navigator::RenderMainPannelSettings() { - // TITLE - ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text("Settings"); - ImGui::PopFont(); - ImGui::SetCursorPosY(width_); - // // Appearance // - ImGui::Text("Appearance"); + ImGui::Text("Settings"); int v = Settings::application.accent_color; ImGui::Spacing(); ImGui::SetCursorPosX(0.5f * width_); @@ -4890,7 +5158,7 @@ void Navigator::RenderMainPannelSettings() // // Recording preferences // - ImGui::Text("Record"); + ImGui::TextDisabled("Recording"); // select CODEC and FPS ImGui::SetCursorPosX(width_); @@ -4930,7 +5198,7 @@ void Navigator::RenderMainPannelSettings() // Steaming preferences // ImGuiToolkit::Spacing(); - ImGui::Text("Stream"); + ImGui::TextDisabled("Stream"); ImGuiToolkit::Indication("Peer-to-peer sharing local network\n\n" "vimix can stream JPEG (default) or H264 (less bandwidth, higher encoding cost)", ICON_FA_SHARE_ALT_SQUARE); @@ -5006,7 +5274,7 @@ void Navigator::RenderMainPannelSettings() // OSC preferences // ImGuiToolkit::Spacing(); - ImGui::Text("OSC"); + ImGui::TextDisabled("OSC"); char msg[256]; ImFormatString(msg, IM_ARRAYSIZE(msg), "Open Sound Control\n\n" @@ -5061,7 +5329,7 @@ void Navigator::RenderMainPannelSettings() // ImGuiToolkit::HelpMarker("If you encounter some rendering issues on your machine, " // "you can try to disable some of the OpenGL optimizations below."); // ImGui::SameLine(); - ImGui::Text("System"); + ImGui::TextDisabled("System"); static bool need_restart = false; static bool vsync = (Settings::application.render.vsync > 0); @@ -5077,9 +5345,8 @@ void Navigator::RenderMainPannelSettings() else ImGui::TextDisabled("Hardware en/decoding unavailable"); - change |= ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync); - #ifndef NDEBUG + change |= ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync); change |= ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi); #endif if (change) { @@ -5100,7 +5367,7 @@ void Navigator::RenderMainPannelSettings() } -void Navigator::RenderTransitionPannel() +void Navigator::RenderTransitionPannel(const ImVec2 &iconsize) { if (Settings::application.current_view != View::TRANSITION) { discardPannel(); @@ -5114,8 +5381,8 @@ void Navigator::RenderTransitionPannel() if (ImGui::Begin("##navigatorTrans", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { // TITLE - ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::SetCursorPosY(0.5f * (iconsize.y - ImGui::GetTextLineHeight())); ImGui::Text("Transition"); ImGui::PopFont(); @@ -5170,8 +5437,10 @@ void Navigator::RenderTransitionPannel() } } -void Navigator::RenderMainPannel() +void Navigator::RenderMainPannel(const ImVec2 &iconsize) { + const ImGuiStyle& style = ImGui::GetStyle(); + if (Settings::application.current_view == View::TRANSITION) return; @@ -5185,15 +5454,66 @@ void Navigator::RenderMainPannel() ImGui::SetScrollX(0); // - // Panel content depends on show_config_ + // TITLE // - if (show_config_) - RenderMainPannelSettings(); - else - RenderMainPannelVimix(); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::SetCursorPosY(0.5f * (iconsize.y - ImGui::GetTextLineHeight())); + ImGui::Text("Vimix"); // - // Icon and About vimix + // Panel Mode selector + // + // + ImGui::SetCursorPosY(width_ - style.WindowPadding.x); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); + ImGui::Columns(5, NULL, false); + bool selected_panel_mode[5] = {0}; + selected_panel_mode[pannel_main_mode_] = true; + if (ImGuiToolkit::SelectableIcon( 7, 1, "##SESSION_FILE", selected_panel_mode[0], iconsize)) + Settings::application.pannel_main_mode = pannel_main_mode_ = 0; + ImGui::NextColumn(); + if (ImGuiToolkit::SelectableIcon( 4, 8, "##SESSION_PLAYLIST", selected_panel_mode[1], iconsize)) + Settings::application.pannel_main_mode = pannel_main_mode_ = 1; + ImGui::NextColumn(); + if (ImGuiToolkit::SelectableIcon( 13, 5, "##SETTINGS", selected_panel_mode[2], iconsize)) + pannel_main_mode_ = 2; + ImGui::Columns(1); + ImGui::PopStyleVar(); + ImGui::PopFont(); + + // + // Panel Menu + // + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN) ); + if (ImGui::BeginMenu("File")) { + UserInterface::manager().showMenuFile(); + ImGui::EndMenu(); + } + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN + ImGui::GetTextLineHeightWithSpacing()) ); + if (ImGui::BeginMenu("Edit")) { + UserInterface::manager().showMenuEdit(); + ImGui::EndMenu(); + } + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN + 2.f * ImGui::GetTextLineHeightWithSpacing()) ); + if (ImGui::BeginMenu("View")) { + UserInterface::manager().showMenuWindows(); + ImGui::EndMenu(); + } + + ImGui::SetCursorPosY(2.f * width_ - style.WindowPadding.x); + + // + // Panel content + // + if (pannel_main_mode_ == 0) + RenderMainPannelSession(); + else if (pannel_main_mode_ == 1) + RenderMainPannelPlaylist(); + else + RenderMainPannelSettings(); + + // + // About vimix // ImGuiContext& g = *GImGui; const ImVec2 rightcorner(pannel_width_ + width_, height_); @@ -5221,13 +5541,6 @@ void Navigator::RenderMainPannel() ImGui::PopStyleColor(); } - // - // Settings icon (non scollable) in Bottom-right corner - // - ImGui::SetCursorScreenPos( rightcorner - ImVec2(button_height, button_height)); - const char *tooltip[2] = {"Settings", "Settings"}; - ImGuiToolkit::IconToggle(13,5,12,5, &show_config_, tooltip); - ImGui::End(); } } diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index 54541ce..eafafed 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -16,6 +16,7 @@ #include "TimerMetronomeWindow.h" #include "InputMappingWindow.h" #include "ShaderEditWindow.h" +#include "Playlist.h" struct ImVec2; @@ -48,8 +49,8 @@ class Navigator float padding_width_; // behavior pannel - bool show_config_; bool pannel_visible_; + int pannel_main_mode_; float pannel_alpha_; bool view_pannel_visible; bool selected_button[NAV_COUNT]; @@ -63,12 +64,13 @@ class Navigator void applyButtonSelection(int index); // side pannels - void RenderSourcePannel(Source *s); - void RenderMainPannel(); - void RenderMainPannelVimix(); + void RenderSourcePannel(Source *s, const ImVec2 &iconsize); + void RenderMainPannel(const ImVec2 &iconsize); + void RenderMainPannelSession(); + void RenderMainPannelPlaylist(); void RenderMainPannelSettings(); - void RenderTransitionPannel(); - void RenderNewPannel(); + void RenderTransitionPannel(const ImVec2 &iconsize); + void RenderNewPannel(const ImVec2 &iconsize); void RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 &size); void RenderMousePointerSelector(const ImVec2 &size); @@ -198,6 +200,10 @@ protected: DialogToolkit::OpenSessionDialog *sessionimportdialog; DialogToolkit::SaveSessionDialog *sessionsavedialog; + // Favorites and playlists + Playlist favorites; + std::string playlists_path; + // objects and windows Navigator navigator; ToolBox toolbox; @@ -209,6 +215,7 @@ protected: void showMenuFile(); void showMenuEdit(); + void showMenuWindows(); bool saveOrSaveAs(bool force_versioning = false); void selectSaveFilename(); void selectOpenFilename(); diff --git a/src/defines.h b/src/defines.h index dd0918c..1e92827 100644 --- a/src/defines.h +++ b/src/defines.h @@ -15,6 +15,8 @@ #define VIMIX_FILE_EXT "mix" #define VIMIX_FILE_PATTERN "*.mix" +#define VIMIX_PLAYLIST_FILE_EXT "lix" +#define VIMIX_PLAYLIST_FILE_PATTERN "*.lix" #define MEDIA_FILES_PATTERN "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",\ "*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",\ "*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",\ @@ -140,7 +142,7 @@ #define ALT_LOCK " ALT LOCK" #endif -#define MENU_NEW_FILE ICON_FA_FILE " New" +#define MENU_NEW_FILE ICON_FA_FILE "+ New" #define SHORTCUT_NEW_FILE CTRL_MOD "W" #define MENU_OPEN_FILE ICON_FA_FILE_UPLOAD " Open" #define SHORTCUT_OPEN_FILE CTRL_MOD "O" @@ -186,19 +188,22 @@ #define MENU_CLOSE ICON_FA_TIMES " Close" #define DIALOG_FAILED_SOURCE ICON_FA_EXCLAMATION_TRIANGLE " Source failure" -#define MENU_NOTE ICON_FA_STICKY_NOTE " Add sticky note" #define MENU_METRICS ICON_FA_TACHOMETER_ALT " Metrics" #define MENU_SOURCE_TOOL ICON_FA_WRENCH " Source toolbar" #define MENU_HELP ICON_FA_LIFE_RING " Help" #define SHORTCUT_HELP CTRL_MOD "H" #define MENU_LOGS ICON_FA_LIST_UL " Logs" #define SHORTCUT_LOGS CTRL_MOD "L" +#define MENU_PLAYER ICON_FA_PLAY_CIRCLE " Player " #define TOOLTIP_PLAYER "Player " #define SHORTCUT_PLAYER CTRL_MOD "P" +#define MENU_OUTPUT ICON_FA_DESKTOP " Display " #define TOOLTIP_OUTPUT "Display " #define SHORTCUT_OUTPUT CTRL_MOD "D" +#define MENU_TIMER ICON_FA_CLOCK " Timer " #define TOOLTIP_TIMER "Timer " #define SHORTCUT_TIMER CTRL_MOD "T" +#define MENU_INPUTS ICON_FA_HAND_PAPER " Inputs mapping " #define TOOLTIP_INPUTS "Inputs mapping " #define SHORTCUT_INPUTS CTRL_MOD "I" #define TOOLTIP_SHADEREDITOR "Shader Editor "