mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
2a59076f49
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit d562448222c1990719f860246b5b3bcc3d2e1aa0
|
||||
Subproject commit df53f4ac0cecada789bf84b4283947d2591833bd
|
@ -209,7 +209,8 @@ class OptionsWindow : public app::gen::Options {
|
||||
void uninstall() {
|
||||
ASSERT(m_extension);
|
||||
ASSERT(canBeUninstalled());
|
||||
App::instance()->extensions().uninstallExtension(m_extension);
|
||||
App::instance()->extensions().uninstallExtension(m_extension,
|
||||
DeletePluginPref::kYes);
|
||||
m_extension = nullptr;
|
||||
}
|
||||
|
||||
@ -1546,7 +1547,7 @@ private:
|
||||
|
||||
// Uninstall old version
|
||||
if (ext->canBeUninstalled()) {
|
||||
exts.uninstallExtension(ext);
|
||||
exts.uninstallExtension(ext, DeletePluginPref::kNo);
|
||||
|
||||
ExtensionItem* item = getItemByExtension(ext);
|
||||
if (item)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -353,7 +353,7 @@ void Extension::enable(const bool state)
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
void Extension::uninstall()
|
||||
void Extension::uninstall(const DeletePluginPref delPref)
|
||||
{
|
||||
if (!m_isInstalled)
|
||||
return;
|
||||
@ -366,14 +366,15 @@ void Extension::uninstall()
|
||||
m_name.c_str(), m_path.c_str());
|
||||
|
||||
// Remove all files inside the extension path
|
||||
uninstallFiles(m_path);
|
||||
ASSERT(!base::is_directory(m_path));
|
||||
uninstallFiles(m_path, delPref);
|
||||
ASSERT(!base::is_directory(m_path) || delPref == DeletePluginPref::kNo);
|
||||
|
||||
m_isEnabled = false;
|
||||
m_isInstalled = false;
|
||||
}
|
||||
|
||||
void Extension::uninstallFiles(const std::string& path)
|
||||
void Extension::uninstallFiles(const std::string& path,
|
||||
const DeletePluginPref delPref)
|
||||
{
|
||||
#if 1 // Read the list of files to be uninstalled from __info.json file
|
||||
|
||||
@ -398,11 +399,17 @@ void Extension::uninstallFiles(const std::string& path)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete __pref.lua file
|
||||
// Delete __pref.lua file (only if specified, e.g. if the user is
|
||||
// updating the extension, the preferences should be kept).
|
||||
bool hasPrefFile = false;
|
||||
{
|
||||
std::string fn = base::join_path(path, kPrefLua);
|
||||
if (base::is_file(fn))
|
||||
base::delete_file(fn);
|
||||
if (base::is_file(fn)) {
|
||||
if (delPref == DeletePluginPref::kYes)
|
||||
base::delete_file(fn);
|
||||
else
|
||||
hasPrefFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(installedDirs.begin(),
|
||||
@ -427,7 +434,8 @@ void Extension::uninstallFiles(const std::string& path)
|
||||
}
|
||||
|
||||
TRACE("EXT: Deleting extension directory '%s'\n", path.c_str());
|
||||
base::remove_directory(path);
|
||||
if (!hasPrefFile)
|
||||
base::remove_directory(path);
|
||||
|
||||
#else // The following code delete the whole "path",
|
||||
// we prefer the __info.json approach.
|
||||
@ -439,7 +447,7 @@ void Extension::uninstallFiles(const std::string& path)
|
||||
base::delete_file(fn);
|
||||
}
|
||||
else if (base::is_directory(fn)) {
|
||||
uninstallFiles(fn);
|
||||
uninstallFiles(fn, deleteUserPref);
|
||||
}
|
||||
}
|
||||
|
||||
@ -860,9 +868,10 @@ void Extensions::enableExtension(Extension* extension, const bool state)
|
||||
generateExtensionSignals(extension);
|
||||
}
|
||||
|
||||
void Extensions::uninstallExtension(Extension* extension)
|
||||
void Extensions::uninstallExtension(Extension* extension,
|
||||
const DeletePluginPref delPref)
|
||||
{
|
||||
extension->uninstall();
|
||||
extension->uninstall(delPref);
|
||||
generateExtensionSignals(extension);
|
||||
|
||||
auto it = std::find(m_extensions.begin(),
|
||||
|
@ -31,6 +31,8 @@ namespace app {
|
||||
std::string commonPath;
|
||||
};
|
||||
|
||||
enum DeletePluginPref { kNo, kYes };
|
||||
|
||||
class Extension {
|
||||
friend class Extensions;
|
||||
public:
|
||||
@ -130,8 +132,9 @@ namespace app {
|
||||
|
||||
private:
|
||||
void enable(const bool state);
|
||||
void uninstall();
|
||||
void uninstallFiles(const std::string& path);
|
||||
void uninstall(const DeletePluginPref delPref);
|
||||
void uninstallFiles(const std::string& path,
|
||||
const DeletePluginPref delPref);
|
||||
bool isDefaultTheme() const;
|
||||
void updateCategory(const Category newCategory);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
@ -188,7 +191,8 @@ namespace app {
|
||||
iterator end() { return m_extensions.end(); }
|
||||
|
||||
void enableExtension(Extension* extension, const bool state);
|
||||
void uninstallExtension(Extension* extension);
|
||||
void uninstallExtension(Extension* extension,
|
||||
const DeletePluginPref delPref);
|
||||
ExtensionInfo getCompressedExtensionInfo(const std::string& zipFn);
|
||||
Extension* installCompressedExtension(const std::string& zipFn,
|
||||
const ExtensionInfo& info);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -885,9 +885,6 @@ void FileOp::stop()
|
||||
|
||||
FileOp::~FileOp()
|
||||
{
|
||||
if (m_format)
|
||||
m_format->destroyData(this);
|
||||
|
||||
delete m_seq.palette;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -57,9 +58,4 @@ bool FileFormat::postLoad(FileOp* fop)
|
||||
return onPostLoad(fop);
|
||||
}
|
||||
|
||||
void FileFormat::destroyData(FileOp* fop)
|
||||
{
|
||||
onDestroyData(fop);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -61,9 +61,6 @@ namespace app {
|
||||
// Returns false cancelled the operation.
|
||||
bool postLoad(FileOp* fop);
|
||||
|
||||
// Destroys the custom data stored in "fop->format_data" field.
|
||||
void destroyData(FileOp* fop);
|
||||
|
||||
// Returns extra options for this format. It can return != NULL
|
||||
// only if flags() returns FILE_SUPPORT_GET_FORMAT_OPTIONS.
|
||||
FormatOptionsPtr askUserForFormatOptions(FileOp* fop) {
|
||||
@ -86,7 +83,6 @@ namespace app {
|
||||
#ifdef ENABLE_SAVE
|
||||
virtual bool onSave(FileOp* fop) = 0;
|
||||
#endif
|
||||
virtual void onDestroyData(FileOp* fop) { }
|
||||
|
||||
virtual FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) {
|
||||
return FormatOptionsPtr(nullptr);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,9 +10,7 @@
|
||||
#endif
|
||||
|
||||
#include "app/file/split_filename.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@ -20,41 +19,44 @@ namespace app {
|
||||
// Splits a file-name like "my_ani0000.pcx" to "my_ani" and ".pcx",
|
||||
// returning the number of the center; returns "-1" if the function
|
||||
// can't split anything
|
||||
int split_filename(const std::string& filename, std::string& left, std::string& right, int& width)
|
||||
int split_filename(const std::string& filename,
|
||||
std::string& left,
|
||||
std::string& right,
|
||||
int& width)
|
||||
{
|
||||
left = base::get_file_title_with_path(filename);
|
||||
right = base::get_file_extension(filename);
|
||||
if (!right.empty())
|
||||
right.insert(right.begin(), '.');
|
||||
|
||||
// Remove all trailing numbers in the "left" side, and pass they to "result_str".
|
||||
// Remove all trailing numbers in the "left" side.
|
||||
std::string result_str;
|
||||
width = 0;
|
||||
for (;;) {
|
||||
// Get the last UTF-8 character (as we don't have a
|
||||
// reverse_iterator, we iterate from the beginning)
|
||||
int chr = 0;
|
||||
base::utf8_const_iterator begin(left.begin()), end(left.end());
|
||||
base::utf8_const_iterator it(begin), prev(begin);
|
||||
for (; it != end; prev=it, ++it)
|
||||
chr = *it;
|
||||
int num = -1;
|
||||
int order = 1;
|
||||
|
||||
if ((chr >= '0') && (chr <= '9')) {
|
||||
result_str.insert(result_str.begin(), chr);
|
||||
width++;
|
||||
auto it = left.rbegin();
|
||||
auto end = left.rend();
|
||||
|
||||
left.erase(prev - begin);
|
||||
while (it != end) {
|
||||
const int chr = *it;
|
||||
if (chr >= '0' && chr <= '9') {
|
||||
if (num < 0)
|
||||
num = 0;
|
||||
|
||||
num += order*(chr-'0');
|
||||
order *= 10;
|
||||
++width;
|
||||
++it;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert the "buf" to integer and return it.
|
||||
if (!result_str.empty()) {
|
||||
return base::convert_to<int>(result_str);
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
if (width > 0)
|
||||
left.erase(left.end()-width, left.end());
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,7 +13,10 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
int split_filename(const std::string& filename, std::string& left, std::string& right, int& width);
|
||||
int split_filename(const std::string& filename,
|
||||
std::string& left,
|
||||
std::string& right,
|
||||
int& width);
|
||||
|
||||
} // namespace app
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -17,11 +18,21 @@ TEST(SplitFilename, Common)
|
||||
std::string left, right;
|
||||
int width;
|
||||
|
||||
EXPECT_EQ(-1, split_filename("sprite.png", left, right, width));
|
||||
EXPECT_EQ("sprite", left);
|
||||
EXPECT_EQ(".png", right);
|
||||
EXPECT_EQ(0, width);
|
||||
|
||||
EXPECT_EQ(1, split_filename("C:\\test\\a1.png", left, right, width));
|
||||
EXPECT_EQ("C:\\test\\a", left);
|
||||
EXPECT_EQ(".png", right);
|
||||
EXPECT_EQ(1, width);
|
||||
|
||||
EXPECT_EQ(2001, split_filename("/hi/bye2001.png", left, right, width));
|
||||
EXPECT_EQ("/hi/bye", left);
|
||||
EXPECT_EQ(".png", right);
|
||||
EXPECT_EQ(4, width);
|
||||
|
||||
EXPECT_EQ(1, split_filename("C:/test/a1.png", left, right, width));
|
||||
EXPECT_EQ("C:/test/a", left);
|
||||
EXPECT_EQ(".png", right);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -71,7 +71,7 @@ unsigned int current_file_system_version = 0;
|
||||
#endif
|
||||
|
||||
// a position in the file-system
|
||||
class FileItem : public IFileItem {
|
||||
class FileItem final : public IFileItem {
|
||||
public:
|
||||
// TODO make all these fields private
|
||||
std::string m_keyname;
|
||||
@ -123,6 +123,13 @@ public:
|
||||
m_thumbnailProgress = progress;
|
||||
}
|
||||
|
||||
bool needThumbnail() const override {
|
||||
return
|
||||
!isBrowsable() &&
|
||||
m_thumbnail == nullptr &&
|
||||
m_thumbnailProgress < 1.0;
|
||||
}
|
||||
|
||||
os::SurfaceRef getThumbnail() override;
|
||||
void setThumbnail(const os::SurfaceRef& thumbnail) override;
|
||||
|
||||
@ -593,6 +600,8 @@ os::SurfaceRef FileItem::getThumbnail()
|
||||
|
||||
void FileItem::setThumbnail(const os::SurfaceRef& newThumbnail)
|
||||
{
|
||||
m_thumbnailProgress = 1.0;
|
||||
|
||||
if (newThumbnail)
|
||||
newThumbnail->ref();
|
||||
auto old = m_thumbnail.exchange(newThumbnail.get());
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -87,8 +87,10 @@ namespace app {
|
||||
virtual double getThumbnailProgress() = 0;
|
||||
virtual void setThumbnailProgress(double progress) = 0;
|
||||
|
||||
virtual bool needThumbnail() const = 0;
|
||||
virtual os::SurfaceRef getThumbnail() = 0;
|
||||
virtual void setThumbnail(const os::SurfaceRef& thumbnail) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,7 +18,6 @@
|
||||
#include "app/file_system.h"
|
||||
#include "app/util/conversion_to_surface.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/scoped_lock.h"
|
||||
#include "base/thread.h"
|
||||
#include "doc/algorithm/rotate.h"
|
||||
#include "doc/image.h"
|
||||
@ -28,6 +27,7 @@
|
||||
#include "os/system.h"
|
||||
#include "render/projection.h"
|
||||
#include "render/render.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@ -50,7 +50,7 @@ public:
|
||||
|
||||
~Worker() {
|
||||
{
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_fop)
|
||||
m_fop->stop();
|
||||
}
|
||||
@ -58,7 +58,7 @@ public:
|
||||
}
|
||||
|
||||
void stop() const {
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_fop)
|
||||
m_fop->stop();
|
||||
}
|
||||
@ -68,7 +68,7 @@ public:
|
||||
}
|
||||
|
||||
void updateProgress() {
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_item.fileitem && m_item.fop) {
|
||||
double progress = m_item.fop->progress();
|
||||
if (progress > m_item.fileitem->getThumbnailProgress())
|
||||
@ -81,7 +81,7 @@ private:
|
||||
ASSERT(!m_fop);
|
||||
try {
|
||||
{
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_fop = m_item.fop;
|
||||
ASSERT(m_fop);
|
||||
}
|
||||
@ -168,7 +168,7 @@ private:
|
||||
0, 0, 0, 0, thumbnailImage->width(), thumbnailImage->height());
|
||||
|
||||
{
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_item.fileitem->setThumbnail(thumbnail);
|
||||
}
|
||||
}
|
||||
@ -176,20 +176,29 @@ private:
|
||||
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).
|
||||
{
|
||||
base::scoped_lock lock(m_mutex);
|
||||
m_item.fileitem = nullptr;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
m_fop->setError("Error loading file:\n%s", e.what());
|
||||
}
|
||||
|
||||
if (!m_fop->isStop()) {
|
||||
// Set a nullptr thumbnail if we failed loading the given file,
|
||||
// in this way we're not going to re-try generating this same
|
||||
// thumbnail.
|
||||
if (m_item.fileitem->needThumbnail())
|
||||
m_item.fileitem->setThumbnail(nullptr);
|
||||
}
|
||||
|
||||
// Reset the m_item (first the fileitem so this worker is not
|
||||
// associated to this fileitem anymore, and then the FileOp).
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_item.fileitem = nullptr;
|
||||
}
|
||||
|
||||
m_fop->done();
|
||||
{
|
||||
base::scoped_lock lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_item.fop = nullptr;
|
||||
delete m_fop;
|
||||
m_fop = nullptr;
|
||||
@ -199,8 +208,14 @@ private:
|
||||
|
||||
void loadBgThread() {
|
||||
while (!m_queue.empty()) {
|
||||
while (m_queue.try_pop(m_item)) {
|
||||
loadItem();
|
||||
bool success = true;
|
||||
while (success) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex); // To access m_item
|
||||
success = m_queue.try_pop(m_item);
|
||||
}
|
||||
if (success)
|
||||
loadItem();
|
||||
}
|
||||
base::this_thread::yield();
|
||||
}
|
||||
@ -210,24 +225,22 @@ private:
|
||||
base::concurrent_queue<Item>& m_queue;
|
||||
app::ThumbnailGenerator::Item m_item;
|
||||
FileOp* m_fop;
|
||||
mutable base::mutex m_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
std::atomic<bool> m_isDone;
|
||||
base::thread m_thread;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
static void delete_singleton(ThumbnailGenerator* singleton)
|
||||
{
|
||||
delete singleton;
|
||||
}
|
||||
|
||||
ThumbnailGenerator* ThumbnailGenerator::instance()
|
||||
{
|
||||
static ThumbnailGenerator* singleton = nullptr;
|
||||
if (singleton == NULL) {
|
||||
singleton = new ThumbnailGenerator();
|
||||
App::instance()->Exit.connect([&]{ delete_singleton(singleton); });
|
||||
static std::unique_ptr<ThumbnailGenerator> singleton;
|
||||
ui::assert_ui_thread();
|
||||
if (!singleton) {
|
||||
// We cannot use std::make_unique() because ThumbnailGenerator
|
||||
// ctor is private.
|
||||
singleton.reset(new ThumbnailGenerator);
|
||||
App::instance()->Exit.connect([&]{ singleton.reset(); });
|
||||
}
|
||||
return singleton;
|
||||
return singleton.get();
|
||||
}
|
||||
|
||||
ThumbnailGenerator::ThumbnailGenerator()
|
||||
@ -239,15 +252,13 @@ ThumbnailGenerator::ThumbnailGenerator()
|
||||
|
||||
bool ThumbnailGenerator::checkWorkers()
|
||||
{
|
||||
base::scoped_lock hold(m_workersAccess);
|
||||
std::lock_guard<std::mutex> hold(m_workersAccess);
|
||||
bool doingWork = (!m_workers.empty());
|
||||
|
||||
for (WorkerList::iterator
|
||||
it=m_workers.begin(); it != m_workers.end(); ) {
|
||||
Worker* worker = *it;
|
||||
worker->updateProgress();
|
||||
if (worker->isDone()) {
|
||||
delete worker;
|
||||
(*it)->updateProgress();
|
||||
if ((*it)->isDone()) {
|
||||
it = m_workers.erase(it);
|
||||
}
|
||||
else {
|
||||
@ -260,8 +271,7 @@ bool ThumbnailGenerator::checkWorkers()
|
||||
|
||||
void ThumbnailGenerator::generateThumbnail(IFileItem* fileitem)
|
||||
{
|
||||
if (fileitem->isBrowsable() ||
|
||||
fileitem->getThumbnail())
|
||||
if (!fileitem->needThumbnail())
|
||||
return;
|
||||
|
||||
if (fileitem->getThumbnailProgress() > 0.0) {
|
||||
@ -300,11 +310,12 @@ void ThumbnailGenerator::generateThumbnail(IFileItem* fileitem)
|
||||
fileitem->fileName().c_str(),
|
||||
FILE_LOAD_SEQUENCE_NONE |
|
||||
FILE_LOAD_ONE_FRAME));
|
||||
if (!fop)
|
||||
return;
|
||||
|
||||
if (fop->hasError())
|
||||
if (!fop || fop->hasError()) {
|
||||
// Set a nullptr thumbnail so we don't try to generate a thumbnail
|
||||
// for this fileitem again.
|
||||
fileitem->setThumbnail(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
m_remainingItems.push(Item(fileitem, fop.get()));
|
||||
fop.release();
|
||||
@ -327,18 +338,16 @@ void ThumbnailGenerator::stopAllWorkers()
|
||||
}
|
||||
}
|
||||
|
||||
base::scoped_lock hold(m_workersAccess);
|
||||
for (auto worker : m_workers)
|
||||
std::lock_guard<std::mutex> hold(m_workersAccess);
|
||||
for (const auto& worker : m_workers)
|
||||
worker->stop();
|
||||
}
|
||||
|
||||
void ThumbnailGenerator::startWorker()
|
||||
{
|
||||
base::scoped_lock hold(m_workersAccess);
|
||||
std::lock_guard<std::mutex> hold(m_workersAccess);
|
||||
if (m_workers.size() < m_maxWorkers) {
|
||||
std::unique_ptr<Worker> worker(new Worker(m_remainingItems));
|
||||
m_workers.push_back(worker.get());
|
||||
worker.release();
|
||||
m_workers.push_back(std::make_unique<Worker>(m_remainingItems));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,9 +10,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "base/concurrent_queue.h"
|
||||
#include "base/mutex.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace base {
|
||||
@ -47,7 +47,8 @@ namespace app {
|
||||
void startWorker();
|
||||
|
||||
class Worker;
|
||||
typedef std::vector<Worker*> WorkerList;
|
||||
using WorkerPtr = std::unique_ptr<Worker>;
|
||||
using WorkerList = std::vector<WorkerPtr>;
|
||||
|
||||
struct Item {
|
||||
IFileItem* fileitem;
|
||||
@ -61,8 +62,7 @@ namespace app {
|
||||
|
||||
int m_maxWorkers;
|
||||
WorkerList m_workers;
|
||||
base::mutex m_workersAccess;
|
||||
std::unique_ptr<base::thread> m_stopThread;
|
||||
std::mutex m_workersAccess;
|
||||
base::concurrent_queue<Item> m_remainingItems;
|
||||
};
|
||||
|
||||
|
@ -885,7 +885,7 @@ void FileList::selectIndex(int index)
|
||||
|
||||
void FileList::generateThumbnailForFileItem(IFileItem* fi)
|
||||
{
|
||||
if (fi && animation() == ANI_NONE) {
|
||||
if (fi && fi->needThumbnail() && animation() == ANI_NONE) {
|
||||
auto it = std::find(m_generateThumbnailsForTheseItems.begin(),
|
||||
m_generateThumbnailsForTheseItems.end(), fi);
|
||||
if (it != m_generateThumbnailsForTheseItems.end())
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "base/fs.h"
|
||||
#include "base/log.h"
|
||||
#include "base/string.h"
|
||||
#include "base/utf8_decode.h"
|
||||
#include "gfx/border.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/rect.h"
|
||||
@ -1131,14 +1132,13 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
|
||||
int scroll = delegate.index();
|
||||
|
||||
const std::string& textString = widget->text();
|
||||
base::utf8_const_iterator utf8_it((textString.begin()));
|
||||
int textlen = base::utf8_length(textString);
|
||||
scroll = std::min(scroll, textlen);
|
||||
if (scroll)
|
||||
utf8_it += scroll;
|
||||
base::utf8_decode dec(textString);
|
||||
auto pos = dec.pos();
|
||||
for (int i=0; i<scroll && dec.next(); ++i)
|
||||
pos = dec.pos();
|
||||
|
||||
g->drawText(utf8_it,
|
||||
base::utf8_const_iterator(textString.end()),
|
||||
// TODO use a string_view()
|
||||
g->drawText(std::string(pos, textString.end()),
|
||||
colors.text(), ColorNone,
|
||||
bounds.origin(), &delegate);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -47,52 +48,49 @@ doc::Image* render_text(const std::string& fontfile, int fontsize,
|
||||
image.reset(doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
|
||||
doc::clear_image(image.get(), 0);
|
||||
|
||||
ft::ForEachGlyph<ft::Face> feg(face);
|
||||
if (feg.initialize(base::utf8_const_iterator(text.begin()),
|
||||
base::utf8_const_iterator(text.end()))) {
|
||||
do {
|
||||
auto glyph = feg.glyph();
|
||||
if (!glyph)
|
||||
continue;
|
||||
ft::ForEachGlyph<ft::Face> feg(face, text);
|
||||
while (feg.next()) {
|
||||
auto glyph = feg.glyph();
|
||||
if (!glyph)
|
||||
continue;
|
||||
|
||||
int t, yimg = - bounds.y + int(glyph->y);
|
||||
int t, yimg = - bounds.y + int(glyph->y);
|
||||
|
||||
for (int v=0; v<int(glyph->bitmap->rows); ++v, ++yimg) {
|
||||
const uint8_t* p = glyph->bitmap->buffer + v*glyph->bitmap->pitch;
|
||||
int ximg = - bounds.x + int(glyph->x);
|
||||
int bit = 0;
|
||||
for (int v=0; v<int(glyph->bitmap->rows); ++v, ++yimg) {
|
||||
const uint8_t* p = glyph->bitmap->buffer + v*glyph->bitmap->pitch;
|
||||
int ximg = - bounds.x + int(glyph->x);
|
||||
int bit = 0;
|
||||
|
||||
for (int u=0; u<int(glyph->bitmap->width); ++u, ++ximg) {
|
||||
int alpha;
|
||||
for (int u=0; u<int(glyph->bitmap->width); ++u, ++ximg) {
|
||||
int alpha;
|
||||
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
|
||||
if (output_alpha) {
|
||||
doc::color_t output_color =
|
||||
doc::rgba(doc::rgba_getr(color),
|
||||
doc::rgba_getg(color),
|
||||
doc::rgba_getb(color),
|
||||
output_alpha);
|
||||
|
||||
doc::put_pixel(
|
||||
image.get(), ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image.get(), ximg, yimg),
|
||||
output_color));
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
|
||||
if (output_alpha) {
|
||||
doc::color_t output_color =
|
||||
doc::rgba(doc::rgba_getr(color),
|
||||
doc::rgba_getg(color),
|
||||
doc::rgba_getb(color),
|
||||
output_alpha);
|
||||
|
||||
doc::put_pixel(
|
||||
image.get(), ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image.get(), ximg, yimg),
|
||||
output_color));
|
||||
}
|
||||
}
|
||||
} while (feg.nextChar());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2020-2021 Igara Studio S.A.
|
||||
// Copyright (c) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -304,7 +304,7 @@ static uint32_t* col_diff_r;
|
||||
static uint32_t* col_diff_b;
|
||||
static uint32_t* col_diff_a;
|
||||
|
||||
static void initBestfit()
|
||||
void Palette::initBestfit()
|
||||
{
|
||||
col_diff.resize(4*128, 0);
|
||||
col_diff_g = &col_diff[128*0];
|
||||
@ -327,9 +327,7 @@ int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
|
||||
ASSERT(g >= 0 && g <= 255);
|
||||
ASSERT(b >= 0 && b <= 255);
|
||||
ASSERT(a >= 0 && a <= 255);
|
||||
|
||||
if (col_diff.empty())
|
||||
initBestfit();
|
||||
ASSERT(!col_diff.empty());
|
||||
|
||||
r >>= 3;
|
||||
g >>= 3;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2020-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -24,6 +24,8 @@ namespace doc {
|
||||
|
||||
class Palette : public Object {
|
||||
public:
|
||||
static void initBestfit();
|
||||
|
||||
Palette();
|
||||
Palette(frame_t frame, int ncolors);
|
||||
Palette(const Palette& palette);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2020 Igara Studio S.A.
|
||||
// Copyright (c) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2015 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -166,6 +166,9 @@ TEST(Remap, BetweenPalettesDontChangeMaskForced)
|
||||
|
||||
TEST(Remap, BetweenPalettesNonInvertible)
|
||||
{
|
||||
// create_remap_to_change_palette() uses findBestfit()
|
||||
doc::Palette::initBestfit();
|
||||
|
||||
Palette a(frame_t(0), 4);
|
||||
Palette b(frame_t(0), 3);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -17,6 +17,7 @@
|
||||
#include "base/exception.h"
|
||||
#include "base/memory.h"
|
||||
#include "base/system_console.h"
|
||||
#include "doc/palette.h"
|
||||
#include "os/error.h"
|
||||
#include "os/system.h"
|
||||
#include "ver/info.h"
|
||||
@ -93,6 +94,7 @@ int app_main(int argc, char* argv[])
|
||||
base::SystemConsole systemConsole;
|
||||
app::AppOptions options(argc, const_cast<const char**>(argv));
|
||||
os::SystemRef system(os::make_system());
|
||||
doc::Palette::initBestfit();
|
||||
app::App app;
|
||||
|
||||
#if ENABLE_SENTRY
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2021 Igara Studio S.A.
|
||||
Copyright (c) 2018-2022 Igara Studio S.A.
|
||||
Copyright (c) 2001-2018 David Capello
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
154
src/ui/entry.cpp
154
src/ui/entry.cpp
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -23,18 +23,26 @@
|
||||
#include "ui/size_hint_event.h"
|
||||
#include "ui/system.h"
|
||||
#include "ui/theme.h"
|
||||
#include "ui/timer.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
|
||||
// Shared timer between all entries.
|
||||
static std::unique_ptr<Timer> s_timer;
|
||||
|
||||
static inline bool is_word_char(int ch) {
|
||||
return (ch && !std::isspace(ch) && !std::ispunct(ch));
|
||||
}
|
||||
|
||||
Entry::Entry(const int maxsize, const char* format, ...)
|
||||
: Widget(kEntryWidget)
|
||||
, m_timer(500, this)
|
||||
, m_maxsize(maxsize)
|
||||
, m_caret(0)
|
||||
, m_scroll(0)
|
||||
@ -71,6 +79,7 @@ Entry::Entry(const int maxsize, const char* format, ...)
|
||||
|
||||
Entry::~Entry()
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void Entry::setMaxTextLength(const int maxsize)
|
||||
@ -92,14 +101,14 @@ void Entry::showCaret()
|
||||
{
|
||||
m_hidden = false;
|
||||
if (shouldStartTimer(hasFocus()))
|
||||
m_timer.start();
|
||||
startTimer();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Entry::hideCaret()
|
||||
{
|
||||
m_hidden = true;
|
||||
m_timer.stop();
|
||||
stopTimer();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ -135,7 +144,7 @@ void Entry::setCaretPos(int pos)
|
||||
}
|
||||
|
||||
if (shouldStartTimer(hasFocus()))
|
||||
m_timer.start();
|
||||
startTimer();
|
||||
m_state = true;
|
||||
|
||||
invalidate();
|
||||
@ -195,10 +204,18 @@ Entry::Range Entry::selectedRange() const
|
||||
|
||||
void Entry::setSuffix(const std::string& suffix)
|
||||
{
|
||||
if (m_suffix != suffix) {
|
||||
m_suffix = suffix;
|
||||
invalidate();
|
||||
}
|
||||
// No-op cases
|
||||
if ((!m_suffix && suffix.empty()) ||
|
||||
(m_suffix && *m_suffix == suffix))
|
||||
return;
|
||||
|
||||
m_suffix = std::make_unique<std::string>(suffix);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
std::string Entry::getSuffix()
|
||||
{
|
||||
return (m_suffix ? *m_suffix: std::string());
|
||||
}
|
||||
|
||||
void Entry::setTranslateDeadKeys(bool state)
|
||||
@ -224,16 +241,16 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
switch (msg->type()) {
|
||||
|
||||
case kTimerMessage:
|
||||
if (hasFocus() && static_cast<TimerMessage*>(msg)->timer() == &m_timer) {
|
||||
if (hasFocus() && static_cast<TimerMessage*>(msg)->timer() == s_timer.get()) {
|
||||
// Blinking caret
|
||||
m_state = m_state ? false: true;
|
||||
m_state = (m_state ? false: true);
|
||||
invalidate();
|
||||
}
|
||||
break;
|
||||
|
||||
case kFocusEnterMessage:
|
||||
if (shouldStartTimer(true))
|
||||
m_timer.start();
|
||||
startTimer();
|
||||
|
||||
m_state = true;
|
||||
invalidate();
|
||||
@ -254,7 +271,7 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
case kFocusLeaveMessage:
|
||||
invalidate();
|
||||
|
||||
m_timer.stop();
|
||||
stopTimer();
|
||||
|
||||
if (!m_lock_selection)
|
||||
deselectText();
|
||||
@ -374,6 +391,11 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
case kMouseDownMessage:
|
||||
captureMouse();
|
||||
|
||||
// Disable selecting words if we click again (only
|
||||
// double-clicking enables selecting words again).
|
||||
if (!m_selecting_words.isEmpty())
|
||||
m_selecting_words.reset();
|
||||
|
||||
case kMouseMoveMessage:
|
||||
if (hasCapture()) {
|
||||
bool is_dirty = false;
|
||||
@ -392,14 +414,28 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
m_recent_focused = false;
|
||||
m_select = m_caret;
|
||||
}
|
||||
else if (msg->type() == kMouseDownMessage)
|
||||
// Deselect
|
||||
else if (msg->type() == kMouseDownMessage) {
|
||||
m_select = m_caret;
|
||||
}
|
||||
// Continue selecting words
|
||||
else if (!m_selecting_words.isEmpty()) {
|
||||
Range toWord = wordRange(m_caret);
|
||||
if (toWord.from < m_selecting_words.from) {
|
||||
m_select = std::max(m_selecting_words.to, toWord.to);
|
||||
setCaretPos(std::min(m_selecting_words.from, toWord.from));
|
||||
}
|
||||
else {
|
||||
m_select = std::min(m_selecting_words.from, toWord.from);
|
||||
setCaretPos(std::max(m_selecting_words.to, toWord.to));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the caret
|
||||
if (is_dirty) {
|
||||
if (shouldStartTimer(true))
|
||||
m_timer.start();
|
||||
startTimer();
|
||||
m_state = true;
|
||||
}
|
||||
|
||||
@ -411,6 +447,9 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
if (hasCapture()) {
|
||||
releaseMouse();
|
||||
|
||||
if (!m_selecting_words.isEmpty())
|
||||
m_selecting_words.reset();
|
||||
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
if (mouseMsg->right()) {
|
||||
// This flag is disabled in kFocusEnterMessage message handler.
|
||||
@ -426,10 +465,11 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
if (!hasFocus())
|
||||
requestFocus();
|
||||
|
||||
forwardWord();
|
||||
m_select = m_caret;
|
||||
backwardWord();
|
||||
invalidate();
|
||||
m_selecting_words = wordRange(m_caret);
|
||||
selectText(m_selecting_words.from, m_selecting_words.to);
|
||||
|
||||
// Capture mouse to continue selecting words on kMouseMoveMessage
|
||||
captureMouse();
|
||||
return true;
|
||||
|
||||
case kMouseEnterMessage:
|
||||
@ -750,43 +790,30 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
#define IS_WORD_CHAR(ch) \
|
||||
(!((!ch) || (std::isspace(ch)) || \
|
||||
((ch) == '/') || ((ch) == '\\')))
|
||||
|
||||
void Entry::forwardWord()
|
||||
{
|
||||
int textlen = lastCaretPos();
|
||||
int ch;
|
||||
|
||||
for (; m_caret < textlen; ++m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (IS_WORD_CHAR(ch))
|
||||
if (is_word_char(m_boxes[m_caret].codepoint))
|
||||
break;
|
||||
}
|
||||
|
||||
for (; m_caret < textlen; ++m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (!IS_WORD_CHAR(ch)) {
|
||||
++m_caret;
|
||||
if (!is_word_char(m_boxes[m_caret].codepoint))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Entry::backwardWord()
|
||||
{
|
||||
int ch;
|
||||
|
||||
for (--m_caret; m_caret >= 0; --m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (IS_WORD_CHAR(ch))
|
||||
if (is_word_char(m_boxes[m_caret].codepoint))
|
||||
break;
|
||||
}
|
||||
|
||||
for (; m_caret >= 0; --m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (!IS_WORD_CHAR(ch)) {
|
||||
if (!is_word_char(m_boxes[m_caret].codepoint)) {
|
||||
++m_caret;
|
||||
break;
|
||||
}
|
||||
@ -796,6 +823,41 @@ void Entry::backwardWord()
|
||||
m_caret = 0;
|
||||
}
|
||||
|
||||
Entry::Range Entry::wordRange(int pos)
|
||||
{
|
||||
const int last = lastCaretPos();
|
||||
pos = base::clamp(pos, 0, last);
|
||||
|
||||
int i, j;
|
||||
i = j = pos;
|
||||
|
||||
// Select word space
|
||||
if (is_word_char(m_boxes[pos].codepoint)) {
|
||||
for (; i>=0; --i) {
|
||||
if (!is_word_char(m_boxes[i].codepoint))
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
for (; j<=last; ++j) {
|
||||
if (!is_word_char(m_boxes[j].codepoint))
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Select punctuation space
|
||||
else {
|
||||
for (; i>=0; --i) {
|
||||
if (is_word_char(m_boxes[i].codepoint))
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
for (; j<=last; ++j) {
|
||||
if (is_word_char(m_boxes[j].codepoint))
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Range(i, j);
|
||||
}
|
||||
|
||||
bool Entry::isPosInSelection(int pos)
|
||||
{
|
||||
return (pos >= std::min(m_caret, m_select) &&
|
||||
@ -863,9 +925,7 @@ void Entry::recalcCharBoxes(const std::string& text)
|
||||
{
|
||||
int lastTextIndex = int(text.size());
|
||||
CalcBoxesTextDelegate delegate(lastTextIndex);
|
||||
os::draw_text(nullptr, font(),
|
||||
base::utf8_const_iterator(text.begin()),
|
||||
base::utf8_const_iterator(text.end()),
|
||||
os::draw_text(nullptr, font(), text,
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0, &delegate);
|
||||
m_boxes = delegate.boxes();
|
||||
|
||||
@ -892,4 +952,20 @@ void Entry::deleteRange(const Range& range, std::string& text)
|
||||
m_caret = range.from;
|
||||
}
|
||||
|
||||
void Entry::startTimer()
|
||||
{
|
||||
if (s_timer)
|
||||
s_timer->stop();
|
||||
s_timer = std::make_unique<Timer>(500, this);
|
||||
s_timer->start();
|
||||
}
|
||||
|
||||
void Entry::stopTimer()
|
||||
{
|
||||
if (s_timer) {
|
||||
s_timer->stop();
|
||||
s_timer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -10,9 +10,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "obs/signal.h"
|
||||
#include "ui/timer.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class MouseMessage;
|
||||
@ -21,8 +22,11 @@ namespace ui {
|
||||
public:
|
||||
struct Range {
|
||||
int from = -1, to = -1;
|
||||
Range() { }
|
||||
Range(int from, int to) : from(from), to(to) { }
|
||||
bool isEmpty() const { return from < 0; }
|
||||
int size() const { return to - from; }
|
||||
void reset() { from = to = -1; }
|
||||
};
|
||||
|
||||
Entry(const int maxsize, const char *format, ...);
|
||||
@ -49,7 +53,7 @@ namespace ui {
|
||||
Range selectedRange() const;
|
||||
|
||||
void setSuffix(const std::string& suffix);
|
||||
const std::string& getSuffix() { return m_suffix; }
|
||||
std::string getSuffix();
|
||||
|
||||
void setTranslateDeadKeys(bool state);
|
||||
|
||||
@ -98,11 +102,14 @@ namespace ui {
|
||||
void executeCmd(EntryCmd cmd, int ascii, bool shift_pressed);
|
||||
void forwardWord();
|
||||
void backwardWord();
|
||||
Range wordRange(int pos);
|
||||
bool isPosInSelection(int pos);
|
||||
void showEditPopupMenu(const gfx::Point& pt);
|
||||
void recalcCharBoxes(const std::string& text);
|
||||
bool shouldStartTimer(const bool hasFocus);
|
||||
void deleteRange(const Range& range, std::string& text);
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
|
||||
class CalcBoxesTextDelegate;
|
||||
|
||||
@ -113,21 +120,21 @@ namespace ui {
|
||||
CharBox() { codepoint = from = to = width = 0; }
|
||||
};
|
||||
|
||||
typedef std::vector<CharBox> CharBoxes;
|
||||
using CharBoxes = std::vector<CharBox>;
|
||||
|
||||
CharBoxes m_boxes;
|
||||
Timer m_timer;
|
||||
int m_maxsize;
|
||||
int m_caret;
|
||||
int m_scroll;
|
||||
int m_select;
|
||||
bool m_hidden;
|
||||
bool m_state; // show or not the text caret
|
||||
bool m_readonly;
|
||||
bool m_recent_focused;
|
||||
bool m_lock_selection;
|
||||
bool m_translate_dead_keys;
|
||||
std::string m_suffix;
|
||||
bool m_hidden : 1;
|
||||
bool m_state : 1; // show or not the text caret
|
||||
bool m_readonly : 1;
|
||||
bool m_recent_focused : 1;
|
||||
bool m_lock_selection : 1;
|
||||
bool m_translate_dead_keys : 1;
|
||||
Range m_selecting_words;
|
||||
std::unique_ptr<std::string> m_suffix;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
@ -373,8 +373,7 @@ void Graphics::setFont(const os::FontRef& font)
|
||||
m_font = font;
|
||||
}
|
||||
|
||||
void Graphics::drawText(base::utf8_const_iterator it,
|
||||
const base::utf8_const_iterator& end,
|
||||
void Graphics::drawText(const std::string& str,
|
||||
gfx::Color fg, gfx::Color bg,
|
||||
const gfx::Point& origPt,
|
||||
os::DrawTextDelegate* delegate)
|
||||
@ -383,18 +382,11 @@ void Graphics::drawText(base::utf8_const_iterator it,
|
||||
|
||||
os::SurfaceLock lock(m_surface.get());
|
||||
gfx::Rect textBounds =
|
||||
os::draw_text(m_surface.get(), m_font.get(), it, end, fg, bg, pt.x, pt.y, delegate);
|
||||
os::draw_text(m_surface.get(), m_font.get(), str, fg, bg, pt.x, pt.y, delegate);
|
||||
|
||||
dirty(gfx::Rect(pt.x, pt.y, textBounds.w, textBounds.h));
|
||||
}
|
||||
|
||||
void Graphics::drawText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt)
|
||||
{
|
||||
drawText(base::utf8_const_iterator(str.begin()),
|
||||
base::utf8_const_iterator(str.end()),
|
||||
fg, bg, pt, nullptr);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class DrawUITextDelegate : public os::DrawTextDelegate {
|
||||
@ -468,10 +460,8 @@ void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
|
||||
int y = m_dy+pt.y;
|
||||
|
||||
DrawUITextDelegate delegate(m_surface.get(), m_font.get(), mnemonic);
|
||||
os::draw_text(m_surface.get(), m_font.get(),
|
||||
base::utf8_const_iterator(str.begin()),
|
||||
base::utf8_const_iterator(str.end()),
|
||||
fg, bg, x, y, &delegate);
|
||||
os::draw_text(m_surface.get(), m_font.get(), str,
|
||||
fg, bg, x, y, &delegate);
|
||||
|
||||
dirty(delegate.bounds());
|
||||
}
|
||||
@ -493,11 +483,9 @@ gfx::Size Graphics::measureUIText(const std::string& str)
|
||||
int Graphics::measureUITextLength(const std::string& str, os::Font* font)
|
||||
{
|
||||
DrawUITextDelegate delegate(nullptr, font, 0);
|
||||
os::draw_text(nullptr, font,
|
||||
base::utf8_const_iterator(str.begin()),
|
||||
base::utf8_const_iterator(str.end()),
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0,
|
||||
&delegate);
|
||||
os::draw_text(nullptr, font, str,
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0,
|
||||
&delegate);
|
||||
return delegate.bounds().w;
|
||||
}
|
||||
|
||||
|
@ -120,11 +120,10 @@ namespace ui {
|
||||
os::Font* font() { return m_font.get(); }
|
||||
void setFont(const os::FontRef& font);
|
||||
|
||||
void drawText(base::utf8_const_iterator it,
|
||||
const base::utf8_const_iterator& end,
|
||||
gfx::Color fg, gfx::Color bg, const gfx::Point& pt,
|
||||
os::DrawTextDelegate* delegate);
|
||||
void drawText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt);
|
||||
void drawText(const std::string& str,
|
||||
gfx::Color fg, gfx::Color bg,
|
||||
const gfx::Point& pt,
|
||||
os::DrawTextDelegate* delegate = nullptr);
|
||||
void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, const int mnemonic);
|
||||
void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, const int align);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2020-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -43,7 +43,7 @@ namespace ui {
|
||||
protected:
|
||||
virtual void onTick();
|
||||
|
||||
public:
|
||||
private:
|
||||
Widget* m_owner;
|
||||
int m_interval;
|
||||
bool m_running;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "base/clamp.h"
|
||||
#include "base/memory.h"
|
||||
#include "base/string.h"
|
||||
#include "base/utf8_decode.h"
|
||||
#include "os/font.h"
|
||||
#include "os/surface.h"
|
||||
#include "os/system.h"
|
||||
@ -1461,19 +1462,18 @@ void Widget::processMnemonicFromText(int escapeChar)
|
||||
if (!m_text.empty())
|
||||
newText.reserve(m_text.size());
|
||||
|
||||
for (base::utf8_const_iterator
|
||||
it(m_text.begin()),
|
||||
end(m_text.end()); it != end; ++it) {
|
||||
if (*it == escapeChar) {
|
||||
++it;
|
||||
if (it == end) {
|
||||
base::utf8_decode decode(m_text);
|
||||
while (int chr = decode.next()) {
|
||||
if (chr == escapeChar) {
|
||||
chr = decode.next();
|
||||
if (!chr) {
|
||||
break; // Ill-formed string (it ends with escape character)
|
||||
}
|
||||
else if (*it != escapeChar) {
|
||||
setMnemonic(*it);
|
||||
else if (chr != escapeChar) {
|
||||
setMnemonic(chr);
|
||||
}
|
||||
}
|
||||
newText.push_back(*it);
|
||||
newText.push_back(chr);
|
||||
}
|
||||
|
||||
setText(base::to_utf8(newText));
|
||||
|
1
third_party/CMakeLists.txt
vendored
1
third_party/CMakeLists.txt
vendored
@ -132,6 +132,7 @@ set(ENABLE_LIBXML2 OFF CACHE BOOL "Enable the use of the system libxml2 library
|
||||
set(ENABLE_CAT OFF CACHE BOOL "Enable cat building")
|
||||
set(ENABLE_TAR OFF CACHE BOOL "Enable tar building")
|
||||
set(ENABLE_CPIO OFF CACHE BOOL "Enable cpio building")
|
||||
set(ENABLE_LIBB2 OFF CACHE BOOL "Enable the use of the system LIBB2 library if found")
|
||||
add_subdirectory(libarchive)
|
||||
target_include_directories(archive_static INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libarchive/libarchive>)
|
||||
|
Loading…
x
Reference in New Issue
Block a user