diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index a69be9d..f9aafa8 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 68d8eb7..8fe7b1b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,7 @@ set(VMIX_SRCS ScreenCaptureSource.cpp MousePointer.cpp Grid.cpp + Playlist.cpp ) ##### diff --git a/src/DialogToolkit.cpp b/src/DialogToolkit.cpp index 9488a33..5e592a8 100644 --- a/src/DialogToolkit.cpp +++ b/src/DialogToolkit.cpp @@ -140,6 +140,54 @@ void DialogToolkit::OpenSessionDialog::open() } } +std::list 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 "