mirror of https://github.com/aseprite/aseprite.git
Compare commits
16 Commits
fd1c6acd3c
...
a9160b5626
Author | SHA1 | Date |
---|---|---|
|
a9160b5626 | |
|
6c7544a132 | |
|
577caa4793 | |
|
f3a372e78e | |
|
11df0bc877 | |
|
41e5097c33 | |
|
cf290b7679 | |
|
d7b2faca6d | |
|
fa0b25fe87 | |
|
f26ce64208 | |
|
e39dc90001 | |
|
8f0dea0988 | |
|
e8f18087e5 | |
|
d8ac1a7a9e | |
|
5cfa75ab3f | |
|
3d21b99a97 |
|
@ -24,7 +24,8 @@ jobs:
|
|||
sudo apt-get update -qq
|
||||
sudo apt-get install -y \
|
||||
libpixman-1-dev libfreetype6-dev libharfbuzz-dev zlib1g-dev \
|
||||
libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev \
|
||||
libfontconfig1-dev
|
||||
- uses: aseprite/get-ninja@main
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
if: runner.os == 'Windows'
|
||||
|
|
|
@ -28,7 +28,8 @@ jobs:
|
|||
sudo apt-get update -qq
|
||||
sudo apt-get install -y \
|
||||
libpixman-1-dev libfreetype6-dev libharfbuzz-dev zlib1g-dev \
|
||||
libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev \
|
||||
libfontconfig1-dev
|
||||
- name: Install Skia
|
||||
if: ${{ matrix.ui == 'gui' }}
|
||||
shell: bash
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
apt_packages: |
|
||||
libc++-dev, libc++abi-dev, libpixman-1-dev,
|
||||
libfreetype6-dev, libharfbuzz-dev, zlib1g-dev, libx11-dev,
|
||||
libxcursor-dev, libxi-dev, libgl1-mesa-dev
|
||||
libxcursor-dev, libxi-dev, libxrandr-dev, libgl1-mesa-dev
|
||||
cmake_command: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DLAF_BACKEND=none -DCMAKE_EXPORT_COMPILE_COMMANDS=on
|
||||
- uses: ZedThree/clang-tidy-review/upload@v0.20.1
|
||||
|
|
|
@ -74,7 +74,7 @@ On macOS you will need macOS 15.4 SDK and Xcode 16.3 (older versions might work)
|
|||
|
||||
You will need the following dependencies on Ubuntu/Debian:
|
||||
|
||||
sudo apt-get install -y g++ clang cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
sudo apt-get install -y g++ clang cmake ninja-build libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
|
||||
Or use clang-12 packages (or newer) in case that clang in your distribution is older than clang 12.0:
|
||||
|
||||
|
@ -82,15 +82,15 @@ Or use clang-12 packages (or newer) in case that clang in your distribution is o
|
|||
|
||||
On Fedora:
|
||||
|
||||
sudo dnf install -y gcc-c++ clang libcxx-devel cmake ninja-build libX11-devel libXcursor-devel libXi-devel mesa-libGL-devel fontconfig-devel
|
||||
sudo dnf install -y gcc-c++ clang libcxx-devel cmake ninja-build libX11-devel libXcursor-devel libXi-devel libXrandr-devel mesa-libGL-devel fontconfig-devel
|
||||
|
||||
On Arch:
|
||||
|
||||
sudo pacman -S gcc clang cmake ninja libx11 libxcursor mesa-libgl fontconfig libwebp
|
||||
sudo pacman -S gcc clang cmake ninja libx11 libxcursor libxi libxrandr mesa-libgl fontconfig libwebp
|
||||
|
||||
On SUSE:
|
||||
|
||||
sudo zypper install gcc-c++ clang cmake ninja libX11-devel libXcursor-devel libXi-devel Mesa-libGL-devel fontconfig-devel
|
||||
sudo zypper install gcc-c++ clang cmake ninja libX11-devel libXcursor-devel libXi-devel libXrandr-devel Mesa-libGL-devel fontconfig-devel
|
||||
|
||||
# Automatic Building
|
||||
|
||||
|
|
2
laf
2
laf
|
@ -1 +1 @@
|
|||
Subproject commit ff3146071e86db059af6eef64fd24096554fa15e
|
||||
Subproject commit 39706c11063fb53cf4c8e865102c6f71e2606906
|
|
@ -325,10 +325,15 @@ void FilterManagerImpl::applyToTarget()
|
|||
void FilterManagerImpl::initTransaction()
|
||||
{
|
||||
ASSERT(!m_tx);
|
||||
m_writer.reset(new ContextWriter(m_reader));
|
||||
m_writer = std::make_unique<ContextWriter>(m_reader);
|
||||
m_tx.reset(new Tx(*m_writer, m_filter->getName(), ModifyDocument));
|
||||
}
|
||||
|
||||
void FilterManagerImpl::updateWriterThread()
|
||||
{
|
||||
document()->updateWriterThread();
|
||||
}
|
||||
|
||||
bool FilterManagerImpl::isTransaction() const
|
||||
{
|
||||
return (m_tx != nullptr);
|
||||
|
|
|
@ -93,6 +93,7 @@ public:
|
|||
void applyToTarget();
|
||||
|
||||
void initTransaction();
|
||||
void updateWriterThread();
|
||||
bool isTransaction() const;
|
||||
void commitTransaction();
|
||||
|
||||
|
|
|
@ -118,7 +118,13 @@ FilterWorker::~FilterWorker()
|
|||
|
||||
void FilterWorker::run()
|
||||
{
|
||||
// Initialize writting transaction
|
||||
// Initialize writing transaction from the main thread. This is
|
||||
// required to get the activeSite() from the UIContext from
|
||||
// CmdTransaction::calcSpritePosition().
|
||||
//
|
||||
// The document will keep the UI thread associated as the "writer"
|
||||
// thread, but that will be updated later in
|
||||
// applyFilterInBackground() with the worker thread ID.
|
||||
m_filterMgr->initTransaction();
|
||||
|
||||
std::thread thread;
|
||||
|
@ -182,6 +188,11 @@ bool FilterWorker::isCancelled()
|
|||
void FilterWorker::applyFilterInBackground()
|
||||
{
|
||||
try {
|
||||
// This background thread is the new writer. This is required to
|
||||
// avoid read-locking from the UI thread from Editor and Timeline
|
||||
// onPaint() events.
|
||||
m_filterMgr->updateWriterThread();
|
||||
|
||||
// Apply the filter
|
||||
m_filterMgr->applyToTarget();
|
||||
|
||||
|
|
|
@ -124,6 +124,11 @@ Doc::LockResult Doc::upgradeToWrite(int timeout)
|
|||
return res;
|
||||
}
|
||||
|
||||
void Doc::updateWriterThread()
|
||||
{
|
||||
m_rwLock.updateWriterThread();
|
||||
}
|
||||
|
||||
void Doc::downgradeToRead(LockResult lockResult)
|
||||
{
|
||||
DOC_TRACE("DOC: downgradeToRead", this, (int)lockResult);
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
LockResult readLock(int timeout);
|
||||
LockResult writeLock(int timeout);
|
||||
LockResult upgradeToWrite(int timeout);
|
||||
void updateWriterThread();
|
||||
void downgradeToRead(LockResult lockResult);
|
||||
void unlock(LockResult lockResult);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
|
@ -42,6 +42,7 @@
|
|||
#include "archive_entry.h"
|
||||
#include "json11.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
|
@ -535,6 +536,19 @@ void Extension::updateCategory(const Category newCategory)
|
|||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
static bool is_simple_id(const char* k)
|
||||
{
|
||||
if (!*k)
|
||||
return false;
|
||||
if (!std::isalpha(*k) && *k != '_')
|
||||
return false;
|
||||
for (const char* p = k + 1; *p; ++p) {
|
||||
if (!std::isalnum(*p) && *p != '_')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO move this to app/script/tableutils.h
|
||||
static void serialize_table(lua_State* L, int idx, std::string& result)
|
||||
{
|
||||
|
@ -555,7 +569,21 @@ static void serialize_table(lua_State* L, int idx, std::string& result)
|
|||
// Save key
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
if (const char* k = lua_tostring(L, -2)) {
|
||||
result += k;
|
||||
// If this is a simple identifier we can avoid using ["..."] to enclose
|
||||
// the key name.
|
||||
if (is_simple_id(k)) {
|
||||
result += k;
|
||||
}
|
||||
// In other cases, we enclose the key name in brackets and
|
||||
// double quotes ["..."] to avoid syntax errors when the name
|
||||
// has unicode characters.
|
||||
else {
|
||||
result.push_back('[');
|
||||
result.push_back('"');
|
||||
result += k;
|
||||
result.push_back('"');
|
||||
result.push_back(']');
|
||||
}
|
||||
result.push_back('=');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -406,7 +406,7 @@ void load_window_pos(Window* window, const char* section, const bool limitMinSiz
|
|||
if (get_multiple_displays()) {
|
||||
Rect frame = get_config_rect(section, "WindowFrame", gfx::Rect());
|
||||
if (!frame.isEmpty()) {
|
||||
limit_with_workarea(parentDisplay, frame);
|
||||
limit_least(frame);
|
||||
window->loadNativeFrame(frame);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
|
||||
// Increment this value if the scripting API is modified between two
|
||||
// released Aseprite versions.
|
||||
#define API_VERSION 35
|
||||
#define API_VERSION 36
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2810,7 +2810,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point*
|
|||
ASSERT(image);
|
||||
|
||||
std::unique_ptr<Mask> temp_mask;
|
||||
if (!mask) {
|
||||
if (!mask || mask->bounds().isEmpty()) {
|
||||
gfx::Rect visibleBounds = getVisibleSpriteBounds();
|
||||
gfx::Rect imageBounds = image->bounds();
|
||||
|
||||
|
|
|
@ -549,7 +549,10 @@ private:
|
|||
|
||||
class StatusBar::CustomizedTipWindow : public ui::TipWindow {
|
||||
public:
|
||||
CustomizedTipWindow(const std::string& text) : ui::TipWindow(text) {}
|
||||
CustomizedTipWindow(const std::string& text) : ui::TipWindow(text)
|
||||
{
|
||||
setClickBehavior(ClickBehavior::CloseOnClick);
|
||||
}
|
||||
|
||||
void setInterval(int msecs)
|
||||
{
|
||||
|
@ -557,9 +560,11 @@ public:
|
|||
m_timer.reset(new ui::Timer(msecs, this));
|
||||
else
|
||||
m_timer->setInterval(msecs);
|
||||
m_originalInterval = msecs;
|
||||
}
|
||||
|
||||
void startTimer() { m_timer->start(); }
|
||||
void startTimer() const { m_timer->start(); }
|
||||
int originalInterval() const { return m_originalInterval; }
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(Message* msg) override
|
||||
|
@ -569,12 +574,18 @@ protected:
|
|||
closeWindow(nullptr);
|
||||
m_timer->stop();
|
||||
break;
|
||||
case kMouseEnterMessage: m_timer->stop(); break;
|
||||
case kMouseLeaveMessage:
|
||||
m_timer->setInterval(m_originalInterval);
|
||||
m_timer->start();
|
||||
return true; // Avoid PopupWindow closing us.
|
||||
}
|
||||
return ui::TipWindow::onProcessMessage(msg);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ui::Timer> m_timer;
|
||||
int m_originalInterval = 0;
|
||||
};
|
||||
|
||||
// TODO Use a ui::TipWindow with rounded borders, when we add support
|
||||
|
|
|
@ -4631,13 +4631,13 @@ void Timeline::onDrop(ui::DragEvent& e)
|
|||
std::string txmsg;
|
||||
std::unique_ptr<docapi::DocProvider> docProvider = nullptr;
|
||||
if (droppedImage) {
|
||||
txmsg = "Dropped image on timeline";
|
||||
txmsg = "Drop Image";
|
||||
doc::ImageRef image = nullptr;
|
||||
convert_surface_to_image(surface.get(), 0, 0, surface->width(), surface->height(), image);
|
||||
docProvider = std::make_unique<DocProviderFromImage>(image);
|
||||
}
|
||||
else {
|
||||
txmsg = "Dropped paths on timeline";
|
||||
txmsg = "Drop File";
|
||||
docProvider = std::make_unique<DocProviderFromPaths>(m_document->context(), paths);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "app/ui/workspace.h"
|
||||
#include "app/ui/workspace_tabs.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -120,6 +121,10 @@ void UIContext::setActiveView(DocView* docView)
|
|||
mainWin->getTimeline()->updateUsingEditor(editor);
|
||||
mainWin->getPreviewEditor()->updateUsingEditor(editor);
|
||||
|
||||
// Update mouse widgets immediately after changing views rather
|
||||
// than waiting for mouse movement.
|
||||
mainWin->manager()->_updateMouseWidgets();
|
||||
|
||||
// Change the image-type of color bar.
|
||||
ColorBar::instance()->setPixelFormat(app_get_current_pixel_format());
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2024-2025 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
@ -26,7 +26,7 @@ uint32_t convert_color_to_image(gfx::Color c, const os::SurfaceFormatData* fd)
|
|||
uint8_t b = ((c & fd->blueMask) >> fd->blueShift);
|
||||
uint8_t a = ((c & fd->alphaMask) >> fd->alphaShift);
|
||||
|
||||
if (fd->pixelAlpha == os::PixelAlpha::kPremultiplied) {
|
||||
if (a > 0 && fd->pixelAlpha == os::PixelAlpha::kPremultiplied) {
|
||||
r = r * 255 / a;
|
||||
g = g * 255 / a;
|
||||
b = b * 255 / a;
|
||||
|
|
|
@ -20,21 +20,20 @@
|
|||
#include "ui/window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
|
||||
namespace ui {
|
||||
|
||||
#if 0 // TODO unused function, referenced in a comment in this file
|
||||
static gfx::Region get_workarea_region()
|
||||
// if the number of monitors changes during runtime this could break
|
||||
static const std::vector<gfx::Rect> get_all_workareas()
|
||||
{
|
||||
// Returns the union of the workarea of all available screens
|
||||
gfx::Region wa;
|
||||
std::vector<gfx::Rect> workareas;
|
||||
os::ScreenList screens;
|
||||
os::System::instance()->listScreens(screens);
|
||||
for (const auto& screen : screens)
|
||||
wa |= gfx::Region(screen->workarea());
|
||||
return wa;
|
||||
workareas.push_back(screen->workarea());
|
||||
return workareas;
|
||||
}
|
||||
#endif
|
||||
|
||||
int fit_bounds(Display* display, int arrowAlign, const gfx::Rect& target, gfx::Rect& bounds)
|
||||
{
|
||||
|
@ -120,7 +119,7 @@ void fit_bounds(const Display* parentDisplay,
|
|||
if (get_multiple_displays() && window->shouldCreateNativeWindow()) {
|
||||
const os::Window* nativeWindow = const_cast<ui::Display*>(parentDisplay)->nativeWindow();
|
||||
// Limit to the current screen workarea (instead of using all the
|
||||
// available workarea between all monitors, get_workarea_region())
|
||||
// available workarea between all monitors)
|
||||
const gfx::Rect workarea = nativeWindow->screen()->workarea();
|
||||
const int scale = nativeWindow->scale();
|
||||
|
||||
|
@ -158,38 +157,63 @@ void fit_bounds(const Display* parentDisplay,
|
|||
}
|
||||
}
|
||||
|
||||
// Limit window position using the union of all workareas
|
||||
//
|
||||
// TODO at least the title bar should be visible so we can
|
||||
// resize it, because workareas can form an irregular shape
|
||||
// (not rectangular) the calculation is a little more
|
||||
// complex
|
||||
void limit_with_workarea(Display* parentDisplay, gfx::Rect& frame)
|
||||
void limit_with_workarea(const gfx::Rect& workareaBounds, gfx::Rect& frame)
|
||||
{
|
||||
if (!get_multiple_displays())
|
||||
return;
|
||||
|
||||
ASSERT(parentDisplay);
|
||||
|
||||
gfx::Rect waBounds = parentDisplay->nativeWindow()->screen()->workarea();
|
||||
if (frame.x < waBounds.x)
|
||||
frame.x = waBounds.x;
|
||||
if (frame.y < waBounds.y)
|
||||
frame.y = waBounds.y;
|
||||
if (frame.x2() > waBounds.x2()) {
|
||||
frame.x -= frame.x2() - waBounds.x2();
|
||||
if (frame.x < waBounds.x) {
|
||||
frame.x = waBounds.x;
|
||||
frame.w = waBounds.w;
|
||||
if (frame.x < workareaBounds.x)
|
||||
frame.x = workareaBounds.x;
|
||||
if (frame.y < workareaBounds.y)
|
||||
frame.y = workareaBounds.y;
|
||||
if (frame.x2() > workareaBounds.x2()) {
|
||||
frame.x -= frame.x2() - workareaBounds.x2();
|
||||
if (frame.x < workareaBounds.x) {
|
||||
frame.x = workareaBounds.x;
|
||||
frame.w = workareaBounds.w;
|
||||
}
|
||||
}
|
||||
if (frame.y2() > waBounds.y2()) {
|
||||
frame.y -= frame.y2() - waBounds.y2();
|
||||
if (frame.y < waBounds.y) {
|
||||
frame.y = waBounds.y;
|
||||
frame.h = waBounds.h;
|
||||
if (frame.y2() > workareaBounds.y2()) {
|
||||
frame.y -= frame.y2() - workareaBounds.y2();
|
||||
if (frame.y < workareaBounds.y) {
|
||||
frame.y = workareaBounds.y;
|
||||
frame.h = workareaBounds.h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since sqrt(a) < sqrt(b) for all a < b, we only have to square to compare
|
||||
inline int distance_squared(const gfx::Rect& r1, const gfx::Rect& r2)
|
||||
{
|
||||
int dx = r1.x - r2.x;
|
||||
int dy = r1.y - r2.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
void limit_least(gfx::Rect& frame)
|
||||
{
|
||||
int min_distance = INT_MAX;
|
||||
gfx::Rect candidate(frame); // it shouldn't be possible for there to be no workareas but set
|
||||
// candidate to a valid Rect anyways
|
||||
|
||||
for (const auto& workarea : get_all_workareas()) {
|
||||
// simulate clamping
|
||||
gfx::Rect clone(frame);
|
||||
limit_with_workarea(workarea, clone);
|
||||
|
||||
// find the least clamped clone
|
||||
int distance = distance_squared(clone, frame);
|
||||
if (distance < min_distance) {
|
||||
min_distance = distance;
|
||||
|
||||
candidate.x = clone.x;
|
||||
candidate.y = clone.y;
|
||||
candidate.w = clone.w;
|
||||
candidate.h = clone.h;
|
||||
}
|
||||
}
|
||||
|
||||
frame.x = candidate.x;
|
||||
frame.y = candidate.y;
|
||||
frame.w = candidate.w;
|
||||
frame.h = candidate.h;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
@ -39,7 +39,8 @@ void fit_bounds(
|
|||
std::function<gfx::Rect(Widget*)> getWidgetBounds)> fitLogic = nullptr);
|
||||
|
||||
// The "frame" is a native windows frame bounds.
|
||||
void limit_with_workarea(Display* parentDisplay, gfx::Rect& frame);
|
||||
void limit_with_workarea(const gfx::Rect& workareaBounds, gfx::Rect& frame);
|
||||
void limit_least(gfx::Rect& frame);
|
||||
|
||||
} // namespace ui
|
||||
|
||||
|
|
|
@ -1440,7 +1440,7 @@ void Manager::_openWindow(Window* window, bool center)
|
|||
changeFrame = false;
|
||||
}
|
||||
|
||||
limit_with_workarea(parentDisplay, frame);
|
||||
limit_least(frame);
|
||||
|
||||
spec.position(os::WindowSpec::Position::Frame);
|
||||
spec.frame(frame);
|
||||
|
|
|
@ -157,6 +157,13 @@ bool PopupWindow::onProcessMessage(Message* msg)
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ClickBehavior::CloseOnClick: {
|
||||
if (bounds().contains(mouseMsg->position())) {
|
||||
closeWindow(nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -18,7 +18,8 @@ public:
|
|||
enum class ClickBehavior {
|
||||
DoNothingOnClick,
|
||||
CloseOnClickInOtherWindow,
|
||||
CloseOnClickOutsideHotRegion
|
||||
CloseOnClickOutsideHotRegion,
|
||||
CloseOnClick
|
||||
};
|
||||
|
||||
enum class EnterBehavior {
|
||||
|
|
Loading…
Reference in New Issue