Improve the File Selector adding new view types: list, small icons, big icons (fix #451)

This commit is contained in:
David Capello 2019-03-25 22:09:22 -03:00
parent 01fb806091
commit 9a75d01efe
26 changed files with 732 additions and 321 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -194,6 +194,9 @@
<part id="combobox_arrow_right_disabled" x="131" y="196" w="9" h="9" />
<part id="newfolder" x="99" y="211" w="9" h="9" />
<part id="newfolder_selected" x="115" y="211" w="9" h="9" />
<part id="list_view" x="96" y="224" w="9" h="9" />
<part id="small_icon_view" x="105" y="224" w="9" h="9" />
<part id="big_icon_view" x="114" y="224" w="9" h="9" />
<part id="toolbutton_normal" x="96" y="0" w1="3" w2="10" w3="3" h1="3" h2="9" h3="4" />
<part id="toolbutton_hot" x="112" y="0" w1="3" w2="10" w3="3" h1="3" h2="9" h3="4" />
<part id="toolbutton_last" x="96" y="16" w1="3" w2="10" w3="3" h1="3" h2="9" h3="4" />
@ -406,6 +409,9 @@
<part id="icon_aspect_ratio" x="256" y="264" w="10" h="8" />
<part id="linear_gradient" x="176" y="208" w="8" h="8" />
<part id="radial_gradient" x="184" y="208" w="8" h="8" />
<part id="folder_icon_small" x="144" y="272" w="10" h="8" />
<part id="folder_icon_big" x="176" y="272" w="32" h="32" />
<part id="folder_icon_medium" x="160" y="272" w="16" h="16" />
</parts>
<styles>
<style id="box" />
@ -660,13 +666,13 @@
<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" />
<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" />

View File

@ -254,6 +254,10 @@
<option id="advanced" type="bool" default="false" />
<option id="pixel_ratio" type="std::string" />
</section>
<section id="file_selector">
<option id="current_folder" type="std::string" default="&quot;&lt;empty&gt;&quot;" migrate="FileSelect.CurrentDirectory" />
<option id="zoom" type="double" default="1.0" />
</section>
<section id="text_tool">
<option id="font_face" type="std::string" />
<option id="font_size" type="int" default="12" />

View File

@ -547,6 +547,9 @@ go_back_button_tooltip = Go back one folder
go_forward_button_tooltip = Go forward one folder
go_up_button_tooltip = Up to parent folder
new_folder_button_tooltip = New folder
list_view_button_tooltip = List View
small_icon_view_button_tooltip = Small Icons View
big_icon_view_button_tooltip = Big Icons View
file_name = File name:
file_type = File type:
pinned_folders = Pinned Folders

View File

@ -1,15 +1,25 @@
<!-- Aseprite -->
<!-- Copyright (C) 2001-2018 by David Capello -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="file_selector" text="">
<vbox id="main">
<box horizontal="true">
<box horizontal="true" noborders="true">
<button text="" id="go_back_button" style="go_back_button" tooltip="@.go_back_button_tooltip" />
<button text="" id="go_forward_button" style="go_forward_button" tooltip="@.go_forward_button_tooltip" />
</box>
<button text="" id="go_up_button" style="go_up_button" tooltip="@.go_up_button_tooltip" />
<button text="" id="new_folder_button" style="new_folder_button" tooltip="@.new_folder_button_tooltip" />
<hbox noborders="true">
<button text="" id="go_back_button" style="go_back_button"
tooltip="@.go_back_button_tooltip" tooltip_dir="bottom" />
<button text="" id="go_forward_button" style="go_forward_button"
tooltip="@.go_forward_button_tooltip" tooltip_dir="bottom" />
</hbox>
<button text="" id="go_up_button" style="go_up_button"
tooltip="@.go_up_button_tooltip" tooltip_dir="bottom" />
<button text="" id="new_folder_button" style="new_folder_button"
tooltip="@.new_folder_button_tooltip" tooltip_dir="bottom" />
<buttonset id="view_type" columns="3">
<item icon="list_view" tooltip="@.list_view_button_tooltip" tooltip_dir="bottom" />
<item icon="small_icon_view" tooltip="@.small_icon_view_button_tooltip" tooltip_dir="bottom" />
<item icon="big_icon_view" tooltip="@.big_icon_view_button_tooltip" tooltip_dir="bottom" />
</buttonset>
<combobox id="location" expansive="true" />
</box>
<vbox id="file_view_placeholder" expansive="true" />

2
laf

@ -1 +1 @@
Subproject commit c3a5bd4309e9fbe6d03f490468f9d5981e802ccc
Subproject commit 75b2266ccaf9931730b5c05040af6467288f9bd0

View File

@ -133,6 +133,7 @@ void OpenFileCommand::onExecute(Context* context)
int flags =
FILE_LOAD_DATA_FILE |
FILE_LOAD_CREATE_PALETTE |
(m_repeatCheckbox ? FILE_LOAD_SEQUENCE_ASK_CHECKBOX: 0);
switch (m_seqDecision) {

View File

@ -75,7 +75,11 @@ base::paths get_writable_extensions()
Doc* load_document(Context* context, const std::string& filename)
{
/* TODO add a option to configure what to do with the sequence */
std::unique_ptr<FileOp> fop(FileOp::createLoadDocumentOperation(context, filename, FILE_LOAD_SEQUENCE_NONE));
std::unique_ptr<FileOp> fop(
FileOp::createLoadDocumentOperation(
context, filename,
FILE_LOAD_CREATE_PALETTE |
FILE_LOAD_SEQUENCE_NONE));
if (!fop)
return nullptr;
@ -303,6 +307,9 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const std::string&
if (flags & FILE_LOAD_ONE_FRAME)
fop->m_oneframe = true;
if (flags & FILE_LOAD_CREATE_PALETTE)
fop->m_createPaletteFromRgba = true;
// Does data file exist?
if (flags & FILE_LOAD_DATA_FILE) {
std::string dataFilename = base::replace_extension(filename, "aseprite-data");
@ -887,7 +894,8 @@ void FileOp::postLoad()
Sprite* sprite = m_document->sprite();
if (sprite) {
// Creates a suitable palette for RGB images
if (sprite->pixelFormat() == IMAGE_RGB &&
if (m_createPaletteFromRgba &&
sprite->pixelFormat() == IMAGE_RGB &&
sprite->getPalettes().size() <= 1 &&
sprite->palette(frame_t(0))->isBlack()) {
base::SharedPtr<Palette> palette(
@ -1176,6 +1184,7 @@ FileOp::FileOp(FileOpType type, Context* context)
, m_done(false)
, m_stop(false)
, m_oneframe(false)
, m_createPaletteFromRgba(false)
, m_ignoreEmpty(false)
, m_preserveColorProfile(
Preferences::instance().color.manage())

View File

@ -27,6 +27,7 @@
#define FILE_LOAD_SEQUENCE_YES 0x00000008
#define FILE_LOAD_ONE_FRAME 0x00000010
#define FILE_LOAD_DATA_FILE 0x00000020
#define FILE_LOAD_CREATE_PALETTE 0x00000040
namespace doc {
class FrameTag;
@ -197,6 +198,7 @@ namespace app {
bool m_oneframe; // Load just one frame (in formats
// that support animation like
// GIF/FLI/ASE).
bool m_createPaletteFromRgba;
bool m_ignoreEmpty;
// Return if we've to save/embed the color space of the document

View File

@ -82,6 +82,7 @@ Palette* load_palette(const char* filename)
std::unique_ptr<FileOp> fop(
FileOp::createLoadDocumentOperation(
nullptr, filename,
FILE_LOAD_CREATE_PALETTE |
FILE_LOAD_SEQUENCE_NONE |
FILE_LOAD_ONE_FRAME));

View File

@ -63,6 +63,8 @@ public:
unsigned int m_version;
bool m_removed;
bool m_is_folder;
double m_thumbnailProgress;
os::Surface* m_thumbnail;
#ifdef _WIN32
LPITEMIDLIST m_pidl; // relative to parent
LPITEMIDLIST m_fullpidl; // relative to the Desktop folder
@ -81,34 +83,35 @@ public:
bool operator==(const FileItem& that) const { return compare(that) == 0; }
bool operator!=(const FileItem& that) const { return compare(that) != 0; }
// IFileItem interface
// IFileItem impl
bool isFolder() const override;
bool isBrowsable() const override;
bool isHidden() const override;
bool isFolder() const;
bool isBrowsable() const;
bool isHidden() const;
std::string keyName() const override;
std::string fileName() const override;
std::string displayName() const override;
std::string keyName() const;
std::string fileName() const;
std::string displayName() const;
IFileItem* parent() const override;
const FileItemList& children() override;
void createDirectory(const std::string& dirname) override;
IFileItem* parent() const;
const FileItemList& children();
void createDirectory(const std::string& dirname);
bool hasExtension(const base::paths& extensions) override;
bool hasExtension(const base::paths& extensions);
os::Surface* getThumbnail();
void setThumbnail(os::Surface* thumbnail);
double getThumbnailProgress() override { return m_thumbnailProgress; }
void setThumbnailProgress(double progress) override {
m_thumbnailProgress = progress;
}
os::Surface* getThumbnail() override;
void setThumbnail(os::Surface* thumbnail) override;
};
typedef std::map<std::string, FileItem*> FileItemMap;
typedef std::map<std::string, os::Surface*> ThumbnailMap;
// the root of the file-system
static FileItem* rootitem = NULL;
static FileItemMap* fileitems_map;
static ThumbnailMap* thumbnail_map;
static unsigned int current_file_system_version = 0;
#ifdef _WIN32
@ -146,7 +149,6 @@ FileSystemModule::FileSystemModule()
m_instance = this;
fileitems_map = new FileItemMap;
thumbnail_map = new ThumbnailMap;
#ifdef _WIN32
/* get the IMalloc interface */
@ -178,12 +180,6 @@ FileSystemModule::~FileSystemModule()
}
fileitems_map->clear();
for (ThumbnailMap::iterator
it=thumbnail_map->begin(); it!=thumbnail_map->end(); ++it) {
it->second->dispose();
}
thumbnail_map->clear();
#ifdef _WIN32
// relase desktop IShellFolder interface
shl_idesktop->Release();
@ -194,7 +190,6 @@ FileSystemModule::~FileSystemModule()
#endif
delete fileitems_map;
delete thumbnail_map;
m_instance = NULL;
}
@ -527,24 +522,14 @@ bool FileItem::hasExtension(const base::paths& extensions)
os::Surface* FileItem::getThumbnail()
{
ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
if (it != thumbnail_map->end())
return it->second;
else
return NULL;
return m_thumbnail;
}
void FileItem::setThumbnail(os::Surface* thumbnail)
{
// destroy the current thumbnail of the file (if exists)
ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
if (it != thumbnail_map->end()) {
it->second->dispose();
thumbnail_map->erase(it);
}
// insert the new one in the map
thumbnail_map->insert(std::make_pair(m_filename, thumbnail));
if (m_thumbnail)
m_thumbnail->dispose();
m_thumbnail = thumbnail;
}
FileItem::FileItem(FileItem* parent)
@ -558,6 +543,8 @@ FileItem::FileItem(FileItem* parent)
m_version = current_file_system_version;
m_removed = false;
m_is_folder = false;
m_thumbnailProgress = 0.0;
m_thumbnail = nullptr;
#ifdef _WIN32
m_pidl = NULL;
m_fullpidl = NULL;
@ -568,6 +555,9 @@ FileItem::~FileItem()
{
FS_TRACE("FS: Destroying FileItem() with parent %p\n", m_parent);
if (m_thumbnail)
m_thumbnail->dispose();
#ifdef _WIN32
if (m_fullpidl && m_fullpidl != m_pidl) {
free_pidl(m_fullpidl);

View File

@ -80,6 +80,9 @@ namespace app {
virtual bool hasExtension(const base::paths& extensions) = 0;
virtual double getThumbnailProgress() = 0;
virtual void setThumbnailProgress(double progress) = 0;
virtual os::Surface* getThumbnail() = 0;
virtual void setThumbnail(os::Surface* thumbnail) = 0;
};

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
@ -17,6 +18,7 @@
#include "app/ui/editor/editor_render.h"
#include "base/bind.h"
#include "base/scoped_lock.h"
#include "base/scoped_lock.h"
#include "base/thread.h"
#include "doc/algorithm/rotate.h"
#include "doc/conversion_to_surface.h"
@ -25,35 +27,61 @@
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "os/system.h"
#include "render/projection.h"
#include <memory>
#include <thread>
#define MAX_THUMBNAIL_SIZE 128
#define MAX_THUMBNAIL_SIZE 128
#define THUMB_TRACE(...)
namespace app {
class ThumbnailGenerator::Worker {
public:
Worker(FileOp* fop, IFileItem* fileitem)
: m_fop(fop)
, m_fileitem(fileitem)
, m_thumbnail(nullptr)
, m_palette(nullptr)
Worker(base::concurrent_queue<ThumbnailGenerator::Item>& queue)
: m_queue(queue)
, m_fop(nullptr)
, m_isDone(false)
, m_thread(base::Bind<void>(&Worker::loadBgThread, this)) {
}
~Worker() {
m_fop->stop();
{
base::scoped_lock lock(m_mutex);
if (m_fop)
m_fop->stop();
}
m_thread.join();
}
IFileItem* getFileItem() { return m_fileitem; }
bool isDone() const { return m_fop->isDone(); }
double getProgress() const { return m_fop->progress(); }
bool isDone() const {
return m_isDone;
}
void updateProgress() {
base::scoped_lock lock(m_mutex);
if (m_item.fileitem && m_item.fop) {
double progress = m_item.fop->progress();
if (progress > m_item.fileitem->getThumbnailProgress())
m_item.fileitem->setThumbnailProgress(progress);
}
}
private:
void loadBgThread() {
void loadItem() {
ASSERT(!m_fop);
try {
{
base::scoped_lock lock(m_mutex);
m_fop = m_item.fop;
ASSERT(m_fop);
}
THUMB_TRACE("FOP loading thumbnail: %s\n",
m_item.fileitem->fileName().c_str());
// Load the file
m_fop->operate(nullptr);
// Post load
@ -65,62 +93,92 @@ private:
m_fop->document()->sprite() ?
m_fop->document()->sprite(): nullptr);
std::unique_ptr<Image> thumbnailImage;
std::unique_ptr<Palette> palette;
if (!m_fop->isStop() && sprite) {
// The palette to convert the Image
m_palette.reset(new Palette(*sprite->palette(frame_t(0))));
palette.reset(new Palette(*sprite->palette(frame_t(0))));
// Render first frame of the sprite in 'image'
std::unique_ptr<Image> image(Image::create(
IMAGE_RGB, sprite->width(), sprite->height()));
EditorRender render;
render.setupBackground(NULL, image->pixelFormat());
render.renderSprite(image.get(), sprite, frame_t(0));
const int w = sprite->width()*sprite->pixelRatio().w;
const int h = sprite->height()*sprite->pixelRatio().h;
// Calculate the thumbnail size
int thumb_w = MAX_THUMBNAIL_SIZE * image->width() / MAX(image->width(), image->height());
int thumb_h = MAX_THUMBNAIL_SIZE * image->height() / MAX(image->width(), image->height());
if (MAX(thumb_w, thumb_h) > MAX(image->width(), image->height())) {
thumb_w = image->width();
thumb_h = image->height();
int thumb_w = MAX_THUMBNAIL_SIZE * w / MAX(w, h);
int thumb_h = MAX_THUMBNAIL_SIZE * h / MAX(w, h);
if (MAX(thumb_w, thumb_h) > MAX(w, h)) {
thumb_w = w;
thumb_h = h;
}
thumb_w = MID(1, thumb_w, MAX_THUMBNAIL_SIZE);
thumb_h = MID(1, thumb_h, MAX_THUMBNAIL_SIZE);
// Stretch the 'image'
m_thumbnail.reset(Image::create(image->pixelFormat(), thumb_w, thumb_h));
clear_image(m_thumbnail.get(), 0);
algorithm::scale_image(m_thumbnail.get(), image.get(),
0, 0, thumb_w, thumb_h,
0, 0, image->width(), image->height());
thumbnailImage.reset(
Image::create(
sprite->pixelFormat(), thumb_w, thumb_h));
render::Projection proj(sprite->pixelRatio(),
render::Zoom(thumb_w, w));
EditorRender render;
render.setupBackground(NULL, thumbnailImage->pixelFormat());
render.setProjection(proj);
render.renderSprite(
thumbnailImage.get(), sprite, frame_t(0),
gfx::Clip(0, 0, 0, 0, w, h));
}
// Close file
delete m_fop->releaseDocument();
// Set the thumbnail of the file-item.
if (m_thumbnail) {
if (thumbnailImage) {
os::Surface* thumbnail = os::instance()->createRgbaSurface(
m_thumbnail->width(),
m_thumbnail->height());
thumbnailImage->width(),
thumbnailImage->height());
convert_image_to_surface(
m_thumbnail.get(), m_palette.get(), thumbnail,
0, 0, 0, 0, m_thumbnail->width(), m_thumbnail->height());
thumbnailImage.get(), palette.get(), thumbnail,
0, 0, 0, 0, thumbnailImage->width(), thumbnailImage->height());
m_fileitem->setThumbnail(thumbnail);
m_item.fileitem->setThumbnail(thumbnail);
}
THUMB_TRACE("FOP done with thumbnail: %s %s\n",
m_item.fileitem->fileName().c_str(),
(m_fop->isStop() ? " (stop)": ""));
// Reset the m_item (first the fileitem so this worker is not
// associated to this fileitem anymore, and then the FileOp).
m_item.fileitem = nullptr;
}
catch (const std::exception& e) {
m_fop->setError("Error loading file:\n%s", e.what());
}
m_fop->done();
{
base::scoped_lock lock(m_mutex);
m_item.fop = nullptr;
delete m_fop;
m_fop = nullptr;
}
ASSERT(!m_fop);
}
std::unique_ptr<FileOp> m_fop;
IFileItem* m_fileitem;
std::unique_ptr<Image> m_thumbnail;
std::unique_ptr<Palette> m_palette;
void loadBgThread() {
while (!m_queue.empty()) {
while (m_queue.try_pop(m_item)) {
loadItem();
}
base::this_thread::yield();
}
m_isDone = true;
}
base::concurrent_queue<Item>& m_queue;
app::ThumbnailGenerator::Item m_item;
FileOp* m_fop;
mutable base::mutex m_mutex;
bool m_isDone;
base::thread m_thread;
};
@ -131,7 +189,7 @@ static void delete_singleton(ThumbnailGenerator* singleton)
ThumbnailGenerator* ThumbnailGenerator::instance()
{
static ThumbnailGenerator* singleton = NULL;
static ThumbnailGenerator* singleton = nullptr;
if (singleton == NULL) {
singleton = new ThumbnailGenerator();
App::instance()->Exit.connect(base::Bind<void>(&delete_singleton, singleton));
@ -139,34 +197,17 @@ ThumbnailGenerator* ThumbnailGenerator::instance()
return singleton;
}
ThumbnailGenerator::WorkerStatus ThumbnailGenerator::getWorkerStatus(IFileItem* fileitem, double& progress)
{
base::scoped_lock hold(m_workersAccess);
for (WorkerList::iterator
it=m_workers.begin(), end=m_workers.end(); it!=end; ++it) {
Worker* worker = *it;
if (worker->getFileItem() == fileitem) {
if (worker->isDone())
return ThumbnailIsDone;
else {
progress = worker->getProgress();
return WorkingOnThumbnail;
}
}
}
return WithoutWorker;
}
bool ThumbnailGenerator::checkWorkers()
{
base::scoped_lock hold(m_workersAccess);
bool doingWork = !m_workers.empty();
bool doingWork = (!m_workers.empty());
for (WorkerList::iterator
it=m_workers.begin(); it != m_workers.end(); ) {
if ((*it)->isDone()) {
delete *it;
Worker* worker = *it;
worker->updateProgress();
if (worker->isDone()) {
delete worker;
it = m_workers.erase(it);
}
else {
@ -177,15 +218,28 @@ bool ThumbnailGenerator::checkWorkers()
return doingWork;
}
void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
void ThumbnailGenerator::generateThumbnail(IFileItem* fileitem)
{
double progress;
if (fileitem->isBrowsable() ||
fileitem->getThumbnail() != NULL ||
getWorkerStatus(fileitem, progress) != WithoutWorker)
fileitem->getThumbnail())
return;
if (fileitem->getThumbnailProgress() > 0.0) {
if (fileitem->getThumbnailProgress() < 0.0002) {
m_remainingItems.prioritize(
[fileitem](const Item& item) {
return (item.fileitem == fileitem);
});
}
return;
}
// Set a starting progress so we don't enqueue the same item two times.
fileitem->setThumbnailProgress(0.0001);
THUMB_TRACE("Queue FOP thumbnail for %s\n",
fileitem->fileName().c_str());
std::unique_ptr<FileOp> fop(
FileOp::createLoadDocumentOperation(
nullptr,
@ -198,19 +252,39 @@ void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
if (fop->hasError())
return;
Worker* worker = new Worker(fop.release(), fileitem);
try {
base::scoped_lock hold(m_workersAccess);
m_workers.push_back(worker);
}
catch (...) {
delete worker;
throw;
m_remainingItems.push(Item(fileitem, fop.get()));
fop.release();
int n = std::thread::hardware_concurrency()-1;
if (n < 1) n = 1;
if (m_workers.size() < n) {
Worker* worker = new Worker(m_remainingItems);
try {
base::scoped_lock hold(m_workersAccess);
m_workers.push_back(worker);
}
catch (...) {
delete worker;
throw;
}
}
}
void ThumbnailGenerator::stopAllWorkers()
{
Item item;
while (!m_remainingItems.empty()) {
while (m_remainingItems.try_pop(item)) {
if (!item.fileitem->getThumbnail()) {
// Reset progress to 0.0 because the FileOp wasn't used and we
// will need to create it again if we require this FileItem
// thumbnail again.
item.fileitem->setThumbnailProgress(0.0);
}
delete item.fop;
}
}
base::thread* ptr = new base::thread(base::Bind<void>(&ThumbnailGenerator::stopAllWorkersBackground, this));
m_stopThread.reset(ptr);
}
@ -224,8 +298,8 @@ void ThumbnailGenerator::stopAllWorkersBackground()
m_workers.clear();
}
for (auto it=workersCopy.begin(), end=workersCopy.end(); it!=end; ++it)
delete *it;
for (auto worker : workersCopy)
delete worker;
}
} // namespace app

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
@ -8,6 +9,7 @@
#define APP_THUMBNAIL_GENERATOR_H_INCLUDED
#pragma once
#include "base/concurrent_queue.h"
#include "base/mutex.h"
#include <memory>
@ -18,21 +20,16 @@ namespace base {
}
namespace app {
class FileOp;
class IFileItem;
class ThumbnailGenerator {
public:
enum WorkerStatus { WithoutWorker, WorkingOnThumbnail, ThumbnailIsDone };
static ThumbnailGenerator* instance();
// Generate a thumbnail for the given file-item. It must be called
// from the GUI thread.
void addWorkerToGenerateThumbnail(IFileItem* fileitem);
// Returns the status of the worker that is generating the thumbnail
// for the given file.
WorkerStatus getWorkerStatus(IFileItem* fileitem, double& progress);
void generateThumbnail(IFileItem* fileitem);
// Checks the status of workers. If there are workers that already
// done its job, we've to destroy them. This function must be called
@ -51,10 +48,22 @@ namespace app {
class Worker;
typedef std::vector<Worker*> WorkerList;
struct Item {
IFileItem* fileitem;
FileOp* fop;
Item() : fileitem(nullptr), fop(nullptr) { }
Item(const Item& item) : fileitem(item.fileitem), fop(item.fop) { }
Item(IFileItem* fileitem, FileOp* fop)
: fileitem(fileitem), fop(fop) {
}
};
WorkerList m_workers;
base::mutex m_workersAccess;
std::unique_ptr<base::thread> m_stopThread;
base::concurrent_queue<Item> m_remainingItems;
};
} // namespace app
#endif

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
@ -13,6 +14,7 @@
#include "app/modules/gfx.h"
#include "app/thumbnail_generator.h"
#include "app/ui/skin/skin_theme.h"
#include "base/clamp.h"
#include "base/string.h"
#include "base/time.h"
#include "os/font.h"
@ -40,8 +42,9 @@ FileList::FileList()
, m_generateThumbnailTimer(200, this)
, m_monitoringTimer(50, this)
, m_itemToGenerateThumbnail(nullptr)
, m_thumbnail(nullptr)
, m_multiselect(false)
, m_zoom(1.0)
, m_itemsPerRow(0)
{
setFocusStop(true);
setDoubleBuffered(true);
@ -83,6 +86,10 @@ void FileList::setCurrentFolder(IFileItem* folder)
regenerateList();
// As now we are in other folder, we can stop the generation of all
// thumbnails.
ThumbnailGenerator::instance()->stopAllWorkers();
// select first folder
if (!m_list.empty() && m_list.front()->isBrowsable())
selectIndex(0);
@ -150,6 +157,22 @@ void FileList::goUp()
}
}
void FileList::setZoom(const double zoom)
{
m_zoom = base::clamp(zoom, 1.0, 8.0);
m_req_valid = false;
// if (auto view = View::getView(this))
recalcAllFileItemInfo();
}
void FileList::animateToZoom(const double zoom)
{
m_fromZoom = m_zoom;
m_toZoom = zoom;
startAnimation(ANI_ZOOM, 10);
}
bool FileList::onProcessMessage(Message* msg)
{
switch (msg->type()) {
@ -160,22 +183,23 @@ bool FileList::onProcessMessage(Message* msg)
case kMouseMoveMessage:
if (hasCapture()) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
int th = textHeight();
int y = bounds().y;
IFileItem* oldSelected = m_selected;
m_selected = nullptr;
// Mouse position in client coordinates
gfx::Point mousePos = mouseMsg->position() - bounds().origin();
// rows
int i = 0;
for (auto begin=m_list.begin(), end=m_list.end(), it=begin;
it!=end; ++it, ++i) {
IFileItem* fi = *it;
gfx::Size itemSize = getFileItemSize(fi);
ItemInfo info = getFileItemInfo(i);
if (((mouseMsg->position().y >= y) &&
(mouseMsg->position().y < y+th+4*guiscale())) ||
(it == begin && mouseMsg->position().y < y) ||
(it == end-1 && mouseMsg->position().y >= y+th+4*guiscale())) {
if ((info.bounds.contains(mousePos)) ||
(isListView() &&
((it == begin && mousePos.y < info.bounds.y) ||
(it == end-1 && mousePos.y >= info.bounds.y2())))) {
m_selected = fi;
if (m_multiselect &&
@ -197,12 +221,13 @@ bool FileList::onProcessMessage(Message* msg)
makeSelectedFileitemVisible();
break;
}
y += itemSize.h;
}
if (oldSelected != m_selected) {
generatePreviewOfSelectedItem();
if (hasThumbnailsPerItem())
generateThumbnailForFileItem(m_selected);
else
delayThumbnailGenerationForSelectedItem();
invalidate();
@ -231,14 +256,14 @@ bool FileList::onProcessMessage(Message* msg)
case kKeyUp:
if (select >= 0)
select--;
select -= m_itemsPerRow;
else
select = 0;
break;
case kKeyDown:
if (select >= 0)
select++;
select += m_itemsPerRow;
else
select = 0;
break;
@ -262,15 +287,22 @@ bool FileList::onProcessMessage(Message* msg)
}
case kKeyLeft:
case kKeyRight:
if (select >= 0) {
case kKeyRight: {
const int delta = (scancode == kKeyLeft ? -1: 1);
if (isIconView()) {
if (select >= 0)
select += delta;
else
select = 0;
}
else if (select >= 0) {
gfx::Rect vp = view->viewportBounds();
int sgn = (scancode == kKeyLeft) ? -1: 1;
gfx::Point scroll = view->viewScroll();
scroll.x += vp.w/2*sgn;
scroll.x += vp.w/2*delta;
view->setViewScroll(scroll);
}
break;
}
case kKeyEnter:
case kKeyEnterPad:
@ -335,14 +367,32 @@ bool FileList::onProcessMessage(Message* msg)
case kMouseWheelMessage: {
View* view = View::getView(this);
if (view) {
gfx::Point scroll = view->viewScroll();
// Zoom
if (msg->ctrlPressed() || // TODO configurable
msg->cmdPressed()) {
gfx::Point delta = static_cast<MouseMessage*>(msg)->wheelDelta();
const bool precise = static_cast<MouseMessage*>(msg)->preciseWheel();
double dz = delta.x + delta.y;
if (static_cast<MouseMessage*>(msg)->preciseWheel())
scroll += static_cast<MouseMessage*>(msg)->wheelDelta();
else
scroll += static_cast<MouseMessage*>(msg)->wheelDelta() * 3*(textHeight()+4*guiscale());
if (precise) {
dz /= 1.5;
if (dz < -1.0) dz = -1.0;
else if (dz > 1.0) dz = 1.0;
}
view->setViewScroll(scroll);
setZoom(zoom() - dz);
break;
}
else {
gfx::Point scroll = view->viewScroll();
if (static_cast<MouseMessage*>(msg)->preciseWheel())
scroll += static_cast<MouseMessage*>(msg)->wheelDelta();
else
scroll += static_cast<MouseMessage*>(msg)->wheelDelta() * 3*(textHeight()+4*guiscale());
view->setViewScroll(scroll);
}
}
break;
}
@ -360,124 +410,180 @@ bool FileList::onProcessMessage(Message* msg)
}
break;
case kTouchMagnifyMessage: {
setZoom(zoom() + 4.0*static_cast<ui::TouchMessage*>(msg)->magnification());
break;
}
}
return Widget::onProcessMessage(msg);
}
int FileList::thumbnailY()
{
int y = 0;
for (IFileItem* fi : m_list) {
gfx::Size itemSize = getFileItemSize(fi);
if (fi == m_selected) {
if (fi->getThumbnail())
return y + itemSize.h/2;
else
break;
}
y += itemSize.h;
}
return 0;
}
void FileList::onPaint(ui::PaintEvent& ev)
{
Graphics* g = ev.graphics();
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
gfx::Rect bounds = clientBounds();
int x, y = bounds.y;
gfx::Color bgcolor;
gfx::Color fgcolor;
g->fillRect(theme->colors.background(), bounds);
// g->fillRect(bgcolor, gfx::Rect(bounds.x, y, bounds.w, itemSize.h));
// rows
m_thumbnail = nullptr;
int i = 0;
// os::Surface* mainThumbnail = nullptr;
int i = 0, selectedIndex = -1;
for (IFileItem* fi : m_list) {
gfx::Size itemSize = getFileItemSize(fi);
const bool evenRow = ((i & 1) == 0);
if ((!m_multiselect && fi == m_selected) ||
(m_multiselect &&
m_selectedItems.size() == m_list.size() &&
m_selectedItems[i])) {
fgcolor = theme->colors.filelistSelectedRowText();
bgcolor = theme->colors.filelistSelectedRowFace();
if (m_selected != fi) {
paintItem(g, fi, i);
}
else {
bgcolor = evenRow ? theme->colors.filelistEvenRowFace():
theme->colors.filelistOddRowFace();
if (fi->isFolder() && !fi->isBrowsable())
fgcolor = theme->colors.filelistDisabledRowText();
else
fgcolor = evenRow ? theme->colors.filelistEvenRowText():
theme->colors.filelistOddRowText();
selectedIndex = i;
}
x = bounds.x+2*guiscale();
// Item background
g->fillRect(bgcolor, gfx::Rect(bounds.x, y, bounds.w, itemSize.h));
if (fi->isFolder()) {
int icon_w = font()->textLength("[+]");
g->drawText("[+]", fgcolor, bgcolor, gfx::Point(x, y+2*guiscale()));
x += icon_w+2*guiscale();
}
// item name
g->drawText(
fi->displayName().c_str(),
fgcolor, bgcolor, gfx::Point(x, y+2*guiscale()));
// draw progress bars
double progress;
ThumbnailGenerator::WorkerStatus workerStatus =
ThumbnailGenerator::instance()->getWorkerStatus(fi, progress);
if (workerStatus == ThumbnailGenerator::WorkingOnThumbnail) {
int barw = 64*guiscale();
theme->paintProgressBar(g,
gfx::Rect(
bounds.x2()-2*guiscale()-barw,
y+itemSize.h/2-3*guiscale(),
barw, 6*guiscale()),
progress);
}
// Thumbnail position
if (fi == m_selected)
m_thumbnail = fi->getThumbnail();
y += itemSize.h;
++i;
}
// Draw the thumbnail
if (m_thumbnail) {
gfx::Rect tbounds = thumbnailBounds();
g->blit(m_thumbnail, 0, 0, tbounds.x, tbounds.y, tbounds.w, tbounds.h);
g->drawRect(gfx::rgba(0, 0, 0), tbounds.enlarge(1));
// Paint main selected index (so if the filename label is bigger it
// will appear over other items).
if (m_selected)
paintItem(g, m_selected, selectedIndex);
// Draw main thumbnail for the selected item when there are no
// thumbnails per item.
if (isListView() &&
m_selected &&
m_selected->getThumbnail()) {
gfx::Rect tbounds = mainThumbnailBounds();
g->drawRect(gfx::rgba(0, 0, 0, 64), tbounds);
tbounds.shrink(1);
g->blit(m_selected->getThumbnail(),
0, 0, tbounds.x, tbounds.y, tbounds.w, tbounds.h);
}
}
gfx::Rect FileList::thumbnailBounds()
void FileList::paintItem(ui::Graphics* g, IFileItem* fi, const int i)
{
if (!m_selected ||
!m_selected->getThumbnail())
return gfx::Rect();
ItemInfo info = getFileItemInfo(i);
if ((g->getClipBounds() & info.bounds).isEmpty())
return;
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
const bool evenRow = ((i & 1) == 0);
gfx::Rect tbounds = info.thumbnail;
gfx::Color bgcolor;
gfx::Color fgcolor;
if ((!m_multiselect && fi == m_selected) ||
(m_multiselect &&
m_selectedItems.size() == m_list.size() &&
m_selectedItems[i])) {
fgcolor = theme->colors.filelistSelectedRowText();
bgcolor = theme->colors.filelistSelectedRowFace();
}
else {
bgcolor = evenRow ? theme->colors.filelistEvenRowFace():
theme->colors.filelistOddRowFace();
if (fi->isFolder() && !fi->isBrowsable())
fgcolor = theme->colors.filelistDisabledRowText();
else
fgcolor = evenRow ? theme->colors.filelistEvenRowText():
theme->colors.filelistOddRowText();
}
// Item background
g->fillRect(bgcolor, info.bounds);
gfx::Rect textBounds = info.text;
// Folder icon or thumbnail
os::Surface* thumbnail = nullptr;
if (fi->isFolder()) {
if (isListView()) {
thumbnail = theme->parts.folderIconSmall()->bitmap(0);
tbounds = textBounds;
tbounds.x += 2*guiscale();
tbounds.w = tbounds.h;
textBounds.x += tbounds.x2();
}
else {
thumbnail =
(m_zoom < 4.0 ?
theme->parts.folderIconMedium()->bitmap(0):
theme->parts.folderIconBig()->bitmap(0));
}
}
else {
thumbnail = fi->getThumbnail();
}
// item name
if (isIconView() && textBounds.w > info.bounds.w) {
g->drawAlignedUIText(
fi->displayName().c_str(),
fgcolor, bgcolor,
(textBounds & gfx::Rect(info.bounds).shrink(2*guiscale())),
ui::CENTER | ui::TOP | ui::CHARWRAP);
}
else {
g->drawText(
fi->displayName().c_str(),
fgcolor, bgcolor,
gfx::Point(textBounds.x+2*guiscale(),
textBounds.y+2*guiscale()));
}
// Draw thumbnail progress bar
double progress = fi->getThumbnailProgress();
if (isIconView() && !thumbnail) {
if (progress == 0.0)
generateThumbnailForFileItem(fi);
}
if (!tbounds.isEmpty()) {
if (thumbnail) {
tbounds =
gfx::Rect(0, 0, thumbnail->width(), thumbnail->height())
.fitIn(tbounds);
if (!fi->isFolder()) {
g->drawRect(gfx::rgba(0, 0, 0, 64), tbounds);
tbounds.shrink(1);
}
g->drawRgbaSurface(thumbnail,
gfx::Rect(0, 0, thumbnail->width(), thumbnail->height()),
tbounds);
}
else {
tbounds = gfx::Rect(0, 0, 20*guiscale(), 2+4*(8.0-m_zoom)/8.0*guiscale())
.fitIn(tbounds);
// Start thumbnail generation for this item
generateThumbnailForFileItem(fi);
theme->paintProgressBar(g, tbounds, progress);
}
}
}
gfx::Rect FileList::mainThumbnailBounds()
{
gfx::Rect result;
if (!m_selected)
return result;
os::Surface* thumbnail = m_selected->getThumbnail();
if (!thumbnail)
return result;
ItemInfo info = getFileItemInfo(selectedIndex());
if (!info.thumbnail.isEmpty()) // There is thumbnail per item
return result;
View* view = View::getView(this);
gfx::Rect vp = view->viewportBounds();
int x = vp.x+vp.w - 2*guiscale() - thumbnail->width();
int y = thumbnailY() - thumbnail->height()/2 + bounds().y;
y = MID(vp.y+2*guiscale(), y, vp.y+vp.h-3*guiscale()-thumbnail->height());
int y = info.bounds.center().y - thumbnail->height()/2 + bounds().y;
y = base::clamp(y, vp.y+2*guiscale(), vp.y+vp.h-3*guiscale()-thumbnail->height());
x -= bounds().x;
y -= bounds().y;
return gfx::Rect(x, y, thumbnail->width(), thumbnail->height());
@ -486,21 +592,17 @@ gfx::Rect FileList::thumbnailBounds()
void FileList::onSizeHint(SizeHintEvent& ev)
{
if (!m_req_valid) {
gfx::Size reqSize(0, 0);
gfx::Rect req;
// rows
for (FileItemList::iterator
it=m_list.begin();
it!=m_list.end(); ++it) {
IFileItem* fi = *it;
gfx::Size itemSize = getFileItemSize(fi);
reqSize.w = MAX(reqSize.w, itemSize.w);
reqSize.h += itemSize.h;
for (int i=0; i<int(m_list.size()); ++i) {
ItemInfo info = getFileItemInfo(i);
req |= info.bounds;
}
m_req_valid = true;
m_req_w = reqSize.w;
m_req_h = reqSize.h;
m_req_w = req.w;
m_req_h = req.h;
}
ev.setSizeHint(Size(m_req_w, m_req_h));
}
@ -530,50 +632,150 @@ void FileList::onGenerateThumbnailTick()
{
m_generateThumbnailTimer.stop();
IFileItem* fileitem = m_itemToGenerateThumbnail;
if (fileitem)
ThumbnailGenerator::instance()->addWorkerToGenerateThumbnail(fileitem);
auto fi = m_itemToGenerateThumbnail;
if (fi)
generateThumbnailForFileItem(fi);
}
gfx::Size FileList::getFileItemSize(IFileItem* fi) const
void FileList::recalcAllFileItemInfo()
{
View* view = View::getView(this);
if (view)
view->setScrollableSize(gfx::Size(1, view->bounds().h), false);
m_info.resize(m_list.size());
if (m_info.empty()) {
m_itemsPerRow = 0;
return;
}
for (int i=0; i<int(m_info.size()); ++i)
m_info[i] = calcFileItemInfo(i);
m_itemsPerRow = 1;
// Redistribute items in X axis
if (isIconView()) {
int maxWidth = 0;
int maxTextWidth = 0;
for (const auto& info : m_info) {
int w = std::min(info.bounds.w, info.thumbnail.w*2);
maxWidth = std::max(maxWidth, w);
maxTextWidth = std::max(maxTextWidth, info.text.w);
}
if (maxWidth == 0)
return;
gfx::Size vp = (view ? view->viewportBounds().size(): size());
int itemsPerRow = vp.w / maxWidth;
if (itemsPerRow < 3) itemsPerRow = 3;
int itemWidth = vp.w / itemsPerRow;
int i = 0;
for (int y=0; i<int(m_info.size()); ) {
int h = 0;
int j = 0;
int x = 0;
for (; j<itemsPerRow && i<int(m_info.size()); ++j, ++i, x += itemWidth) {
auto& info = m_info[i];
int deltax = x - info.bounds.x;
int deltay = y - info.bounds.y;
info.bounds.x += deltax;
info.bounds.y += deltay;
info.bounds.w = itemWidth;
if (maxTextWidth > itemWidth) {
info.bounds.h += info.text.h;
info.text.h += info.text.h;
}
info.text.x = info.bounds.x + info.bounds.w/2 - info.text.w/2;
info.text.y += deltay;
info.thumbnail.x = info.bounds.x + info.bounds.w/2 - info.thumbnail.w/2;
info.thumbnail.y += deltay;
h = info.bounds.h;
}
if (h)
y += h;
else
break;
}
m_itemsPerRow = itemsPerRow;
}
invalidate();
if (view) {
view->updateView();
makeSelectedFileitemVisible();
}
}
FileList::ItemInfo FileList::calcFileItemInfo(int i) const
{
ASSERT(i >= 0 && i < int(m_list.size()));
const bool withThumbnails = hasThumbnailsPerItem();
IFileItem* fi = m_list[i];
int len = 0;
if (fi->isFolder())
len += font()->textLength("[+]") + 2*guiscale();
if (fi->isFolder() && isListView()) {
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
len += theme->parts.folderIconSmall()->bitmap(0)->width() + 2*guiscale();
}
len += font()->textLength(fi->displayName().c_str());
return gfx::Size(len+4*guiscale(), textHeight()+4*guiscale());
int textHeight = this->textHeight() + 4*guiscale();
int rowHeight = textHeight + (withThumbnails ? 8*m_zoom+2*guiscale(): 0);
ItemInfo info;
info.text = gfx::Rect(0, 0+rowHeight*i, len+4*guiscale(), textHeight);
if (withThumbnails) {
info.thumbnail = gfx::Rect(0, info.text.y,
8*m_zoom*guiscale(),
8*m_zoom*guiscale());
info.text.y += info.thumbnail.h + 2*guiscale();
}
info.bounds = info.text | info.thumbnail;
if (withThumbnails) {
info.text.x = info.bounds.x + info.bounds.w/2 - info.text.w/2;
info.thumbnail.x = info.bounds.x + info.bounds.w/2 - info.thumbnail.w/2;
}
else {
info.bounds.x = 0;
if (View* view = View::getView(this))
info.bounds.w = view->viewportBounds().w;
else
info.bounds.w = bounds().w;
}
return info;
}
void FileList::makeSelectedFileitemVisible()
{
int i = selectedIndex();
if (i < 0)
return;
View* view = View::getView(this);
gfx::Rect vp = view->viewportBounds();
gfx::Point scroll = view->viewScroll();
int th = textHeight();
int y = bounds().y;
ItemInfo info = getFileItemInfo(i);
// rows
for (FileItemList::iterator
it=m_list.begin();
it!=m_list.end(); ++it) {
IFileItem* fi = *it;
gfx::Size itemSize = getFileItemSize(fi);
if (info.bounds.x+bounds().x <= vp.x)
scroll.x = info.bounds.x;
else if (info.bounds.x+bounds().x > vp.x2() - info.bounds.w)
scroll.x = info.bounds.x - vp.w + info.bounds.w;
if (fi == m_selected) {
if (y < vp.y)
scroll.y = y - bounds().y;
else if (y > vp.y + vp.h - (th+4*guiscale()))
scroll.y = y - bounds().y - vp.h + (th+4*guiscale());
if (info.bounds.y+bounds().y < vp.y)
scroll.y = info.bounds.y;
else if (info.bounds.y+bounds().y > vp.y2() - info.bounds.h)
scroll.y = info.bounds.y - vp.h + info.bounds.h;
view->setViewScroll(scroll);
break;
}
y += itemSize.h;
}
view->setViewScroll(scroll);
}
void FileList::regenerateList()
@ -599,6 +801,8 @@ void FileList::regenerateList()
}
}
recalcAllFileItemInfo();
if (m_multiselect && !m_list.empty()) {
m_selectedItems.resize(m_list.size());
deselectedFileItems();
@ -633,20 +837,35 @@ void FileList::selectIndex(int index)
onFileSelected();
}
generatePreviewOfSelectedItem();
delayThumbnailGenerationForSelectedItem();
}
// Puts the selected file-item as the next item to be processed by the
// round-robin that generate thumbnails
void FileList::generatePreviewOfSelectedItem()
void FileList::generateThumbnailForFileItem(IFileItem* fi)
{
if (fi && animation() == ANI_NONE)
ThumbnailGenerator::instance()->generateThumbnail(fi);
}
void FileList::delayThumbnailGenerationForSelectedItem()
{
if (m_selected &&
!m_selected->isFolder() &&
!m_selected->getThumbnail())
{
m_itemToGenerateThumbnail = m_selected;
m_generateThumbnailTimer.start();
}
!m_selected->getThumbnail()) {
m_itemToGenerateThumbnail = m_selected;
m_generateThumbnailTimer.start();
}
}
void FileList::onAnimationStop(int animation)
{
if (animation == ANI_ZOOM)
setZoom(m_toZoom);
}
void FileList::onAnimationFrame()
{
if (animation() == ANI_ZOOM)
setZoom(inbetween(m_fromZoom, m_toZoom, animationTime()));
}
} // namespace app

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
@ -9,6 +10,7 @@
#pragma once
#include "app/file_system.h"
#include "app/ui/animated_widget.h"
#include "base/paths.h"
#include "base/time.h"
#include "obs/signal.h"
@ -24,7 +26,8 @@ namespace os {
namespace app {
class FileList : public ui::Widget {
class FileList : public ui::Widget
, private AnimatedWidget {
public:
FileList();
virtual ~FileList();
@ -45,7 +48,11 @@ namespace app {
void goUp();
gfx::Rect thumbnailBounds();
gfx::Rect mainThumbnailBounds();
double zoom() const { return m_zoom; }
void setZoom(const double zoom);
void animateToZoom(const double zoom);
obs::signal<void()> FileSelected;
obs::signal<void()> FileAccepted;
@ -60,18 +67,40 @@ namespace app {
virtual void onCurrentFolderChanged();
private:
enum {
ANI_NONE,
ANI_ZOOM,
};
struct ItemInfo {
gfx::Rect bounds;
gfx::Rect text;
gfx::Rect thumbnail;
};
void paintItem(ui::Graphics* g, IFileItem* fi, const int i);
void onGenerateThumbnailTick();
void onMonitoringTick();
gfx::Size getFileItemSize(IFileItem* fi) const;
void recalcAllFileItemInfo();
ItemInfo calcFileItemInfo(int i) const;
ItemInfo getFileItemInfo(int i) const { return m_info[i]; }
void makeSelectedFileitemVisible();
void regenerateList();
int selectedIndex() const;
void selectIndex(int index);
void generatePreviewOfSelectedItem();
int thumbnailY();
void generateThumbnailForFileItem(IFileItem* fi);
void delayThumbnailGenerationForSelectedItem();
bool hasThumbnailsPerItem() const { return m_zoom > 1.0; }
bool isListView() const { return !hasThumbnailsPerItem(); }
bool isIconView() const { return hasThumbnailsPerItem(); }
// AnimatedWidget impl
void onAnimationStop(int animation) override;
void onAnimationFrame() override;
IFileItem* m_currentFolder;
FileItemList m_list;
std::vector<ItemInfo> m_info;
bool m_req_valid;
int m_req_w, m_req_h;
@ -94,11 +123,15 @@ namespace app {
// thumbnail to generate when the m_generateThumbnailTimer ticks.
IFileItem* m_itemToGenerateThumbnail;
os::Surface* m_thumbnail;
// True if this listbox accepts selecting multiple items at the
// same time.
bool m_multiselect;
double m_zoom;
double m_fromZoom;
double m_toZoom;
int m_itemsPerRow;
};
} // namespace app

View File

@ -18,7 +18,7 @@ namespace app {
void FileListView::onScrollRegion(ui::ScrollRegionEvent& ev)
{
if (auto fileList = dynamic_cast<FileList*>(attachedWidget())) {
gfx::Rect tbounds = fileList->thumbnailBounds();
gfx::Rect tbounds = fileList->mainThumbnailBounds();
if (!tbounds.isEmpty()) {
tbounds
.enlarge(1)

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
@ -14,9 +15,9 @@
#include "app/console.h"
#include "app/file/file.h"
#include "app/i18n/strings.h"
#include "app/ini_file.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/ui/file_list.h"
#include "app/ui/file_list_view.h"
@ -275,10 +276,14 @@ FileSelector::FileSelector(FileSelectorType type)
goForwardButton()->setFocusStop(false);
goUpButton()->setFocusStop(false);
newFolderButton()->setFocusStop(false);
viewType()->setFocusStop(false);
for (auto child : viewType()->children())
child->setFocusStop(false);
m_fileList = new FileList();
m_fileList->setId("fileview");
m_fileName->setAssociatedFileList(m_fileList);
m_fileList->setZoom(Preferences::instance().fileSelector.zoom());
m_fileView = new FileListView();
m_fileView->attachToView(m_fileList);
@ -289,6 +294,7 @@ FileSelector::FileSelector(FileSelectorType type)
goForwardButton()->Click.connect(base::Bind<void>(&FileSelector::onGoForward, this));
goUpButton()->Click.connect(base::Bind<void>(&FileSelector::onGoUp, this));
newFolderButton()->Click.connect(base::Bind<void>(&FileSelector::onNewFolder, this));
viewType()->ItemChange.connect(base::Bind<void>(&FileSelector::onChangeViewType, this));
location()->CloseListBox.connect(base::Bind<void>(&FileSelector::onLocationCloseListBox, this));
fileType()->Change.connect(base::Bind<void>(&FileSelector::onFileTypeChange, this));
m_fileList->FileSelected.connect(base::Bind<void>(&FileSelector::onFileListFileSelected, this));
@ -352,7 +358,7 @@ bool FileSelector::show(
// If initialPath doesn't contain a path.
if (base::get_file_path(initialPath).empty()) {
// Get the saved `path' in the configuration file.
std::string path = get_config_string("FileSelect", "CurrentDirectory", "<empty>");
std::string path = Preferences::instance().fileSelector.currentFolder();
if (path == "<empty>") {
start_folder_path = base::get_user_docs_folder();
path = base::join_path(start_folder_path, initialPath);
@ -630,9 +636,9 @@ again:
// save the path in the configuration file
std::string lastpath = folder->keyName();
set_config_string("FileSelect", "CurrentDirectory",
lastpath.c_str());
Preferences::instance().fileSelector.currentFolder(lastpath);
}
Preferences::instance().fileSelector.zoom(m_fileList->zoom());
return (!output.empty());
}
@ -809,6 +815,17 @@ void FileSelector::onNewFolder()
}
}
void FileSelector::onChangeViewType()
{
double newZoom = m_fileList->zoom();
switch (viewType()->selectedItem()) {
case 0: newZoom = 1.0; break;
case 1: newZoom = 2.0; break;
case 2: newZoom = 8.0; break;
}
m_fileList->animateToZoom(newZoom);
}
// Hook for the 'location' combo-box
void FileSelector::onLocationCloseListBox()
{
@ -874,7 +891,7 @@ void FileSelector::onFileListFileSelected()
{
IFileItem* fileitem = m_fileList->selectedFileItem();
if (!fileitem->isFolder()) {
if (fileitem && !fileitem->isFolder()) {
std::string filename = base::get_file_name(fileitem->fileName());
if (m_type != FileSelectorType::OpenMultiple ||

View File

@ -52,6 +52,7 @@ namespace app {
void onGoForward();
void onGoUp();
void onNewFolder();
void onChangeViewType();
void onLocationCloseListBox();
void onFileTypeChange();
void onFileListFileSelected();

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
@ -1623,7 +1624,11 @@ void SkinTheme::drawVline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* part)
void SkinTheme::paintProgressBar(ui::Graphics* g, const gfx::Rect& rc0, double progress)
{
g->drawRect(colors.text(), rc0);
gfx::Color border = colors.text();
border = gfx::rgba(gfx::getr(border),
gfx::getg(border),
gfx::getb(border), 64);
g->drawRect(border, rc0);
gfx::Rect rc = rc0;
rc.shrink(1);

View File

@ -490,7 +490,9 @@ CompositeImageFunc get_fastest_composition_path(const Projection& proj,
}
// Slower composite function for special cases with odd zoom and non-square pixel ratio
else if (((proj.removeX(1) > 1) && (proj.removeX(1) & 1)) ||
((proj.removeY(1) > 1) && (proj.removeY(1) & 1))) {
((proj.removeY(1) > 1) && (proj.removeY(1) & 1)) ||
(proj.applyX(1.0) - proj.applyX(1) > 0.01) ||
(proj.applyY(1.0) - proj.applyY(1) > 0.01)) {
return composite_image_general<DstTraits, SrcTraits>;
}
else {

View File

@ -1,4 +1,4 @@
Copyright (C) 2018 Igara Studio S.A.
Copyright (C) 2018-2019 Igara Studio S.A.
Copyright (c) 2001-2018 David Capello
Permission is hereby granted, free of charge, to any person obtaining

View File

@ -64,6 +64,7 @@ namespace ui {
BOTTOM = 0x00800000,
HOMOGENEOUS = 0x01000000,
WORDWRAP = 0x02000000,
CHARWRAP = 0x04000000,
ALIGN_MASK = 0x7fff0000,
};

View File

@ -1,4 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -407,26 +408,40 @@ gfx::Size Graphics::doUIStringAlgorithm(const std::string& str, gfx::Color fg, g
}
gfx::Size calculatedSize(0, 0);
std::size_t beg, end, new_word_beg, old_end;
std::size_t beg, end, newBeg;
std::string line;
int lineSeparation = 2*guiscale();
// Draw line-by-line
for (beg=end=0; end != std::string::npos; ) {
for (beg=end=0; end != std::string::npos && beg<str.size(); ) {
pt.x = rc.x;
// Without word-wrap
if ((align & WORDWRAP) == 0) {
if ((align & (WORDWRAP | CHARWRAP)) == 0) {
end = str.find('\n', beg);
newBeg = end+1;
}
// With char-wrap
else if ((align & CHARWRAP) == CHARWRAP) {
for (end=beg+1; end<str.size(); ++end) {
// If we are out of the available width (rc.w) using the new "end"
if ((rc.w > 0) &&
(m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) {
if (end > beg+1)
--end;
break;
}
}
newBeg = end;
}
// With word-wrap
else {
old_end = std::string::npos;
for (new_word_beg=beg;;) {
std::size_t old_end = std::string::npos;
for (std::size_t new_word_beg=beg;;) {
end = str.find_first_of(" \n", new_word_beg);
// If we have already a word to print (old_end != npos), and
// we are out of the available width (rc.w) using the new "end",
// we are out of the available width (rc.w) using the new "end"
if ((old_end != std::string::npos) &&
(rc.w > 0) &&
(pt.x+m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) {
@ -449,8 +464,11 @@ gfx::Size Graphics::doUIStringAlgorithm(const std::string& str, gfx::Color fg, g
old_end = end;
}
newBeg = end+1;
}
ASSERT(beg < end);
// Get the entire line to be painted
line = str.substr(beg, end-beg);
@ -479,7 +497,10 @@ gfx::Size Graphics::doUIStringAlgorithm(const std::string& str, gfx::Color fg, g
pt.y += lineSize.h;
calculatedSize.h += lineSize.h;
beg = end+1;
beg = newBeg;
if (pt.y+lineSize.h >= rc.y2())
break;
}
if (calculatedSize.h > 0)

View File

@ -187,7 +187,7 @@ Rect View::viewportBounds()
}
// static
View* View::getView(Widget* widget)
View* View::getView(const Widget* widget)
{
if ((widget->parent()) &&
(widget->parent()->type() == kViewViewportWidget) &&

View File

@ -57,7 +57,7 @@ namespace ui {
gfx::Rect viewportBounds();
// For viewable widgets
static View* getView(Widget* viewableWidget);
static View* getView(const Widget* viewableWidget);
protected:
// Events