mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Improve the File Selector adding new view types: list, small icons, big icons (fix #451)
This commit is contained in:
parent
01fb806091
commit
9a75d01efe
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -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" />
|
||||
|
@ -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=""<empty>"" 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" />
|
||||
|
@ -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
|
||||
|
@ -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
2
laf
@ -1 +1 @@
|
||||
Subproject commit c3a5bd4309e9fbe6d03f490468f9d5981e802ccc
|
||||
Subproject commit 75b2266ccaf9931730b5c05040af6467288f9bd0
|
@ -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) {
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 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() {
|
||||
{
|
||||
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,7 +252,13 @@ void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
|
||||
if (fop->hasError())
|
||||
return;
|
||||
|
||||
Worker* worker = new Worker(fop.release(), fileitem);
|
||||
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);
|
||||
@ -208,9 +268,23 @@ void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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,6 +367,23 @@ bool FileList::onProcessMessage(Message* msg)
|
||||
case kMouseWheelMessage: {
|
||||
View* view = View::getView(this);
|
||||
if (view) {
|
||||
// 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 (precise) {
|
||||
dz /= 1.5;
|
||||
if (dz < -1.0) dz = -1.0;
|
||||
else if (dz > 1.0) dz = 1.0;
|
||||
}
|
||||
|
||||
setZoom(zoom() - dz);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
gfx::Point scroll = view->viewScroll();
|
||||
|
||||
if (static_cast<MouseMessage*>(msg)->preciseWheel())
|
||||
@ -344,6 +393,7 @@ bool FileList::onProcessMessage(Message* msg)
|
||||
|
||||
view->setViewScroll(scroll);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -360,45 +410,67 @@ 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_selected != fi) {
|
||||
paintItem(g, fi, i);
|
||||
}
|
||||
else {
|
||||
selectedIndex = i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void FileList::paintItem(ui::Graphics* g, IFileItem* fi, const int i)
|
||||
{
|
||||
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() &&
|
||||
@ -417,67 +489,101 @@ void FileList::onPaint(ui::PaintEvent& ev)
|
||||
theme->colors.filelistOddRowText();
|
||||
}
|
||||
|
||||
x = bounds.x+2*guiscale();
|
||||
|
||||
// Item background
|
||||
g->fillRect(bgcolor, gfx::Rect(bounds.x, y, bounds.w, itemSize.h));
|
||||
g->fillRect(bgcolor, info.bounds);
|
||||
|
||||
gfx::Rect textBounds = info.text;
|
||||
|
||||
// Folder icon or thumbnail
|
||||
os::Surface* thumbnail = nullptr;
|
||||
if (fi->isFolder()) {
|
||||
int icon_w = font()->textLength("[+]");
|
||||
|
||||
g->drawText("[+]", fgcolor, bgcolor, gfx::Point(x, y+2*guiscale()));
|
||||
x += icon_w+2*guiscale();
|
||||
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(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);
|
||||
fgcolor, bgcolor,
|
||||
gfx::Point(textBounds.x+2*guiscale(),
|
||||
textBounds.y+2*guiscale()));
|
||||
}
|
||||
|
||||
// Thumbnail position
|
||||
if (fi == m_selected)
|
||||
m_thumbnail = fi->getThumbnail();
|
||||
|
||||
y += itemSize.h;
|
||||
++i;
|
||||
// Draw thumbnail progress bar
|
||||
double progress = fi->getThumbnailProgress();
|
||||
if (isIconView() && !thumbnail) {
|
||||
if (progress == 0.0)
|
||||
generateThumbnailForFileItem(fi);
|
||||
}
|
||||
|
||||
// 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));
|
||||
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::thumbnailBounds()
|
||||
gfx::Rect FileList::mainThumbnailBounds()
|
||||
{
|
||||
if (!m_selected ||
|
||||
!m_selected->getThumbnail())
|
||||
return gfx::Rect();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 ||
|
||||
|
@ -52,6 +52,7 @@ namespace app {
|
||||
void onGoForward();
|
||||
void onGoUp();
|
||||
void onNewFolder();
|
||||
void onChangeViewType();
|
||||
void onLocationCloseListBox();
|
||||
void onFileTypeChange();
|
||||
void onFileListFileSelected();
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -64,6 +64,7 @@ namespace ui {
|
||||
BOTTOM = 0x00800000,
|
||||
HOMOGENEOUS = 0x01000000,
|
||||
WORDWRAP = 0x02000000,
|
||||
CHARWRAP = 0x04000000,
|
||||
ALIGN_MASK = 0x7fff0000,
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) &&
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user