mirror of https://github.com/aseprite/aseprite.git
				
				
				
			Merge branch 'recent-files-improvements' (fix #578)
This commit is contained in:
		
						commit
						f5c0d667c9
					
				|  | @ -654,6 +654,15 @@ | |||
|         <style id="recent_file_detail" border="2" border-left="0" extends="recent_file"> | ||||
|             <text color="disabled" align="left" x="2" /> | ||||
|         </style> | ||||
|         <style id="recent_file_pin"> | ||||
|           <background color="background" /> | ||||
|           <background color="menuitem_hot_face" state="mouse" /> | ||||
|           <background color="listitem_selected_face" state="selected" /> | ||||
|           <icon part="unpinned" align="center middle" color="text" /> | ||||
|           <icon part="unpinned" align="center middle" color="listitem_selected_text" state="selected" /> | ||||
|           <icon part="pinned" align="center middle" color="text" state="focus" /> | ||||
|           <icon part="pinned" align="center middle" color="listitem_selected_text" state="focus selected" /> | ||||
|         </style> | ||||
|         <style id="news_item" border="2"> | ||||
|             <background color="background" /> | ||||
|             <background color="menuitem_hot_face" state="mouse" /> | ||||
|  |  | |||
|  | @ -545,6 +545,8 @@ go_up_button_tooltip = Up to parent folder | |||
| new_folder_button_tooltip = New folder | ||||
| file_name = File name: | ||||
| file_type = File type: | ||||
| pinned_folders = Pinned Folders | ||||
| recent_folders = Recent Folders | ||||
| 
 | ||||
| [filters] | ||||
| selected_cels = Selected | ||||
|  |  | |||
							
								
								
									
										2
									
								
								laf
								
								
								
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								laf
								
								
								
								
							|  | @ -1 +1 @@ | |||
| Subproject commit 950f62c1ccff7cf59c220e9016ead0845767e546 | ||||
| Subproject commit 75f29b27c661e66e1549dc6e6a172f284cd9326b | ||||
|  | @ -474,17 +474,20 @@ bool AppMenus::rebuildRecentList() | |||
|     submenu = new Menu(); | ||||
|     list_menuitem->setSubmenu(submenu); | ||||
| 
 | ||||
|     auto it = App::instance()->recentFiles()->files_begin(); | ||||
|     auto end = App::instance()->recentFiles()->files_end(); | ||||
|     if (it != end) { | ||||
|     auto recent = App::instance()->recentFiles(); | ||||
|     base::paths files; | ||||
|     files.insert(files.end(), | ||||
|                  recent->pinnedFiles().begin(), | ||||
|                  recent->pinnedFiles().end()); | ||||
|     files.insert(files.end(), | ||||
|                  recent->recentFiles().begin(), | ||||
|                  recent->recentFiles().end()); | ||||
|     if (!files.empty()) { | ||||
|       Params params; | ||||
| 
 | ||||
|       for (; it != end; ++it) { | ||||
|         const char* filename = it->c_str(); | ||||
|         params.set("filename", filename); | ||||
| 
 | ||||
|       for (const auto& fn : files) { | ||||
|         params.set("filename", fn.c_str()); | ||||
|         menuitem = new AppMenuItem( | ||||
|           base::get_file_name(filename).c_str(), | ||||
|           base::get_file_name(fn).c_str(), | ||||
|           cmd_open_file, | ||||
|           params); | ||||
|         submenu->addChild(menuitem); | ||||
|  |  | |||
|  | @ -274,4 +274,11 @@ void del_config_value(const char* section, const char* name) | |||
|   g_configs.back()->deleteValue(section, name); | ||||
| } | ||||
| 
 | ||||
| base::paths enum_config_keys(const char* section) | ||||
| { | ||||
|   base::paths keys; | ||||
|   g_configs.back()->getAllKeys(section, keys); | ||||
|   return keys; | ||||
| } | ||||
| 
 | ||||
| } // namespace app
 | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "app/color.h" | ||||
| #include "base/paths.h" | ||||
| #include "gfx/point.h" | ||||
| #include "gfx/rect.h" | ||||
| #include "gfx/size.h" | ||||
|  | @ -58,6 +59,8 @@ namespace app { | |||
| 
 | ||||
|   void del_config_value(const char* section, const char* name); | ||||
| 
 | ||||
|   base::paths enum_config_keys(const char* section); | ||||
| 
 | ||||
|   // Generic get/set_config_value functions
 | ||||
| 
 | ||||
|   inline const char* get_config_value(const char* section, const char* name, const char* value) { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -11,8 +12,8 @@ | |||
| #include "app/recent_files.h" | ||||
| 
 | ||||
| #include "app/ini_file.h" | ||||
| #include "base/convert_to.h" | ||||
| #include "base/fs.h" | ||||
| #include "fmt/format.h" | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <cstring> | ||||
|  | @ -20,8 +21,10 @@ | |||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| const char* kRecentFilesSection = "RecentFiles"; | ||||
| const char* kRecentFoldersSection = "RecentPaths"; | ||||
| const char* kSectionName[] = { "PinnedFiles", | ||||
|                                "RecentFiles", | ||||
|                                "PinnedPaths", | ||||
|                                "RecentPaths" }; | ||||
| 
 | ||||
| struct compare_path { | ||||
|   std::string a; | ||||
|  | @ -31,81 +34,40 @@ struct compare_path { | |||
|   } | ||||
| }; | ||||
| 
 | ||||
| std::string format_filename_var(const int i) { | ||||
|   return fmt::format("Filename{0:02d}", i); | ||||
| } | ||||
| 
 | ||||
| std::string format_folder_var(const int i) { | ||||
|   return fmt::format("Path{0:02d}", i); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| namespace app { | ||||
| 
 | ||||
| RecentFiles::RecentFiles(const int limit) | ||||
|   : m_files(limit) | ||||
|   , m_paths(limit) | ||||
|   : m_limit(limit) | ||||
| { | ||||
|   for (int c=m_files.limit()-1; c>=0; c--) { | ||||
|     const char* filename = get_config_string(kRecentFilesSection, | ||||
|                                              format_filename_var(c).c_str(), | ||||
|                                              nullptr); | ||||
|     if (filename && *filename && base::is_file(filename)) { | ||||
|       std::string fn = normalizePath(filename); | ||||
|       m_files.addItem(fn, compare_path(fn)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   for (int c=m_paths.limit()-1; c>=0; c--) { | ||||
|     const char* path = get_config_string(kRecentFoldersSection, | ||||
|                                          format_folder_var(c).c_str(), | ||||
|                                          nullptr); | ||||
|     if (path && *path) { | ||||
|       std::string p = normalizePath(path); | ||||
|       m_paths.addItem(p, compare_path(p)); | ||||
|     } | ||||
|   } | ||||
|   load(); | ||||
| } | ||||
| 
 | ||||
| RecentFiles::~RecentFiles() | ||||
| { | ||||
|   // Save recent files
 | ||||
| 
 | ||||
|   int c = 0; | ||||
|   for (auto const& filename : m_files) { | ||||
|     set_config_string(kRecentFilesSection, | ||||
|                       format_filename_var(c).c_str(), | ||||
|                       filename.c_str()); | ||||
|     ++c; | ||||
|   } | ||||
|   for (; c<m_files.limit(); ++c) { | ||||
|     del_config_value(kRecentFilesSection, | ||||
|                      format_filename_var(c).c_str()); | ||||
|   } | ||||
| 
 | ||||
|   // Save recent folders
 | ||||
| 
 | ||||
|   c = 0; | ||||
|   for (auto const& path : m_paths) { | ||||
|     set_config_string(kRecentFoldersSection, | ||||
|                       format_folder_var(c).c_str(), | ||||
|                       path.c_str()); | ||||
|     ++c; | ||||
|   } | ||||
|   for (; c<m_files.limit(); ++c) { | ||||
|     del_config_value(kRecentFoldersSection, | ||||
|                      format_folder_var(c).c_str()); | ||||
|   } | ||||
|   save(); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::addRecentFile(const std::string& filename) | ||||
| { | ||||
|   std::string fn = normalizePath(filename); | ||||
|   m_files.addItem(fn, compare_path(fn)); | ||||
| 
 | ||||
|   // If the filename is already pinned, we don't add it in the
 | ||||
|   // collection of recent files collection.
 | ||||
|   auto it = std::find(m_paths[kPinnedFiles].begin(), | ||||
|                       m_paths[kPinnedFiles].end(), fn); | ||||
|   if (it != m_paths[kPinnedFiles].end()) | ||||
|     return; | ||||
|   addItem(m_paths[kRecentFiles], fn); | ||||
| 
 | ||||
|   // Add recent folder
 | ||||
|   std::string path = base::get_file_path(fn); | ||||
|   m_paths.addItem(path, compare_path(path)); | ||||
|   it = std::find(m_paths[kPinnedFolders].begin(), | ||||
|                  m_paths[kPinnedFolders].end(), path); | ||||
|   if (it == m_paths[kPinnedFolders].end()) { | ||||
|     addItem(m_paths[kRecentFolders], path); | ||||
|   } | ||||
| 
 | ||||
|   Changed(); | ||||
| } | ||||
|  | @ -113,7 +75,7 @@ void RecentFiles::addRecentFile(const std::string& filename) | |||
| void RecentFiles::removeRecentFile(const std::string& filename) | ||||
| { | ||||
|   std::string fn = normalizePath(filename); | ||||
|   m_files.removeItem(fn, compare_path(fn)); | ||||
|   removeItem(m_paths[kRecentFiles], fn); | ||||
| 
 | ||||
|   std::string dir = base::get_file_path(fn); | ||||
|   if (!base::is_directory(dir)) | ||||
|  | @ -125,30 +87,109 @@ void RecentFiles::removeRecentFile(const std::string& filename) | |||
| void RecentFiles::removeRecentFolder(const std::string& dir) | ||||
| { | ||||
|   std::string fn = normalizePath(dir); | ||||
|   m_paths.removeItem(fn, compare_path(fn)); | ||||
|   removeItem(m_paths[kRecentFolders], fn); | ||||
| 
 | ||||
|   Changed(); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::setLimit(const int n) | ||||
| void RecentFiles::setLimit(const int newLimit) | ||||
| { | ||||
|   m_files.setLimit(n); | ||||
|   m_paths.setLimit(n); | ||||
|   ASSERT(newLimit >= 0); | ||||
| 
 | ||||
|   for (auto& list : m_paths) { | ||||
|     if (newLimit < list.size()) { | ||||
|       auto it = list.begin(); | ||||
|       std::advance(it, newLimit); | ||||
|       list.erase(it, list.end()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   m_limit = newLimit; | ||||
|   Changed(); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::clear() | ||||
| { | ||||
|   m_files.clear(); | ||||
|   m_paths.clear(); | ||||
|   // Clear only recent items (not pinned items)
 | ||||
|   m_paths[kRecentFiles].clear(); | ||||
|   m_paths[kRecentFolders].clear(); | ||||
| 
 | ||||
|   Changed(); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::setFiles(const base::paths& pinnedFiles, | ||||
|                            const base::paths& recentFiles) | ||||
| { | ||||
|   m_paths[kPinnedFiles] = pinnedFiles; | ||||
|   m_paths[kRecentFiles] = recentFiles; | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::setFolders(const base::paths& pinnedFolders, | ||||
|                              const base::paths& recentFolders) | ||||
| { | ||||
|   m_paths[kPinnedFolders] = pinnedFolders; | ||||
|   m_paths[kRecentFolders] = recentFolders; | ||||
| } | ||||
| 
 | ||||
| std::string RecentFiles::normalizePath(const std::string& filename) | ||||
| { | ||||
|   return base::normalize_path(filename); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::addItem(base::paths& list, const std::string& fn) | ||||
| { | ||||
|   auto it = std::find_if(list.begin(), list.end(), compare_path(fn)); | ||||
| 
 | ||||
|   // If the item already exist in the list...
 | ||||
|   if (it != list.end()) { | ||||
|     // Move it to the first position
 | ||||
|     list.erase(it); | ||||
|     list.insert(list.begin(), fn); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (m_limit > 0) | ||||
|     list.insert(list.begin(), fn); | ||||
| 
 | ||||
|   while (list.size() > m_limit) | ||||
|     list.erase(--list.end()); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::removeItem(base::paths& list, const std::string& fn) | ||||
| { | ||||
|   auto it = std::find_if(list.begin(), list.end(), compare_path(fn)); | ||||
|   if (it != list.end()) | ||||
|     list.erase(it); | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::load() | ||||
| { | ||||
|   for (int i=0; i<kCollections; ++i) { | ||||
|     for (const auto& key : enum_config_keys(kSectionName[i])) { | ||||
|       const char* fn = get_config_string(kSectionName[i], key.c_str(), nullptr); | ||||
|       if (fn && *fn && | ||||
|           ((i < 2 && base::is_file(fn)) || | ||||
|            (i >= 2 && base::is_directory(fn)))) { | ||||
|         std::string normalFn = normalizePath(fn); | ||||
|         m_paths[i].push_back(normalFn); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void RecentFiles::save() | ||||
| { | ||||
|   for (int i=0; i<kCollections; ++i) { | ||||
|     for (const auto& key : enum_config_keys(kSectionName[i])) | ||||
|       del_config_value(kSectionName[i], | ||||
|                        key.c_str()); | ||||
| 
 | ||||
|     for (int j=0; j<m_paths[i].size(); ++j) { | ||||
|       set_config_string(kSectionName[i], | ||||
|                         base::convert_to<std::string>(j).c_str(), | ||||
|                         m_paths[i][j].c_str()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| } // namespace app
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -8,7 +9,7 @@ | |||
| #define APP_RECENT_FILES_H_INCLUDED | ||||
| #pragma once | ||||
| 
 | ||||
| #include "base/recent_items.h" | ||||
| #include "base/paths.h" | ||||
| #include "obs/signal.h" | ||||
| 
 | ||||
| #include <string> | ||||
|  | @ -16,18 +17,16 @@ | |||
| namespace app { | ||||
| 
 | ||||
|   class RecentFiles { | ||||
|     enum { kPinnedFiles, | ||||
|            kRecentFiles, | ||||
|            kPinnedFolders, | ||||
|            kRecentFolders, | ||||
|            kCollections }; | ||||
|   public: | ||||
|     typedef base::RecentItems<std::string> List; | ||||
|     typedef List::iterator iterator; | ||||
|     typedef List::const_iterator const_iterator; | ||||
| 
 | ||||
|     // Iterate through recent files.
 | ||||
|     const_iterator files_begin() { return m_files.begin(); } | ||||
|     const_iterator files_end() { return m_files.end(); } | ||||
| 
 | ||||
|     // Iterate through recent paths.
 | ||||
|     const_iterator paths_begin() { return m_paths.begin(); } | ||||
|     const_iterator paths_end() { return m_paths.end(); } | ||||
|     const base::paths& pinnedFiles() { return m_paths[kPinnedFiles]; } | ||||
|     const base::paths& recentFiles() { return m_paths[kRecentFiles]; } | ||||
|     const base::paths& pinnedFolders() { return m_paths[kPinnedFolders]; } | ||||
|     const base::paths& recentFolders() { return m_paths[kRecentFolders]; } | ||||
| 
 | ||||
|     RecentFiles(const int limit); | ||||
|     ~RecentFiles(); | ||||
|  | @ -35,16 +34,25 @@ namespace app { | |||
|     void addRecentFile(const std::string& filename); | ||||
|     void removeRecentFile(const std::string& filename); | ||||
|     void removeRecentFolder(const std::string& dir); | ||||
|     void setLimit(const int n); | ||||
|     void setLimit(const int newLimit); | ||||
|     void clear(); | ||||
| 
 | ||||
|     void setFiles(const base::paths& pinnedFiles, | ||||
|                   const base::paths& recentFiles); | ||||
|     void setFolders(const base::paths& pinnedFolders, | ||||
|                     const base::paths& recentFolders); | ||||
| 
 | ||||
|     obs::signal<void()> Changed; | ||||
| 
 | ||||
|   private: | ||||
|     std::string normalizePath(const std::string& filename); | ||||
|     void addItem(base::paths& list, const std::string& filename); | ||||
|     void removeItem(base::paths& list, const std::string& filename); | ||||
|     void load(); | ||||
|     void save(); | ||||
| 
 | ||||
|     List m_files; | ||||
|     List m_paths; | ||||
|     base::paths m_paths[kCollections]; | ||||
|     int m_limit; | ||||
|   }; | ||||
| 
 | ||||
| } // namespace app
 | ||||
|  |  | |||
|  | @ -0,0 +1,222 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
| // the End-User License Agreement for Aseprite.
 | ||||
| 
 | ||||
| #ifndef APP_UI_DRAGGABLE_WIDGET_H_INCLUDED | ||||
| #define APP_UI_DRAGGABLE_WIDGET_H_INCLUDED | ||||
| #pragma once | ||||
| 
 | ||||
| #include "os/surface.h" | ||||
| #include "os/system.h" | ||||
| #include "ui/graphics.h" | ||||
| #include "ui/message.h" | ||||
| #include "ui/overlay.h" | ||||
| #include "ui/overlay_manager.h" | ||||
| #include "ui/paint_event.h" | ||||
| #include "ui/system.h" | ||||
| #include "ui/view.h" | ||||
| 
 | ||||
| namespace app { | ||||
| 
 | ||||
| template<typename Base> | ||||
| class DraggableWidget : public Base { | ||||
| public: | ||||
|   template<typename...Args> | ||||
|   DraggableWidget(Args...args) : Base(args...) { } | ||||
| 
 | ||||
|   bool onProcessMessage(ui::Message* msg) override { | ||||
|     switch (msg->type()) { | ||||
| 
 | ||||
|       case ui::kSetCursorMessage: | ||||
|         if (m_floatingOverlay) { | ||||
|           const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); | ||||
|           const gfx::Point mousePos = mouseMsg->position(); | ||||
|           if (onCanDropItemsOutside() && | ||||
|               !getParentBounds().contains(mousePos)) { | ||||
|             ui::set_mouse_cursor(ui::kForbiddenCursor); | ||||
|           } | ||||
|           else { | ||||
|             ui::set_mouse_cursor(ui::kMoveCursor); | ||||
|           } | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case ui::kMouseDownMessage: { | ||||
|         const bool wasCaptured = this->hasCapture(); | ||||
|         const bool result = Base::onProcessMessage(msg); | ||||
| 
 | ||||
|         if (!wasCaptured && this->hasCapture()) { | ||||
|           const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); | ||||
|           const gfx::Point mousePos = mouseMsg->position(); | ||||
|           m_dragMousePos = mousePos; | ||||
|           m_floatingOffset = mouseMsg->position() - this->bounds().origin(); | ||||
|           m_createFloatingOverlay = true; | ||||
|         } | ||||
|         return result; | ||||
|       } | ||||
| 
 | ||||
|       case ui::kMouseMoveMessage: { | ||||
|         const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); | ||||
|         const gfx::Point mousePos = mouseMsg->position(); | ||||
| 
 | ||||
|         if (this->hasCapture() && m_createFloatingOverlay) { | ||||
|           if (this->manager()->pick(mousePos) != this) { | ||||
|             m_createFloatingOverlay = false; | ||||
|             if (!m_floatingOverlay) | ||||
|               createFloatingOverlay(); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         if (m_floatingOverlay) { | ||||
|           m_floatingOverlay->moveOverlay(mousePos - m_floatingOffset); | ||||
| 
 | ||||
|           bool inside = true; | ||||
|           if (onCanDropItemsOutside()) { | ||||
|             inside = getParentBounds().contains(mousePos); | ||||
|             if (inside) { | ||||
|               if (this->hasFlags(ui::HIDDEN)) { | ||||
|                 this->disableFlags(ui::HIDDEN); | ||||
|                 layoutParent(); | ||||
|               } | ||||
|             } | ||||
|             else { | ||||
|               if (!this->hasFlags(ui::HIDDEN)) { | ||||
|                 this->enableFlags(ui::HIDDEN); | ||||
|                 layoutParent(); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           onReorderWidgets(mousePos, inside); | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
| 
 | ||||
|       case ui::kMouseUpMessage: { | ||||
|         const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); | ||||
|         const gfx::Point mousePos = mouseMsg->position(); | ||||
| 
 | ||||
|         m_wasDragged = (this->hasCapture() && m_floatingOverlay); | ||||
|         const bool result = Base::onProcessMessage(msg); | ||||
| 
 | ||||
|         if (!this->hasCapture()) { | ||||
|           if (m_floatingOverlay) { | ||||
|             destroyFloatingOverlay(); | ||||
|             ASSERT(!m_createFloatingOverlay); | ||||
|             onFinalDrop(getParentBounds().contains(mousePos)); | ||||
|           } | ||||
|           else if (m_createFloatingOverlay) | ||||
|             m_createFloatingOverlay = false; | ||||
|         } | ||||
| 
 | ||||
|         m_wasDragged = false; | ||||
|         return result; | ||||
|       } | ||||
| 
 | ||||
|     } | ||||
|     return Base::onProcessMessage(msg); | ||||
|   } | ||||
| 
 | ||||
|   bool wasDragged() const { | ||||
|     return m_wasDragged; | ||||
|   } | ||||
| 
 | ||||
|   bool isDragging() const { | ||||
|     return m_isDragging; | ||||
|   } | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|   void createFloatingOverlay() { | ||||
|     ASSERT(!m_floatingOverlay); | ||||
| 
 | ||||
|     m_isDragging = true; | ||||
| 
 | ||||
|     gfx::Size sz = getFloatingOverlaySize(); | ||||
|     sz.w = std::max(1, sz.w); | ||||
|     sz.h = std::max(1, sz.h); | ||||
|     os::Surface* surface = os::instance()->createRgbaSurface(sz.w, sz.h); | ||||
| 
 | ||||
|     { | ||||
|       os::SurfaceLock lock(surface); | ||||
|       surface->fillRect(gfx::rgba(0, 0, 0, 0), | ||||
|                         gfx::Rect(0, 0, surface->width(), surface->height())); | ||||
|     } | ||||
|     { | ||||
|       ui::Graphics g(surface, 0, 0); | ||||
|       g.setFont(this->font()); | ||||
|       drawFloatingOverlay(g); | ||||
|     } | ||||
| 
 | ||||
|     m_floatingOverlay.reset(new ui::Overlay(surface, gfx::Point(), | ||||
|                                             ui::Overlay::MouseZOrder-1)); | ||||
|     ui::OverlayManager::instance()->addOverlay(m_floatingOverlay.get()); | ||||
|   } | ||||
| 
 | ||||
|   void destroyFloatingOverlay() { | ||||
|     ui::OverlayManager::instance()->removeOverlay(m_floatingOverlay.get()); | ||||
|     m_floatingOverlay.reset(); | ||||
|     m_isDragging = false; | ||||
|   } | ||||
| 
 | ||||
|   gfx::Size getFloatingOverlaySize() { | ||||
|     auto view = ui::View::getView(this->parent()); | ||||
|     if (view) | ||||
|       return (view->viewportBounds().offset(view->viewScroll()) & this->bounds()).size(); | ||||
|     else | ||||
|       return this->size(); | ||||
|   } | ||||
| 
 | ||||
|   gfx::Rect getParentBounds() { | ||||
|     auto view = ui::View::getView(this->parent()); | ||||
|     if (view) | ||||
|       return view->viewportBounds(); | ||||
|     else | ||||
|       return this->parent()->bounds(); | ||||
|   } | ||||
| 
 | ||||
|   void layoutParent() { | ||||
|     this->parent()->layout(); | ||||
|     auto view = ui::View::getView(this->parent()); | ||||
|     if (view) | ||||
|       return view->updateView(); | ||||
|   } | ||||
| 
 | ||||
|   void drawFloatingOverlay(ui::Graphics& g) { | ||||
|     ui::PaintEvent ev(this, &g); | ||||
|     this->onPaint(ev); | ||||
|   } | ||||
| 
 | ||||
|   virtual bool onCanDropItemsOutside() { return true; } | ||||
|   virtual void onReorderWidgets(const gfx::Point& mousePos, bool inside) { } | ||||
|   virtual void onFinalDrop(bool inside) { } | ||||
| 
 | ||||
|   // True if we should create the floating overlay after leaving the
 | ||||
|   // widget bounds.
 | ||||
|   bool m_createFloatingOverlay = false; | ||||
| 
 | ||||
|   bool m_isDragging = false; | ||||
| 
 | ||||
|   // True when the mouse button is released (drop operation) and we've
 | ||||
|   // dragged the widget to other position. Can be used to avoid
 | ||||
|   // triggering the default click operation by derived classes when
 | ||||
|   // we've dragged the widget.
 | ||||
|   bool m_wasDragged = false; | ||||
| 
 | ||||
|   // Initial mouse position when we start the dragging process.
 | ||||
|   gfx::Point m_dragMousePos; | ||||
| 
 | ||||
|   // Overlay used to show the floating widget (this overlay floats
 | ||||
|   // next to the mouse cursor).
 | ||||
|   std::unique_ptr<ui::Overlay> m_floatingOverlay; | ||||
| 
 | ||||
|   // Relative mouse position between the widget and the overlay.
 | ||||
|   gfx::Point m_floatingOffset; | ||||
| }; | ||||
| 
 | ||||
| } // namespace app
 | ||||
| 
 | ||||
| #endif | ||||
|  | @ -121,8 +121,6 @@ void EditorView::onSetViewScroll(const gfx::Point& pt) | |||
| 
 | ||||
| void EditorView::onScrollRegion(ui::ScrollRegionEvent& ev) | ||||
| { | ||||
|   View::onScrollRegion(ev); | ||||
| 
 | ||||
|   gfx::Region& region = ev.region(); | ||||
|   Editor* editor = this->editor(); | ||||
|   ASSERT(editor); | ||||
|  |  | |||
|  | @ -17,8 +17,6 @@ namespace app { | |||
| 
 | ||||
| void FileListView::onScrollRegion(ui::ScrollRegionEvent& ev) | ||||
| { | ||||
|   View::onScrollRegion(ev); | ||||
| 
 | ||||
|   if (auto fileList = dynamic_cast<FileList*>(attachedWidget())) { | ||||
|     gfx::Rect tbounds = fileList->thumbnailBounds(); | ||||
|     if (!tbounds.isEmpty()) { | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
| #include "app/recent_files.h" | ||||
| #include "app/ui/file_list.h" | ||||
| #include "app/ui/file_list_view.h" | ||||
| #include "app/ui/separator_in_view.h" | ||||
| #include "app/ui/skin/skin_theme.h" | ||||
| #include "app/widget_loader.h" | ||||
| #include "base/bind.h" | ||||
|  | @ -36,6 +37,7 @@ | |||
| #include <cctype> | ||||
| #include <cerrno> | ||||
| #include <iterator> | ||||
| #include <list> | ||||
| #include <set> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | @ -675,14 +677,20 @@ void FileSelector::updateLocation() | |||
|   } | ||||
| 
 | ||||
|   // Add paths from recent files list
 | ||||
|   { | ||||
|     location()->addItem(""); | ||||
|     location()->addItem("-------- Recent Paths --------"); | ||||
| 
 | ||||
|     auto it = App::instance()->recentFiles()->paths_begin(); | ||||
|     auto end = App::instance()->recentFiles()->paths_end(); | ||||
|     for (; it != end; ++it) | ||||
|       location()->addItem(new CustomFolderNameItem(it->c_str())); | ||||
|   auto recent = App::instance()->recentFiles(); | ||||
|   if (!recent->pinnedFolders().empty()) { | ||||
|     auto sep = new SeparatorInView(Strings::file_selector_pinned_folders(), HORIZONTAL); | ||||
|     sep->setMinSize(gfx::Size(0, sep->sizeHint().h*2)); | ||||
|     location()->addItem(sep); | ||||
|     for (const auto& fn : recent->pinnedFolders()) | ||||
|       location()->addItem(new CustomFolderNameItem(fn.c_str())); | ||||
|   } | ||||
|   if (!recent->recentFolders().empty()) { | ||||
|     auto sep = new SeparatorInView(Strings::file_selector_recent_folders(), HORIZONTAL); | ||||
|     sep->setMinSize(gfx::Size(0, sep->sizeHint().h*2)); | ||||
|     location()->addItem(sep); | ||||
|     for (const auto& fn : recent->recentFolders()) | ||||
|       location()->addItem(new CustomFolderNameItem(fn.c_str())); | ||||
|   } | ||||
| 
 | ||||
|   // Select the location
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -16,6 +17,7 @@ | |||
| #include "app/i18n/strings.h" | ||||
| #include "app/pref/preferences.h" | ||||
| #include "app/recent_files.h" | ||||
| #include "app/ui/draggable_widget.h" | ||||
| #include "app/ui/skin/skin_theme.h" | ||||
| #include "app/ui_context.h" | ||||
| #include "base/bind.h" | ||||
|  | @ -26,6 +28,7 @@ | |||
| #include "ui/listitem.h" | ||||
| #include "ui/message.h" | ||||
| #include "ui/paint_event.h" | ||||
| #include "ui/scroll_region_event.h" | ||||
| #include "ui/size_hint_event.h" | ||||
| #include "ui/system.h" | ||||
| #include "ui/view.h" | ||||
|  | @ -38,16 +41,30 @@ using namespace skin; | |||
| //////////////////////////////////////////////////////////////////////
 | ||||
| // RecentFileItem
 | ||||
| 
 | ||||
| class RecentFileItem : public LinkLabel { | ||||
| class RecentFileItem : public DraggableWidget<LinkLabel> { | ||||
| public: | ||||
|   RecentFileItem(const std::string& file) | ||||
|     : LinkLabel("") | ||||
|   RecentFileItem(const std::string& file, | ||||
|                  const bool pinned) | ||||
|     : DraggableWidget<LinkLabel>("") | ||||
|     , m_fullpath(file) | ||||
|     , m_name(base::get_file_name(file)) | ||||
|     , m_path(base::get_file_path(file)) { | ||||
|     , m_path(base::get_file_path(file)) | ||||
|     , m_pinned(pinned) { | ||||
|     initTheme(); | ||||
|   } | ||||
| 
 | ||||
|   const std::string& fullpath() const { return m_fullpath; } | ||||
|   bool pinned() const { return m_pinned; } | ||||
| 
 | ||||
|   void pin() { | ||||
|     m_pinned = true; | ||||
|     invalidate(); | ||||
|   } | ||||
| 
 | ||||
|   void onScrollRegion(ui::ScrollRegionEvent& ev) { | ||||
|     ev.region() -= gfx::Region(pinBounds(bounds())); | ||||
|   } | ||||
| 
 | ||||
| protected: | ||||
|   void onInitTheme(InitThemeEvent& ev) override { | ||||
|     LinkLabel::onInitTheme(ev); | ||||
|  | @ -68,6 +85,53 @@ protected: | |||
|     ev.setSizeHint(gfx::Size(sz1.w+sz2.w, MAX(sz1.h, sz2.h))); | ||||
|   } | ||||
| 
 | ||||
|   bool onProcessMessage(Message* msg) override { | ||||
|     switch (msg->type()) { | ||||
|       case kMouseDownMessage: { | ||||
|         const gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); | ||||
|         gfx::Rect rc = pinBounds(bounds()); | ||||
|         rc.y = bounds().y; | ||||
|         rc.h = bounds().h; | ||||
|         if (rc.contains(mousePos)) { | ||||
|           m_pinned = !m_pinned; | ||||
|           invalidate(); | ||||
| 
 | ||||
|           auto parent = this->parent(); | ||||
|           const auto& children = parent->children(); | ||||
|           auto end = children.end(); | ||||
|           auto moveTo = parent->firstChild(); | ||||
|           if (m_pinned) { | ||||
|             for (auto it=children.begin(); it != end; ++it) { | ||||
|               if (*it == this || !static_cast<RecentFileItem*>(*it)->pinned()) { | ||||
|                 moveTo = *it; | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           else { | ||||
|             auto it = std::find(children.begin(), end, this); | ||||
|             if (it != end) { | ||||
|               auto prevIt = it++; | ||||
|               for (; it != end; prevIt=it++) { | ||||
|                 if (!static_cast<RecentFileItem*>(*it)->pinned()) | ||||
|                   break; | ||||
|               } | ||||
|               moveTo = *prevIt; | ||||
|             } | ||||
|           } | ||||
|           if (this != moveTo) { | ||||
|             parent->moveChildTo(this, moveTo); | ||||
|             parent->layout(); | ||||
|           } | ||||
|           saveConfig(); | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     return DraggableWidget<LinkLabel>::onProcessMessage(msg); | ||||
|   } | ||||
| 
 | ||||
|   void onPaint(PaintEvent& ev) override { | ||||
|     SkinTheme* theme = static_cast<SkinTheme*>(this->theme()); | ||||
|     Graphics* g = ev.graphics(); | ||||
|  | @ -86,16 +150,80 @@ protected: | |||
|       setTextQuiet(m_path.c_str()); | ||||
|       theme->paintWidget(g, this, styleDetail, detailsBounds); | ||||
|     } | ||||
| 
 | ||||
|     if (!isDragging() && (m_pinned || hasMouse())) { | ||||
|       ui::Style* pinStyle = theme->styles.recentFilePin(); | ||||
|       const gfx::Rect pinBounds = this->pinBounds(bounds); | ||||
|       PaintWidgetPartInfo pi; | ||||
|       pi.styleFlags = | ||||
|         (isSelected() ? ui::Style::Layer::kSelected: 0) | | ||||
|         (m_pinned ? ui::Style::Layer::kFocus: 0) | | ||||
|         (hasMouse() ? ui::Style::Layer::kMouse: 0); | ||||
|       theme->paintWidgetPart(g, pinStyle, pinBounds, pi); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void onClick() override { | ||||
|     if (!wasDragged()) | ||||
|       static_cast<RecentListBox*>(parent())->onClick(m_fullpath); | ||||
|   } | ||||
| 
 | ||||
|   void onReorderWidgets(const gfx::Point& mousePos, bool inside) override { | ||||
|     auto parent = this->parent(); | ||||
|     auto other = manager()->pick(mousePos); | ||||
|     if (other && other != this && other->parent() == parent) { | ||||
|       parent->moveChildTo(this, other); | ||||
|       parent->layout(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void onFinalDrop(bool inside) override { | ||||
|     if (!wasDragged()) | ||||
|       return; | ||||
| 
 | ||||
|     if (inside) { | ||||
|       // Pin all elements to keep the order
 | ||||
|       const auto& children = parent()->children(); | ||||
|       for (auto it=children.rbegin(), end=children.rend(); it!=end; ++it) { | ||||
|         if (this == *it) { | ||||
|           for (; it!=end; ++it) | ||||
|             static_cast<RecentFileItem*>(*it)->pin(); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     else { | ||||
|       setVisible(false); | ||||
|       parent()->layout(); | ||||
|     } | ||||
| 
 | ||||
|     saveConfig(); | ||||
| 
 | ||||
|     if (!inside) | ||||
|       deferDelete(); | ||||
|   } | ||||
| 
 | ||||
| private: | ||||
|   gfx::Rect pinBounds(const gfx::Rect& bounds) { | ||||
|     SkinTheme* theme = static_cast<SkinTheme*>(this->theme()); | ||||
|     ui::Style* pinStyle = theme->styles.recentFilePin(); | ||||
|     ui::View* view = View::getView(parent()); | ||||
|     const gfx::Size pinSize = theme->calcSizeHint(this, pinStyle); | ||||
|     const gfx::Rect vp = view->viewportBounds(); | ||||
|     const gfx::Point scroll = view->viewScroll(); | ||||
|     return gfx::Rect(scroll.x+bounds.x+vp.w-pinSize.w, | ||||
|                      bounds.y+bounds.h/2-pinSize.h/2, | ||||
|                      pinSize.w, pinSize.h); | ||||
|   } | ||||
| 
 | ||||
|   void saveConfig() { | ||||
|     static_cast<RecentListBox*>(parent())->updateRecentListFromUIItems(); | ||||
|   } | ||||
| 
 | ||||
|   std::string m_fullpath; | ||||
|   std::string m_name; | ||||
|   std::string m_path; | ||||
|   bool m_pinned; | ||||
| }; | ||||
| 
 | ||||
| //////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -129,6 +257,29 @@ void RecentListBox::rebuildList() | |||
|     layout(); | ||||
| } | ||||
| 
 | ||||
| void RecentListBox::updateRecentListFromUIItems() | ||||
| { | ||||
|   base::paths pinnedPaths; | ||||
|   base::paths recentPaths; | ||||
|   for (auto item : children()) { | ||||
|     auto fi = static_cast<RecentFileItem*>(item); | ||||
|     if (fi->hasFlags(ui::HIDDEN)) | ||||
|       continue; | ||||
|     if (fi->pinned()) | ||||
|       pinnedPaths.push_back(fi->fullpath()); | ||||
|     else | ||||
|       recentPaths.push_back(fi->fullpath()); | ||||
|   } | ||||
|   onUpdateRecentListFromUIItems(pinnedPaths, | ||||
|                                 recentPaths); | ||||
| } | ||||
| 
 | ||||
| void RecentListBox::onScrollRegion(ui::ScrollRegionEvent& ev) | ||||
| { | ||||
|   for (auto item : children()) | ||||
|     static_cast<RecentFileItem*>(item)->onScrollRegion(ev); | ||||
| } | ||||
| 
 | ||||
| //////////////////////////////////////////////////////////////////////
 | ||||
| // RecentFilesListBox
 | ||||
| 
 | ||||
|  | @ -140,10 +291,10 @@ RecentFilesListBox::RecentFilesListBox() | |||
| void RecentFilesListBox::onRebuildList() | ||||
| { | ||||
|   auto recent = App::instance()->recentFiles(); | ||||
|   auto it = recent->files_begin(); | ||||
|   auto end = recent->files_end(); | ||||
|   for (; it != end; ++it) | ||||
|     addChild(new RecentFileItem(it->c_str())); | ||||
|   for (const auto& fn : recent->pinnedFiles()) | ||||
|     addChild(new RecentFileItem(fn, true)); | ||||
|   for (const auto& fn : recent->recentFiles()) | ||||
|     addChild(new RecentFileItem(fn, false)); | ||||
| } | ||||
| 
 | ||||
| void RecentFilesListBox::onClick(const std::string& path) | ||||
|  | @ -160,6 +311,13 @@ void RecentFilesListBox::onClick(const std::string& path) | |||
|   UIContext::instance()->executeCommand(command, params); | ||||
| } | ||||
| 
 | ||||
| void RecentFilesListBox::onUpdateRecentListFromUIItems(const base::paths& pinnedPaths, | ||||
|                                                        const base::paths& recentPaths) | ||||
| { | ||||
|   App::instance()->recentFiles()->setFiles(pinnedPaths, | ||||
|                                            recentPaths); | ||||
| } | ||||
| 
 | ||||
| //////////////////////////////////////////////////////////////////////
 | ||||
| // RecentFoldersListBox
 | ||||
| 
 | ||||
|  | @ -171,10 +329,10 @@ RecentFoldersListBox::RecentFoldersListBox() | |||
| void RecentFoldersListBox::onRebuildList() | ||||
| { | ||||
|   auto recent = App::instance()->recentFiles(); | ||||
|   auto it = recent->paths_begin(); | ||||
|   auto end = recent->paths_end(); | ||||
|   for (; it != end; ++it) | ||||
|     addChild(new RecentFileItem(*it)); | ||||
|   for (const auto& fn : recent->pinnedFolders()) | ||||
|     addChild(new RecentFileItem(fn, true)); | ||||
|   for (const auto& fn : recent->recentFolders()) | ||||
|     addChild(new RecentFileItem(fn, false)); | ||||
| } | ||||
| 
 | ||||
| void RecentFoldersListBox::onClick(const std::string& path) | ||||
|  | @ -191,4 +349,11 @@ void RecentFoldersListBox::onClick(const std::string& path) | |||
|   UIContext::instance()->executeCommand(command, params); | ||||
| } | ||||
| 
 | ||||
| void RecentFoldersListBox::onUpdateRecentListFromUIItems(const base::paths& pinnedPaths, | ||||
|                                                          const base::paths& recentPaths) | ||||
| { | ||||
|   App::instance()->recentFiles()->setFolders(pinnedPaths, | ||||
|                                              recentPaths); | ||||
| } | ||||
| 
 | ||||
| } // namespace app
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2016  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -8,21 +9,31 @@ | |||
| #define APP_UI_RECENT_LISTBOX_H_INCLUDED | ||||
| #pragma once | ||||
| 
 | ||||
| #include "base/paths.h" | ||||
| #include "obs/connection.h" | ||||
| #include "ui/listbox.h" | ||||
| #include "ui/view.h" | ||||
| 
 | ||||
| namespace app { | ||||
| 
 | ||||
|   class RecentFileItem; | ||||
| 
 | ||||
|   class RecentListBox : public ui::ListBox { | ||||
|   class RecentListBox : public ui::ListBox, | ||||
|                         public ui::ViewableWidget { | ||||
|     friend class RecentFileItem; | ||||
|   public: | ||||
|     RecentListBox(); | ||||
| 
 | ||||
|     void updateRecentListFromUIItems(); | ||||
| 
 | ||||
|   protected: | ||||
|     // ui::ViewableWidget impl
 | ||||
|     virtual void onScrollRegion(ui::ScrollRegionEvent& ev); | ||||
| 
 | ||||
|     virtual void onRebuildList() = 0; | ||||
|     virtual void onClick(const std::string& path) = 0; | ||||
|     virtual void onUpdateRecentListFromUIItems(const base::paths& pinnedPaths, | ||||
|                                                const base::paths& recentPaths) = 0; | ||||
| 
 | ||||
|   private: | ||||
|     void rebuildList(); | ||||
|  | @ -35,18 +46,22 @@ namespace app { | |||
|   public: | ||||
|     RecentFilesListBox(); | ||||
| 
 | ||||
|   protected: | ||||
|   private: | ||||
|     void onRebuildList() override; | ||||
|     void onClick(const std::string& path) override; | ||||
|     void onUpdateRecentListFromUIItems(const base::paths& pinnedPaths, | ||||
|                                        const base::paths& recentPaths) override; | ||||
|   }; | ||||
| 
 | ||||
|   class RecentFoldersListBox : public RecentListBox { | ||||
|   public: | ||||
|     RecentFoldersListBox(); | ||||
| 
 | ||||
|   protected: | ||||
|   private: | ||||
|     void onRebuildList() override; | ||||
|     void onClick(const std::string& path) override; | ||||
|     void onUpdateRecentListFromUIItems(const base::paths& pinnedPaths, | ||||
|                                        const base::paths& recentPaths) override; | ||||
|   }; | ||||
| 
 | ||||
| } // namespace app
 | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| // #define DEBUG_PAINT_EVENTS
 | ||||
| // #define LIMIT_DISPATCH_TIME
 | ||||
| // #define DEBUG_UI_THREADS
 | ||||
| #define GARBAGE_TRACE(...) | ||||
| 
 | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
|  | @ -351,6 +352,17 @@ void Manager::generateMessagesFromOSEvents() | |||
| 
 | ||||
|     if (canWait && used_msg_queue.empty()) | ||||
|       collectGarbage(); | ||||
| #if _DEBUG | ||||
|     else if (!m_garbage.empty()) { | ||||
|       GARBAGE_TRACE("collectGarbage() wasn't called #objects=%d" | ||||
|                     " (msg_queue=%d used_msg_queue=%d redrawState=%d runningTimers=%d)\n", | ||||
|                     int(m_garbage.size()), | ||||
|                     msg_queue.size(), | ||||
|                     used_msg_queue.size(), | ||||
|                     int(redrawState), | ||||
|                     Timer::haveRunningTimers()); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     m_eventQueue->getEvent(sheEvent, canWait); | ||||
|     if (sheEvent.type() == os::Event::None) | ||||
|  | @ -1568,10 +1580,13 @@ void Manager::collectGarbage() | |||
|   if (m_garbage.empty()) | ||||
|     return; | ||||
| 
 | ||||
|   for (WidgetsList::iterator | ||||
|          it = m_garbage.begin(), | ||||
|          end = m_garbage.end(); it != end; ++it) { | ||||
|     delete *it; | ||||
|   GARBAGE_TRACE("Manager::collectGarbage() #objects=%d\n", int(m_garbage.size())); | ||||
| 
 | ||||
|   for (auto widget : m_garbage) { | ||||
|     GARBAGE_TRACE(" -> deleting %s %s ---\n", | ||||
|                   typeid(*widget).name(), | ||||
|                   widget->id().c_str()); | ||||
|     delete widget; | ||||
|   } | ||||
|   m_garbage.clear(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite UI Library
 | ||||
| // Copyright (C) 2018  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This file is released under the terms of the MIT license.
 | ||||
|  | @ -13,6 +14,7 @@ | |||
| #include "os/display.h" | ||||
| #include "os/surface.h" | ||||
| #include "os/system.h" | ||||
| #include "ui/overlay_manager.h" | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
|  | @ -27,6 +29,11 @@ void move_region(Manager* manager, const Region& region, int dx, int dy) | |||
|   if (!display) | ||||
|     return; | ||||
| 
 | ||||
|   auto overlays = ui::OverlayManager::instance(); | ||||
|   gfx::Rect bounds = region.bounds(); | ||||
|   bounds |= gfx::Rect(bounds).offset(dx, dy); | ||||
|   overlays->restoreOverlappedAreas(bounds); | ||||
| 
 | ||||
|   os::Surface* surface = display->getSurface(); | ||||
|   os::SurfaceLock lock(surface); | ||||
|   std::size_t nrects = region.size(); | ||||
|  | @ -83,6 +90,8 @@ void move_region(Manager* manager, const Region& region, int dx, int dy) | |||
|       manager->dirtyRect(rc); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   overlays->drawOverlays(); | ||||
| } | ||||
| 
 | ||||
| } // namespace ui
 | ||||
|  |  | |||
|  | @ -57,8 +57,10 @@ void Timer::start() | |||
|   assert_ui_thread(); | ||||
| 
 | ||||
|   m_lastTick = base::current_tick(); | ||||
|   if (!m_running) { | ||||
|     m_running = true; | ||||
|     ++running_timers; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Timer::stop() | ||||
|  |  | |||
|  | @ -356,7 +356,8 @@ void View::onSetViewScroll(const gfx::Point& pt) | |||
| 
 | ||||
| void View::onScrollRegion(ScrollRegionEvent& ev) | ||||
| { | ||||
|   // Do nothing
 | ||||
|   if (auto viewable = dynamic_cast<ViewableWidget*>(attachedWidget())) | ||||
|     viewable->onScrollRegion(ev); | ||||
| } | ||||
| 
 | ||||
| void View::onScrollChange() | ||||
|  |  | |||
|  | @ -17,6 +17,12 @@ | |||
| namespace ui { | ||||
|   class ScrollRegionEvent; | ||||
| 
 | ||||
|   class ViewableWidget { | ||||
|   public: | ||||
|     virtual ~ViewableWidget() { } | ||||
|     virtual void onScrollRegion(ScrollRegionEvent& ev) = 0; | ||||
|   }; | ||||
| 
 | ||||
|   class View : public Widget | ||||
|              , public ScrollableViewDelegate { | ||||
|   public: | ||||
|  |  | |||
|  | @ -577,6 +577,19 @@ void Widget::insertChild(int index, Widget* child) | |||
|   child->m_parent = this; | ||||
| } | ||||
| 
 | ||||
| void Widget::moveChildTo(Widget* thisChild, Widget* toThisPosition) | ||||
| { | ||||
|   auto itA = std::find(m_children.begin(), m_children.end(), thisChild); | ||||
|   auto itB = std::find(m_children.begin(), m_children.end(), toThisPosition); | ||||
|   if (itA == m_children.end()) { | ||||
|     ASSERT(false); | ||||
|     return; | ||||
|   } | ||||
|   int index = itB - m_children.begin(); | ||||
|   m_children.erase(itA); | ||||
|   m_children.insert(m_children.begin() + index, thisChild); | ||||
| } | ||||
| 
 | ||||
| // ===============================================================
 | ||||
| // LAYOUT & CONSTRAINT
 | ||||
| // ===============================================================
 | ||||
|  |  | |||
|  | @ -212,6 +212,7 @@ namespace ui { | |||
|     void removeAllChildren(); | ||||
|     void replaceChild(Widget* oldChild, Widget* newChild); | ||||
|     void insertChild(int index, Widget* child); | ||||
|     void moveChildTo(Widget* thisChild, Widget* toThisPosition); | ||||
| 
 | ||||
|     // ===============================================================
 | ||||
|     // LAYOUT & CONSTRAINT
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue