Use cancelable async tasks to load/delete backup sessions

This commit is contained in:
David Capello 2019-05-30 23:17:13 -03:00
parent 91665c62c3
commit 93fe19d353
17 changed files with 552 additions and 77 deletions

2
laf

@ -1 +1 @@
Subproject commit 5afdc83f2ba9622aa37e520ad6b2a605d46022fb
Subproject commit c293519ad611d1c419d9666d5fee077a86ac4c87

View File

@ -391,6 +391,7 @@ if(ENABLE_UI)
ui/slider2.cpp
ui/status_bar.cpp
ui/tabs.cpp
ui/task_widget.cpp
ui/timeline/ani_controls.cpp
ui/timeline/timeline.cpp
ui/toolbar.cpp
@ -568,6 +569,7 @@ add_library(app-lib
site.cpp
snap_to_grid.cpp
sprite_job.cpp
task.cpp
thumbnail_generator.cpp
thumbnails.cpp
tools/active_tool.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -167,6 +167,12 @@ void Console::printf(const char* format, ...)
// static
void Console::showException(const std::exception& e)
{
ui::assert_ui_thread();
if (!ui::is_ui_thread()) {
LOG(ERROR, "A problem has occurred.\n\nDetails:\n%s\n", e.what());
return;
}
Console console;
if (typeid(e) == typeid(std::bad_alloc))
console.printf("There is not enough memory to complete the action.");

View File

@ -5,7 +5,7 @@
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
// Uncomment if you want to test the backup process each 5 secondsh
// Uncomment if you want to test the backup process each 5 seconds.
//#define TEST_BACKUPS_WITH_A_SHORT_PERIOD
// Uncomment if you want to check that backups are correctly saved
@ -137,7 +137,7 @@ void BackupObserver::backgroundThread()
else {
DocReader reader(doc, 500);
std::unique_ptr<Doc> copy(
m_session->restoreBackupDocById(doc->id()));
m_session->restoreBackupDocById(doc->id(), nullptr));
DocDiff diff = compare_docs(doc, copy.get());
if (diff.anything) {
TRACE("RECO: Differences (%s/%s/%s/%s/%s/%s/%s)\n",

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -52,12 +52,14 @@ namespace {
class Reader : public SubObjectsIO {
public:
Reader(const std::string& dir)
Reader(const std::string& dir,
base::task_token* t)
: m_sprite(nullptr)
, m_dir(dir)
, m_docId(0)
, m_docVersions(nullptr)
, m_loadInfo(nullptr) {
, m_loadInfo(nullptr)
, m_taskToken(t) {
for (const auto& fn : base::list_files(dir)) {
auto i = fn.find('-');
if (i == std::string::npos)
@ -220,7 +222,7 @@ private:
m_loadInfo->width = w;
m_loadInfo->height = h;
m_loadInfo->frames = nframes;
return (Sprite*)1;
return (Sprite*)1; // TODO improve this
}
std::unique_ptr<Sprite> spr(new Sprite(ImageSpec(mode, w, h), 256));
@ -244,7 +246,10 @@ private:
std::map<ObjectId, LayerGroup*> layersMap;
layersMap[0] = spr->root(); // parentId = 0 is the root level
for (int i = 0; i < nlayers; ++i) {
for (int i=0; i<nlayers; ++i) {
if (canceled())
return nullptr;
ObjectId layId = read32(s);
ObjectId parentId = read32(s);
@ -267,10 +272,39 @@ private:
Console().printf("Invalid number of layers #%d\n", nlayers);
}
// Read all cels
for (size_t i=0; i<m_celsToLoad.size(); ++i) {
if (canceled())
return nullptr;
const auto& pair = m_celsToLoad[i];
LayerImage* lay = doc::get<LayerImage>(pair.first);
if (!lay)
continue;
ObjectId celId = pair.second;
Cel* cel = loadObject<Cel*>("cel", celId, &Reader::readCel);
if (cel) {
// Expand sprite size
if (cel->frame() > m_sprite->lastFrame())
m_sprite->setTotalFrames(cel->frame()+1);
lay->addCel(cel);
}
if (m_taskToken) {
m_taskToken->set_progress(float(i) / float(m_celsToLoad.size()));
}
}
// Read palettes
int npalettes = read32(s);
if (npalettes >= 1 && npalettes < 0xfffff) {
for (int i = 0; i < npalettes; ++i) {
if (canceled())
return nullptr;
ObjectId palId = read32(s);
Palette* pal = loadObject<Palette*>("pal", palId, &Reader::readPalette);
if (pal)
@ -282,6 +316,9 @@ private:
int nfrtags = read32(s);
if (nfrtags >= 1 && nfrtags < 0xfffff) {
for (int i = 0; i < nfrtags; ++i) {
if (canceled())
return nullptr;
ObjectId tagId = read32(s);
FrameTag* tag = loadObject<FrameTag*>("frtag", tagId, &Reader::readFrameTag);
if (tag)
@ -293,6 +330,9 @@ private:
int nslices = read32(s);
if (nslices >= 1 && nslices < 0xffffff) {
for (int i = 0; i < nslices; ++i) {
if (canceled())
return nullptr;
ObjectId sliceId = read32(s);
Slice* slice = loadObject<Slice*>("slice", sliceId, &Reader::readSlice);
if (slice)
@ -345,15 +385,12 @@ private:
// Cels
int ncels = read32(s);
for (int i=0; i<ncels; ++i) {
ObjectId celId = read32(s);
Cel* cel = loadObject<Cel*>("cel", celId, &Reader::readCel);
if (cel) {
// Expand sprite size
if (cel->frame() > m_sprite->lastFrame())
m_sprite->setTotalFrames(cel->frame()+1);
if (canceled())
return nullptr;
lay->addCel(cel);
}
// Add a new cel to load in the future after we load all layers
ObjectId celId = read32(s);
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
}
return lay.release();
}
@ -417,14 +454,23 @@ private:
}
}
bool canceled() const {
if (m_taskToken)
return m_taskToken->canceled();
else
return false;
}
Sprite* m_sprite; // Used to pass the sprite in LayerImage() ctor
std::string m_dir;
ObjectVersion m_docId;
ObjVersionsMap m_objVersions;
ObjVersions* m_docVersions;
DocumentInfo* m_loadInfo;
std::vector<std::pair<ObjectId, ObjectId> > m_celsToLoad;
std::map<ObjectId, ImageRef> m_images;
std::map<ObjectId, CelDataRef> m_celdatas;
base::task_token* m_taskToken;
};
} // anonymous namespace
@ -434,18 +480,20 @@ private:
bool read_document_info(const std::string& dir, DocumentInfo& info)
{
return Reader(dir).loadDocumentInfo(info);
return Reader(dir, nullptr).loadDocumentInfo(info);
}
Doc* read_document(const std::string& dir)
Doc* read_document(const std::string& dir,
base::task_token* t)
{
return Reader(dir).loadDocument();
return Reader(dir, t).loadDocument();
}
Doc* read_document_with_raw_images(const std::string& dir,
RawImagesAs as)
RawImagesAs as,
base::task_token* t)
{
Reader reader(dir);
Reader reader(dir, t);
DocumentInfo info;
if (!reader.loadDocumentInfo(info)) {
@ -462,8 +510,13 @@ Doc* read_document_with_raw_images(const std::string& dir,
auto lay = new LayerImage(spr);
spr->root()->addLayer(lay);
int i = 0;
frame_t frame = 0;
for (const auto& fn : base::list_files(dir)) {
auto fns = base::list_files(dir);
for (const auto& fn : fns) {
if (t)
t->set_progress((i++) / fns.size());
if (fn.compare(0, 3, "img") != 0)
continue;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -10,6 +10,7 @@
#pragma once
#include "app/crash/raw_images_as.h"
#include "base/task.h"
#include "doc/color_mode.h"
#include "doc/frame.h"
@ -34,10 +35,13 @@ namespace crash {
}
};
bool read_document_info(const std::string& dir, DocumentInfo& info);
Doc* read_document(const std::string& dir);
bool read_document_info(const std::string& dir,
DocumentInfo& info);
Doc* read_document(const std::string& dir,
base::task_token* t);
Doc* read_document_with_raw_images(const std::string& dir,
RawImagesAs as);
RawImagesAs as,
base::task_token* t);
} // namespace crash
} // namespace app

View File

@ -27,6 +27,7 @@
#include "base/process.h"
#include "base/split_string.h"
#include "base/string.h"
#include "base/thread.h"
#include "base/time.h"
#include "doc/cancel_io.h"
@ -254,16 +255,17 @@ void Session::removeDocument(Doc* doc)
markDocumentAsCorrectlyClosed(doc);
}
catch (const std::exception&) {
// TODO Log this error
catch (const std::exception& ex) {
LOG(FATAL) << "Exception deleting document " << ex.what() << "\n";
}
}
Doc* Session::restoreBackupDoc(const std::string& backupDir)
Doc* Session::restoreBackupDoc(const std::string& backupDir,
base::task_token* t)
{
Console console;
try {
Doc* doc = read_document(backupDir);
Doc* doc = read_document(backupDir, t);
if (doc) {
fixFilename(doc);
return doc;
@ -275,47 +277,49 @@ Doc* Session::restoreBackupDoc(const std::string& backupDir)
return nullptr;
}
void Session::restoreBackup(Backup* backup)
Doc* Session::restoreBackupDoc(Backup* backup,
base::task_token* t)
{
Doc* doc = restoreBackupDoc(backup->dir());
if (doc)
UIContext::instance()->documents().add(doc);
return restoreBackupDoc(backup->dir(), t);
}
void Session::restoreBackupById(const ObjectId id)
Doc* Session::restoreBackupById(const doc::ObjectId id,
base::task_token* t)
{
std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
if (!base::is_directory(docDir))
return;
Doc* doc = restoreBackupDoc(docDir);
if (doc)
UIContext::instance()->documents().add(doc);
if (base::is_directory(docDir))
return restoreBackupDoc(docDir, t);
else
return nullptr;
}
Doc* Session::restoreBackupDocById(const doc::ObjectId id)
Doc* Session::restoreBackupDocById(const doc::ObjectId id,
base::task_token* t)
{
std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
if (!base::is_directory(docDir))
return nullptr;
return restoreBackupDoc(docDir);
return restoreBackupDoc(docDir, t);
}
void Session::restoreRawImages(Backup* backup, RawImagesAs as)
Doc* Session::restoreBackupRawImages(Backup* backup,
const RawImagesAs as,
base::task_token* t)
{
Console console;
try {
Doc* doc = read_document_with_raw_images(backup->dir(), as);
Doc* doc = read_document_with_raw_images(backup->dir(), as, t);
if (doc) {
if (isCrashedSession())
fixFilename(doc);
UIContext::instance()->documents().add(doc);
}
return doc;
}
catch (const std::exception& ex) {
Console::showException(ex);
}
return nullptr;
}
void Session::deleteBackup(Backup* backup)

View File

@ -13,6 +13,7 @@
#include "base/disable_copying.h"
#include "base/process.h"
#include "base/shared_ptr.h"
#include "base/task.h"
#include "doc/object_id.h"
#include <fstream>
@ -58,14 +59,17 @@ namespace crash {
bool saveDocumentChanges(Doc* doc);
void removeDocument(Doc* doc);
void restoreBackup(Backup* backup);
void restoreBackupById(const doc::ObjectId id);
Doc* restoreBackupDocById(const doc::ObjectId id);
void restoreRawImages(Backup* backup, RawImagesAs as);
Doc* restoreBackupDoc(Backup* backup,
base::task_token* t);
Doc* restoreBackupById(const doc::ObjectId id, base::task_token* t);
Doc* restoreBackupDocById(const doc::ObjectId id, base::task_token* t);
Doc* restoreBackupRawImages(Backup* backup,
const RawImagesAs as, base::task_token* t);
void deleteBackup(Backup* backup);
private:
Doc* restoreBackupDoc(const std::string& backupDir);
Doc* restoreBackupDoc(const std::string& backupDir,
base::task_token* t);
void loadPid();
std::string pidFilename() const;
std::string verFilename() const;

View File

@ -607,10 +607,12 @@ bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg)
App::instance()->dataRecovery()->activeSession() &&
current_editor &&
current_editor->document()) {
App::instance()
Doc* doc = App::instance()
->dataRecovery()
->activeSession()
->restoreBackupById(current_editor->document()->id());
->restoreBackupById(current_editor->document()->id(), nullptr);
if (doc)
UIContext::instance()->documents().add(doc);
return true;
}
#endif // ENABLE_DATA_RECOVERY

35
src/app/task.cpp Normal file
View File

@ -0,0 +1,35 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/task.h"
#include "base/task.h"
#include "base/thread_pool.h"
namespace app {
static base::thread_pool tasks_pool(4);
Task::Task()
: m_token(nullptr)
{
}
Task::~Task()
{
}
void Task::run(base::task::func_t&& func)
{
m_task.on_execute(std::move(func));
m_token = &m_task.start(tasks_pool);
}
} // namespace app

59
src/app/task.h Normal file
View File

@ -0,0 +1,59 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_TASK_H_INCLUDED
#define APP_TASK_H_INCLUDED
#pragma once
#include "base/task.h"
#include <functional>
namespace app {
class Task {
public:
Task();
~Task();
void run(base::task::func_t&& func);
bool completed() const {
return m_task.completed();
}
bool canceled() const {
if (m_token)
return m_token->canceled();
else
return false;
}
float progress() const {
if (m_token)
return m_token->progress();
else
return 0.0f;
}
void cancel() {
if (m_token)
m_token->cancel();
}
void set_progress(float progress) {
if (m_token)
m_token->set_progress(progress);
}
private:
base::task m_task;
base::task_token* m_token;
};
} // namespace app
#endif

View File

@ -11,15 +11,21 @@
#include "app/ui/data_recovery_view.h"
#include "app/app.h"
#include "app/app_menus.h"
#include "app/crash/data_recovery.h"
#include "app/crash/session.h"
#include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/task.h"
#include "app/ui/data_recovery_view.h"
#include "app/ui/drop_down_button.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/task_widget.h"
#include "app/ui/workspace.h"
#include "app/ui/workspace.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "fmt/format.h"
#include "ui/alert.h"
@ -49,12 +55,73 @@ public:
: ListItem(backup->description())
, m_session(session)
, m_backup(backup)
, m_task(nullptr)
{
}
crash::Session* session() const { return m_session; }
crash::Session::Backup* backup() const { return m_backup; }
bool isTaskRunning() const { return m_task != nullptr; }
void restoreBackup() {
if (m_task)
return;
m_task = new TaskWidget(
[this](base::task_token& t) {
// Warning: This is executed from a worker thread
Doc* doc = m_session->restoreBackupDoc(m_backup, &t);
ui::execute_from_ui_thread(
[this, doc]{
onOpenDoc(doc);
onDeleteTaskWidget();
});
});
addChild(m_task);
parent()->layout();
}
void restoreRawImages(crash::RawImagesAs as) {
if (m_task)
return;
m_task = new TaskWidget(
[this, as](base::task_token& t){
// Warning: This is executed from a worker thread
Doc* doc = m_session->restoreBackupRawImages(m_backup, as, &t);
ui::execute_from_ui_thread(
[this, doc]{
onOpenDoc(doc);
onDeleteTaskWidget();
});
});
addChild(m_task);
updateView();
}
void deleteBackup() {
if (m_task)
return;
m_task = new TaskWidget(
TaskWidget::kCannotCancel,
[this](base::task_token& t){
// Warning: This is executed from a worker thread
m_session->deleteBackup(m_backup);
ui::execute_from_ui_thread(
[this]{
onDeleteTaskWidget();
// We cannot use this->deferDelete() here because it looks
// like the m_task field can be still in use.
setVisible(false);
updateView();
});
});
addChild(m_task);
updateView();
}
private:
void onSizeHint(SizeHintEvent& ev) override {
ListItem::onSizeHint(ev);
@ -63,8 +130,67 @@ private:
ev.setSizeHint(sz);
}
void onResize(ResizeEvent& ev) override {
setBoundsQuietly(ev.bounds());
if (m_task) {
gfx::Size sz = m_task->sizeHint();
gfx::Rect cpos = childrenBounds();
int x2;
if (auto view = View::getView(this->parent()))
x2 = view->viewportBounds().x2();
else
x2 = cpos.x2();
cpos.x = x2 - sz.w;
cpos.w = sz.w;
m_task->setBounds(cpos);
}
}
void onOpenDoc(Doc* doc) {
if (!doc)
return;
Workspace* workspace = App::instance()->workspace();
WorkspaceView* restoreView = workspace->activeView();
// If we have this specific item selected, and the active view in
// the workspace is the DataRecoveryView, we can open the
// recovered document. In other case, opening the recovered
// document is confusing (because the user already selected other
// item, or other tab from the workspace).
if (dynamic_cast<DataRecoveryView*>(restoreView) &&
isSelected()) {
restoreView = nullptr;
}
UIContext::instance()->documents().add(doc);
if (restoreView)
workspace->setActiveView(restoreView);
}
void onDeleteTaskWidget() {
if (m_task) {
removeChild(m_task);
m_task->deferDelete();
m_task = nullptr;
layout();
}
}
void updateView() {
if (auto view = View::getView(this->parent()))
view->updateView();
else
parent()->layout();
}
crash::Session* m_session;
crash::Session::Backup* m_backup;
TaskWidget* m_task;
};
} // anonymous namespace
@ -74,6 +200,7 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
, m_openButton(Strings::recover_files_recover_sprite().c_str())
, m_deleteButton(Strings::recover_files_delete())
, m_refreshButton(Strings::recover_files_refresh())
, m_waitToEnableRefreshTimer(100)
{
m_listBox.setMultiselect(true);
m_view.setExpansive(true);
@ -110,6 +237,7 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
m_refreshButton.Click.connect(base::Bind(&DataRecoveryView::onRefresh, this));
m_listBox.Change.connect(base::Bind(&DataRecoveryView::onChangeSelection, this));
m_listBox.DoubleClickItem.connect(base::Bind(&DataRecoveryView::onOpen, this));
m_waitToEnableRefreshTimer.Tick.connect(base::Bind(&DataRecoveryView::onCheckIfWeCanEnableRefreshButton, this));
}
void DataRecoveryView::refreshListNotification()
@ -190,6 +318,12 @@ void DataRecoveryView::fillListWith(const bool crashes)
Empty();
}
void DataRecoveryView::disableRefresh()
{
m_refreshButton.setEnabled(false);
m_waitToEnableRefreshTimer.start();
}
std::string DataRecoveryView::getTabText()
{
return Strings::recover_files_title();
@ -221,26 +355,32 @@ void DataRecoveryView::onTabPopup(Workspace* workspace)
void DataRecoveryView::onOpen()
{
disableRefresh();
for (auto widget : m_listBox.children()) {
if (!widget->isSelected())
if (!widget->isVisible() ||
!widget->isSelected())
continue;
if (auto item = dynamic_cast<Item*>(widget)) {
if (item->backup())
item->session()->restoreBackup(item->backup());
item->restoreBackup();
}
}
}
void DataRecoveryView::onOpenRaw(crash::RawImagesAs as)
{
disableRefresh();
for (auto widget : m_listBox.children()) {
if (!widget->isSelected())
if (!widget->isVisible() ||
!widget->isSelected())
continue;
if (auto item = dynamic_cast<Item*>(widget)) {
if (item->backup())
item->session()->restoreRawImages(item->backup(), as);
item->restoreRawImages(as);
}
}
}
@ -263,14 +403,17 @@ void DataRecoveryView::onOpenMenu()
void DataRecoveryView::onDelete()
{
std::vector<Item*> items; // Items to delete.
disableRefresh();
std::vector<Item*> items; // Items to delete.
for (auto widget : m_listBox.children()) {
if (!widget->isSelected())
if (!widget->isVisible() ||
!widget->isSelected())
continue;
if (auto item = dynamic_cast<Item*>(widget)) {
if (item->backup())
if (item->backup() &&
!item->isTaskRunning())
items.push_back(item);
}
}
@ -284,19 +427,8 @@ void DataRecoveryView::onDelete()
int(items.size()))) != 1)
return; // Cancel
for (auto item : items) {
item->session()->deleteBackup(item->backup());
m_listBox.removeChild(item);
delete item;
}
onChangeSelection();
// Check if there is no more crash sessions
if (!thereAreCrashSessions())
Empty();
m_listBox.layout();
m_view.updateView();
for (auto item : items)
item->deleteBackup();
}
void DataRecoveryView::onRefresh()
@ -312,7 +444,8 @@ void DataRecoveryView::onChangeSelection()
{
int count = 0;
for (auto widget : m_listBox.children()) {
if (!widget->isSelected())
if (!widget->isVisible() ||
!widget->isSelected())
continue;
if (dynamic_cast<Item*>(widget)) {
@ -332,11 +465,34 @@ void DataRecoveryView::onChangeSelection()
}
}
void DataRecoveryView::onCheckIfWeCanEnableRefreshButton()
{
for (auto widget : m_listBox.children()) {
if (auto item = dynamic_cast<Item*>(widget)) {
if (item->isTaskRunning())
return;
}
}
m_refreshButton.setEnabled(true);
m_waitToEnableRefreshTimer.stop();
onChangeSelection();
// Check if there is no more crash sessions
if (!thereAreCrashSessions())
Empty();
m_listBox.layout();
m_view.updateView();
}
bool DataRecoveryView::thereAreCrashSessions() const
{
for (auto widget : m_listBox.children()) {
if (auto item = dynamic_cast<const Item*>(widget)) {
if (item &&
item->isVisible() &&
item->session() &&
item->session()->isCrashedSession())
return true;

View File

@ -51,6 +51,7 @@ namespace app {
void clearList();
void fillList();
void fillListWith(const bool crashes);
void disableRefresh();
void onOpen();
void onOpenRaw(crash::RawImagesAs as);
@ -58,6 +59,7 @@ namespace app {
void onDelete();
void onRefresh();
void onChangeSelection();
void onCheckIfWeCanEnableRefreshButton();
bool thereAreCrashSessions() const;
crash::DataRecovery* m_dataRecovery;
@ -66,6 +68,7 @@ namespace app {
DropDownButton m_openButton;
ui::Button m_deleteButton;
ui::Button m_refreshButton;
ui::Timer m_waitToEnableRefreshTimer;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -52,6 +52,7 @@ Tabs::Tabs(TabsDelegate* delegate)
, m_clickedCloseButton(false)
, m_selected(nullptr)
, m_delegate(delegate)
, m_addedTab(nullptr)
, m_removedTab(nullptr)
, m_isDragging(false)
, m_dragCopy(false)
@ -68,6 +69,7 @@ Tabs::Tabs(TabsDelegate* delegate)
Tabs::~Tabs()
{
m_addedTab.reset();
m_removedTab.reset();
// Stop animation
@ -99,6 +101,8 @@ void Tabs::addTab(TabView* tabView, bool from_drop, int pos)
tab->oldWidth = tab->width;
tab->modified = (m_delegate ? m_delegate->isTabModified(this, tabView): false);
m_addedTab = tab;
}
void Tabs::removeTab(TabView* tabView, bool with_animation)
@ -497,7 +501,7 @@ void Tabs::onPaint(PaintEvent& ev)
(tab->view != m_dragTab->view) ||
(m_dragCopy)) {
int dy = 0;
if (animation() == ANI_ADDING_TAB && tab == m_selected) {
if (animation() == ANI_ADDING_TAB && tab == m_addedTab) {
double t = animationTime();
dy = int(box.h - box.h * t);
}
@ -791,6 +795,8 @@ void Tabs::onAnimationFrame()
void Tabs::onAnimationStop(int animation)
{
m_addedTab.reset();
if (m_list.empty()) {
Widget* root = window();
if (root)
@ -956,6 +962,8 @@ void Tabs::destroyFloatingTab()
tab->oldX = tab->x;
tab->oldWidth = 0;
m_addedTab = tab;
}
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -234,6 +235,7 @@ namespace app {
TabsDelegate* m_delegate;
// Variables for animation purposes
TabPtr m_addedTab;
TabPtr m_removedTab;
////////////////////////////////////////

View File

@ -0,0 +1,91 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/task_widget.h"
#include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/ui/skin/skin_theme.h"
#include "base/clamp.h"
#include "ui/scale.h"
#include <limits>
namespace app {
using namespace ui;
using namespace app::skin;
TaskWidget::TaskWidget(const Type type,
base::task::func_t&& func)
: Box(HORIZONTAL | HOMOGENEOUS)
, m_monitorTimer(25)
, m_cancelButton("Cancel")
, m_progressBar(0, 100, 0)
{
if (int(type) & int(kCanCancel)) {
addChild(&m_cancelButton);
m_cancelButton.Click.connect(
[this](Event&){
m_task.cancel();
m_cancelButton.setEnabled(false);
m_progressBar.setEnabled(false);
});
}
if (int(type) & int(kWithProgress)) {
m_progressBar.setReadOnly(true);
addChild(&m_progressBar);
}
m_monitorTimer.Tick.connect(
[this]{
if (m_task.completed()) {
m_monitorTimer.stop();
onComplete();
}
else if (m_progressBar.parent()) {
float v = m_task.progress();
if (v > 0.0f) {
TRACEARGS("progressBar setValue",
int(base::clamp(v*100.0f, 0.0f, 100.0f)));
m_progressBar.setValue(
int(base::clamp(v*100.0f, 0.0f, 100.0f)));
}
}
});
m_monitorTimer.start();
InitTheme.connect(
[this]{
auto theme = static_cast<SkinTheme*>(this->theme());
setTransparent(true);
setBgColor(gfx::ColorNone);
m_cancelButton.setTransparent(true);
m_cancelButton.setStyle(theme->styles.miniButton());
m_cancelButton.setBgColor(gfx::ColorNone);
m_progressBar.setTransparent(true);
m_progressBar.setBgColor(gfx::ColorNone);
setup_mini_font(&m_cancelButton);
setup_mini_look(&m_progressBar);
setMaxSize(gfx::Size(std::numeric_limits<int>::max(), textHeight()));
});
initTheme();
m_task.run(std::move(func));
}
void TaskWidget::onComplete()
{
// Do nothing
}
} // namespace app

46
src/app/ui/task_widget.h Normal file
View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_TASK_WIDGET_H_INCLUDED
#define APP_UI_TASK_WIDGET_H_INCLUDED
#pragma once
#include "app/task.h"
#include "obs/signal.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/slider.h"
#include "ui/timer.h"
namespace app {
class TaskWidget : public ui::Box {
public:
enum Type {
kCanCancel = 1,
kWithProgress = 2,
kCannotCancel = kWithProgress,
kCancelAndProgress = 3,
};
TaskWidget(const Type type,
base::task::func_t&& func);
TaskWidget(base::task::func_t&& func)
: TaskWidget(kCancelAndProgress, std::move(func)) { }
protected:
virtual void onComplete();
private:
ui::Timer m_monitorTimer;
ui::Button m_cancelButton;
ui::Slider m_progressBar;
Task m_task;
};
} // namespace app
#endif