From b6d915601342a4a6627c5ef7b98c7fbb3962e6d3 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 21 Dec 2018 12:22:25 -0300 Subject: [PATCH 1/7] Add support to drag and drop recent file items The items are still not persisted/synchronized with the .ini file. --- src/app/ui/draggable_widget.h | 165 ++++++++++++++++++++++++++++++++++ src/app/ui/recent_listbox.cpp | 18 +++- src/ui/move_region.cpp | 9 ++ src/ui/widget.cpp | 13 +++ src/ui/widget.h | 1 + 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/app/ui/draggable_widget.h diff --git a/src/app/ui/draggable_widget.h b/src/app/ui/draggable_widget.h new file mode 100644 index 000000000..74e662493 --- /dev/null +++ b/src/app/ui/draggable_widget.h @@ -0,0 +1,165 @@ +// 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 +class DraggableWidget : public Base { +public: + template + DraggableWidget(Args...args) : Base(args...) { } + + bool onProcessMessage(ui::Message* msg) override { + switch (msg->type()) { + + case ui::kSetCursorMessage: + if (m_floatingOverlay) { + 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(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(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); + onReorderWidgets(mousePos); + } + break; + } + + case ui::kMouseUpMessage: { + m_wasDragged = (this->hasCapture() && m_floatingOverlay); + const bool result = Base::onProcessMessage(msg); + m_wasDragged = false; + + if (!this->hasCapture()) { + if (m_floatingOverlay) { + destroyFloatingOverlay(); + ASSERT(!m_createFloatingOverlay); + } + else if (m_createFloatingOverlay) + m_createFloatingOverlay = false; + } + return result; + } + + } + return Base::onProcessMessage(msg); + } + + bool wasDragged() const { + return m_wasDragged; + } + +private: + + void createFloatingOverlay() { + ASSERT(!m_floatingOverlay); + + gfx::Size sz = getFloatingOverlaySize(); + 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(); + } + + gfx::Size getFloatingOverlaySize() { + auto view = ui::View::getView(this); + if (!view) + view = ui::View::getView(this->parent()); + if (view) + return (view->viewportBounds() & this->bounds()).size(); + else + return this->size(); + } + + void drawFloatingOverlay(ui::Graphics& g) { + ui::PaintEvent ev(this, &g); + this->onPaint(ev); + } + + virtual void onReorderWidgets(const gfx::Point& mousePos) = 0; + + // True if we should create the floating overlay after leaving the + // widget bounds. + bool m_createFloatingOverlay = 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 m_floatingOverlay; + + // Relative mouse position between the widget and the overlay. + gfx::Point m_floatingOffset; +}; + +} // namespace app + +#endif diff --git a/src/app/ui/recent_listbox.cpp b/src/app/ui/recent_listbox.cpp index 6f6a150ad..370dcb0ef 100644 --- a/src/app/ui/recent_listbox.cpp +++ b/src/app/ui/recent_listbox.cpp @@ -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" @@ -38,10 +40,10 @@ using namespace skin; ////////////////////////////////////////////////////////////////////// // RecentFileItem -class RecentFileItem : public LinkLabel { +class RecentFileItem : public DraggableWidget { public: RecentFileItem(const std::string& file) - : LinkLabel("") + : DraggableWidget("") , m_fullpath(file) , m_name(base::get_file_name(file)) , m_path(base::get_file_path(file)) { @@ -89,7 +91,17 @@ protected: } void onClick() override { - static_cast(parent())->onClick(m_fullpath); + if (!wasDragged()) + static_cast(parent())->onClick(m_fullpath); + } + + void onReorderWidgets(const gfx::Point& mousePos) override { + auto parent = this->parent(); + auto other = manager()->pick(mousePos); + if (other && other != this && other->parent() == parent) { + parent->moveChildTo(this, other); + parent->layout(); + } } private: diff --git a/src/ui/move_region.cpp b/src/ui/move_region.cpp index 60c1026b6..b88b8ac9e 100644 --- a/src/ui/move_region.cpp +++ b/src/ui/move_region.cpp @@ -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 @@ -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 diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 0ad43ed7c..f48b77254 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -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 // =============================================================== diff --git a/src/ui/widget.h b/src/ui/widget.h index 7e4dd1aea..04ed00d1c 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -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 From 57a8bbdf1966f36dfcbf0447b526344956541776 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 21 Dec 2018 13:48:35 -0300 Subject: [PATCH 2/7] Save reordered files/folders in .ini files --- src/app/recent_files.cpp | 19 +++++++++++++++++++ src/app/recent_files.h | 5 +++++ src/app/ui/draggable_widget.h | 7 +++++-- src/app/ui/recent_listbox.cpp | 25 +++++++++++++++++++++++++ src/app/ui/recent_listbox.h | 11 +++++++++-- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index 3bf548b98..4a1b0a8e1 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -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 @@ -146,6 +147,24 @@ void RecentFiles::clear() Changed(); } +void RecentFiles::setFiles(base::paths paths) +{ + m_files.clear(); + for (auto it=paths.rbegin(), end=paths.rend(); it!=end; ++it) { + const auto& p = *it; + m_files.addItem(p, compare_path(p)); + } +} + +void RecentFiles::setFolders(base::paths paths) +{ + m_paths.clear(); + for (auto it=paths.rbegin(), end=paths.rend(); it!=end; ++it) { + const auto& p = *it; + m_paths.addItem(p, compare_path(p)); + } +} + std::string RecentFiles::normalizePath(const std::string& filename) { return base::normalize_path(filename); diff --git a/src/app/recent_files.h b/src/app/recent_files.h index 7c298d94f..d5d54719f 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -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,6 +9,7 @@ #define APP_RECENT_FILES_H_INCLUDED #pragma once +#include "base/paths.h" #include "base/recent_items.h" #include "obs/signal.h" @@ -38,6 +40,9 @@ namespace app { void setLimit(const int n); void clear(); + void setFiles(base::paths paths); + void setFolders(base::paths paths); + obs::signal Changed; private: diff --git a/src/app/ui/draggable_widget.h b/src/app/ui/draggable_widget.h index 74e662493..a52cbdac3 100644 --- a/src/app/ui/draggable_widget.h +++ b/src/app/ui/draggable_widget.h @@ -72,16 +72,18 @@ public: case ui::kMouseUpMessage: { m_wasDragged = (this->hasCapture() && m_floatingOverlay); const bool result = Base::onProcessMessage(msg); - m_wasDragged = false; if (!this->hasCapture()) { if (m_floatingOverlay) { destroyFloatingOverlay(); ASSERT(!m_createFloatingOverlay); + onFinalDrop(); } else if (m_createFloatingOverlay) m_createFloatingOverlay = false; } + + m_wasDragged = false; return result; } @@ -137,7 +139,8 @@ private: this->onPaint(ev); } - virtual void onReorderWidgets(const gfx::Point& mousePos) = 0; + virtual void onReorderWidgets(const gfx::Point& mousePos) { } + virtual void onFinalDrop() { } // True if we should create the floating overlay after leaving the // widget bounds. diff --git a/src/app/ui/recent_listbox.cpp b/src/app/ui/recent_listbox.cpp index 370dcb0ef..829794d0a 100644 --- a/src/app/ui/recent_listbox.cpp +++ b/src/app/ui/recent_listbox.cpp @@ -50,6 +50,8 @@ public: initTheme(); } + const std::string& fullpath() const { return m_fullpath; } + protected: void onInitTheme(InitThemeEvent& ev) override { LinkLabel::onInitTheme(ev); @@ -104,6 +106,11 @@ protected: } } + void onFinalDrop() override { + if (wasDragged()) + static_cast(parent())->updateRecentListFromUIItems(); + } + private: std::string m_fullpath; std::string m_name; @@ -141,6 +148,14 @@ void RecentListBox::rebuildList() layout(); } +void RecentListBox::updateRecentListFromUIItems() +{ + base::paths paths; + for (auto item : children()) + paths.push_back(static_cast(item)->fullpath()); + onUpdateRecentListFromUIItems(paths); +} + ////////////////////////////////////////////////////////////////////// // RecentFilesListBox @@ -172,6 +187,11 @@ void RecentFilesListBox::onClick(const std::string& path) UIContext::instance()->executeCommand(command, params); } +void RecentFilesListBox::onUpdateRecentListFromUIItems(const base::paths& paths) +{ + App::instance()->recentFiles()->setFiles(paths); +} + ////////////////////////////////////////////////////////////////////// // RecentFoldersListBox @@ -203,4 +223,9 @@ void RecentFoldersListBox::onClick(const std::string& path) UIContext::instance()->executeCommand(command, params); } +void RecentFoldersListBox::onUpdateRecentListFromUIItems(const base::paths& paths) +{ + App::instance()->recentFiles()->setFolders(paths); +} + } // namespace app diff --git a/src/app/ui/recent_listbox.h b/src/app/ui/recent_listbox.h index fd0cba8f8..fefe4f94e 100644 --- a/src/app/ui/recent_listbox.h +++ b/src/app/ui/recent_listbox.h @@ -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,6 +9,7 @@ #define APP_UI_RECENT_LISTBOX_H_INCLUDED #pragma once +#include "base/paths.h" #include "obs/connection.h" #include "ui/listbox.h" @@ -20,9 +22,12 @@ namespace app { public: RecentListBox(); + void updateRecentListFromUIItems(); + protected: virtual void onRebuildList() = 0; virtual void onClick(const std::string& path) = 0; + virtual void onUpdateRecentListFromUIItems(const base::paths& paths) = 0; private: void rebuildList(); @@ -35,18 +40,20 @@ namespace app { public: RecentFilesListBox(); - protected: + private: void onRebuildList() override; void onClick(const std::string& path) override; + void onUpdateRecentListFromUIItems(const base::paths& paths) override; }; class RecentFoldersListBox : public RecentListBox { public: RecentFoldersListBox(); - protected: + private: void onRebuildList() override; void onClick(const std::string& path) override; + void onUpdateRecentListFromUIItems(const base::paths& paths) override; }; } // namespace app From 72313e1c48cadb9b603662dcb6270d9a321654bf Mon Sep 17 00:00:00 2001 From: David Capello Date: Sat, 22 Dec 2018 00:14:31 -0300 Subject: [PATCH 3/7] Add possibility to pin/unpin recent items --- data/extensions/aseprite-theme/theme.xml | 9 ++ laf | 2 +- src/app/ui/draggable_widget.h | 13 ++- src/app/ui/editor/editor_view.cpp | 2 - src/app/ui/file_list_view.cpp | 2 - src/app/ui/recent_listbox.cpp | 103 ++++++++++++++++++++++- src/app/ui/recent_listbox.h | 7 +- src/ui/view.cpp | 3 +- src/ui/view.h | 6 ++ 9 files changed, 137 insertions(+), 10 deletions(-) diff --git a/data/extensions/aseprite-theme/theme.xml b/data/extensions/aseprite-theme/theme.xml index 91ff8ba4a..02e58ba92 100644 --- a/data/extensions/aseprite-theme/theme.xml +++ b/data/extensions/aseprite-theme/theme.xml @@ -654,6 +654,15 @@ +