Merge branch 'extensions' (#1403)

This commit is contained in:
David Capello 2017-06-14 21:02:00 -03:00
commit dafaf8d59f
107 changed files with 1677 additions and 201 deletions

6
.gitmodules vendored
View File

@ -45,3 +45,9 @@
[submodule "third_party/harfbuzz"]
path = third_party/harfbuzz
url = https://github.com/aseprite/harfbuzz.git
[submodule "third_party/libarchive"]
path = third_party/libarchive
url = https://github.com/aseprite/libarchive.git
[submodule "third_party/json11"]
path = third_party/json11
url = https://github.com/aseprite/json11.git

View File

@ -0,0 +1,24 @@
{
"name": "arne-palettes",
"displayName": "Arne Niklas Jansson Palettes",
"version": "1.0",
"author": { "name": "Arne Niklas Jansson", "url": "https://androidarts.com/" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "a64", "path": "./a64.gpl" },
{ "id": "arne-paldac", "path": "./arne-paldac.gpl" },
{ "id": "arne16", "path": "./arne16.gpl" },
{ "id": "arne32", "path": "./arne32.gpl" },
{ "id": "cg-arne", "path": "./cg-arne.gpl" },
{ "id": "copper-tech", "path": "./copper-tech.gpl" },
{ "id": "cpc-boy", "path": "./cpc-boy.gpl" },
{ "id": "eroge-copper", "path": "./eroge-copper.gpl" },
{ "id": "jmp", "path": "./jmp.gpl" },
{ "id": "psygnork", "path": "./psygnork.gpl" }
]
}
}

View File

@ -0,0 +1,20 @@
{
"name": "aseprite-theme",
"displayName": "Aseprite Default Theme",
"description": "Default Aseprite Pixel-Art Theme",
"version": "1.0",
"author": { "name": "David Capello", "email": "davidcapello@gmail.com", "url": "http://davidcapello.com/" },
"contributors": [
{ "name": "Ilija Melentijevic", "url": "http://ilkke.blogspot.com/" }
],
"publisher": "aseprite",
"license": "CC-BY-4.0",
"categories": [
"Themes"
],
"contributes": {
"themes": [
{ "id": "default", "path": "." }
]
}
}

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -890,5 +890,14 @@
<border part="simple_color_border" />
<border part="simple_color_selected" state="selected" />
</style>
<style id="list_item" border="1">
<background color="listitem_normal_face" />
<background color="listitem_selected_face" state="selected" />
<background color="face" state="disabled" />
<background color="listitem_selected_face" state="selected disabled" />
<text color="listitem_normal_text" align="left middle" x="1" />
<text color="listitem_selected_text" align="left middle" x="1" state="selected" />
<text color="disabled" align="left middle" x="1" state="disabled" />
</style>
</styles>
</theme>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,26 @@
{
"name": "bayer-matrices",
"displayName": "Bayer Matrices for Dithering",
"description": "Dithering matrices created by Bryce E. Bayer",
"version": "1.0",
"publisher": "aseprite",
"contributes": {
"ditheringMatrices": [
{
"id": "bayer8x8",
"name": "Bayer Matrix 8x8",
"path": "./bayer8x8.bmp"
},
{
"id": "bayer4x4",
"name": "Bayer Matrix 4x4",
"path": "./bayer4x4.bmp"
},
{
"id": "bayer2x2",
"name": "Bayer Matrix 2x2",
"path": "./bayer2x2.bmp"
}
]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "davitmasia-palettes",
"displayName": "Davit Masia Palettes",
"description": "Palettes contributed by Davit Masia",
"version": "1.0",
"author": { "name": "Davit Masia", "url": "https://twitter.com/DavitMasia" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "matriax8c", "path": "./matriax8c.gpl" }
]
}
}

View File

@ -0,0 +1,17 @@
{
"name": "dawnbringer-palettes",
"displayName": "Richard \"DawnBringer\" Fhager Palettes",
"description": "Palettes created by Richard \"DawnBringer\" Fhager",
"version": "1.0",
"author": { "name": "Richard Fhager", "url": "http://hem.fyristorg.com/dawnbringer/" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "db16", "path": "./db16.gpl" },
{ "id": "db32", "path": "./db32.gpl" }
]
}
}

View File

@ -0,0 +1,21 @@
{
"name": "endesga-palettes",
"displayName": "ENDESGA Studios Palettes",
"description": "Palettes contributed by ENDESGA Studios",
"version": "1.0",
"author": { "name": "ENDESGA Studios", "url": "https://twitter.com/ENDESGA" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "arq4", "path": "./arq4.gpl" },
{ "id": "edg16", "path": "./edg16.gpl" },
{ "id": "edg32", "path": "./edg32.gpl" },
{ "id": "edg8", "path": "./edg8.gpl" },
{ "id": "en4", "path": "./en4.gpl" },
{ "id": "enos16", "path": "./enos16.gpl" }
]
}
}

View File

@ -0,0 +1,36 @@
{
"name": "harware-palettes",
"displayName": "Hardware Palettes",
"description": "Well-known palettes from old computer hardware",
"version": "1.0",
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "apple-ii", "path": "./apple-ii.gpl" },
{ "id": "atari2600-ntsc", "path": "./atari2600-ntsc.gpl" },
{ "id": "atari2600-pal", "path": "./atari2600-pal.gpl" },
{ "id": "cga", "path": "./cga.gpl" },
{ "id": "cga0", "path": "./cga0.gpl" },
{ "id": "cga0hi", "path": "./cga0hi.gpl" },
{ "id": "cga1", "path": "./cga1.gpl" },
{ "id": "cga1hi", "path": "./cga1hi.gpl" },
{ "id": "cga3rd", "path": "./cga3rd.gpl" },
{ "id": "cga3rdhi", "path": "./cga3rdhi.gpl" },
{ "id": "commodore-plus4", "path": "./commodore-plus4.gpl" },
{ "id": "commodore-vic20", "path": "./commodore-vic20.gpl" },
{ "id": "commodore64", "path": "./commodore64.gpl" },
{ "id": "cpc", "path": "./cpc.gpl" },
{ "id": "gameboy-color-type1", "path": "./gameboy-color-type1.gpl" },
{ "id": "gameboy", "path": "./gameboy.gpl" },
{ "id": "master-system", "path": "./master-system.gpl" },
{ "id": "nes-ntsc", "path": "./nes-ntsc.gpl" },
{ "id": "nes", "path": "./nes.gpl" },
{ "id": "teletext", "path": "./teletext.gpl" },
{ "id": "vga-13h", "path": "./vga-13h.gpl" },
{ "id": "zx-spectrum", "path": "./zx-spectrum.gpl" }
]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "hyohnoo-palettes",
"displayName": "Hyohnoo Palettes",
"description": "Palettes contributed by Hyohnoo",
"version": "1.0",
"author": { "name": "Hyohnoo", "url": "https://twitter.com/Hyohnoo" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "mail24", "path": "./mail24.gpl" }
]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "javierguerrero-palettes",
"displayName": "Javier Guerrero Palettes",
"description": "Palettes contributed by Javier Guerrero",
"version": "1.0",
"author": { "name": "Javier Guerrero", "url": "https://twitter.com/Xavier_Gd" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "nyx8", "path": "./nyx8.gpl" }
]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "pico8-palettes",
"displayName": "PICO-8 Palette",
"description": "Palettes from PICO-8 created by Joseph White",
"version": "1.0",
"author": { "name": "Joseph White", "url": "http://www.pico-8.com/" },
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "pico-8", "path": "./pico-8.gpl" }
]
}
}

View File

@ -0,0 +1,21 @@
{
"name": "software-palettes",
"displayName": "Software Palettes",
"description": "Well-known palettes from software developers",
"version": "1.0",
"publisher": "aseprite",
"categories": [
"Palettes"
],
"contributes": {
"palettes": [
{ "id": "google-ui", "path": "./google-ui.gpl" },
{ "id": "monokai", "path": "./monokai.gpl" },
{ "id": "smile-basic", "path": "./smile-basic.gpl" },
{ "id": "solarized", "path": "./solarized.gpl" },
{ "id": "web-safe-colors", "path": "./web-safe-colors.gpl" },
{ "id": "win16", "path": "./win16.gpl" },
{ "id": "x11", "path": "./x11.gpl" }
]
}
}

View File

@ -268,6 +268,7 @@ section_grid = Grid
section_guides_and_slices = Guides && Slices
section_undo = Undo
section_theme = Theme
section_extensions = Extensions
section_experimental = Experimental
general = General
screen_scaling = Screen Scaling:
@ -391,6 +392,10 @@ undo_allow_nonlinear_history = Allow non-linear history
available_themes = Available Themes
select_theme = &Select
open_theme_folder = Open &Folder
add_extension = &Add Extension
disable_extension = &Disable
uninstall_extension = &Uninstall
open_extension_folder = Open &Folder
user_interface = User Interface
native_file_dialog = Use native file dialog
flash_selected_layer = Flash layer when it is selected

View File

@ -16,6 +16,7 @@
<listitem text="@.section_guides_and_slices" value="section_guides_and_slices" />
<listitem text="@.section_undo" value="section_undo" />
<listitem text="@.section_theme" value="section_theme" />
<listitem text="@.section_extensions" value="section_extensions" />
<listitem text="@.section_experimental" value="section_experimental" />
</listbox>
</view>
@ -270,6 +271,20 @@
</hbox>
</vbox>
<!-- Extensions -->
<vbox id="section_extensions">
<view expansive="true" maxsize="true">
<listbox id="extensions_list" />
</view>
<hbox>
<button id="add_extension" text="@.add_extension" minwidth="60" />
<boxfiller />
<button id="disable_extension" text="@.disable_extension" minwidth="60" />
<button id="uninstall_extension" text="@.uninstall_extension" minwidth="60" />
<button id="open_extension_folder" text="@.open_extension_folder" minwidth="60" />
</hbox>
</vbox>
<!-- Experimental -->
<vbox id="section_experimental">
<separator text="@.user_interface" horizontal="true" />

View File

@ -527,6 +527,94 @@ ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
```
# [json11](https://github.com/dropbox/json11/)
```
Copyright (c) 2013 Dropbox, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```
# [libarchive](http://www.libarchive.org/)
```
The libarchive distribution as a whole is Copyright by Tim Kientzle
and is subject to the copyright notice reproduced at the bottom of
this file.
Each individual file in this distribution should have a clear
copyright/licensing statement at the beginning of the file. If any do
not, please let me know and I will rectify it. The following is
intended to summarize the copyright status of the individual files;
the actual statements in the files are controlling.
* Except as listed below, all C sources (including .c and .h files)
and documentation files are subject to the copyright notice reproduced
at the bottom of this file.
* The following source files are also subject in whole or in part to
a 3-clause UC Regents copyright; please read the individual source
files for details:
libarchive/archive_entry.c
libarchive/archive_read_support_filter_compress.c
libarchive/archive_write_add_filter_compress.c
libarchive/mtree.5
* The following source files are in the public domain:
libarchive/archive_getdate.c
* The build files---including Makefiles, configure scripts,
and auxiliary scripts used as part of the compile process---have
widely varying licensing terms. Please check individual files before
distributing them to see if those restrictions apply to you.
I intend for all new source code to use the license below and hope over
time to replace code with other licenses with new implementations that
do use the license below. The varying licensing of the build scripts
seems to be an unavoidable mess.
Copyright (c) 2003-2009 <author(s)>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer
in this position and unchanged.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
# [libjpeg](http://www.ijg.org/)
```

View File

@ -41,8 +41,8 @@ add_custom_command(
DEPENDS gen)
list(APPEND generated_files ${output_fn})
# Generate theme.xml.h from data/themes/default/theme.xml
set(theme_xml ${CMAKE_SOURCE_DIR}/data/themes/default/theme.xml)
# Generate theme.xml.h from data/extensions/aseprite-theme/theme.xml
set(theme_xml ${CMAKE_SOURCE_DIR}/data/extensions/aseprite-theme/theme.xml)
set(output_fn ${CMAKE_CURRENT_BINARY_DIR}/theme.xml.h)
add_custom_command(
OUTPUT ${output_fn}
@ -82,6 +82,9 @@ if(WITH_WEBP_SUPPORT)
add_definitions(-DASEPRITE_WITH_WEBP_SUPPORT)
endif()
# libarchive definitions
add_definitions(-DLIBARCHIVE_STATIC)
######################################################################
# app-lib target
@ -363,6 +366,7 @@ add_library(app-lib
document_range.cpp
document_range_ops.cpp
document_undo.cpp
extensions.cpp
extra_cel.cpp
file/file.cpp
file/file_data.cpp
@ -381,6 +385,7 @@ add_library(app-lib
ini_file.cpp
job.cpp
launcher.cpp
load_matrix.cpp
log.cpp
loop_tag.cpp
modules.cpp
@ -539,7 +544,9 @@ target_link_libraries(app-lib
${WEBP_LIBRARIES}
${ZLIB_LIBRARIES}
${FREETYPE_LIBRARIES}
${HARFBUZZ_LIBRARIES})
${HARFBUZZ_LIBRARIES}
json11
archive_static)
if(ENABLE_SCRIPTING)
target_link_libraries(app-lib script-lib)

View File

@ -19,6 +19,7 @@
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/crash/data_recovery.h"
#include "app/extensions.h"
#include "app/file/file.h"
#include "app/file/file_formats_manager.h"
#include "app/file_system.h"
@ -96,6 +97,7 @@ public:
RecentFiles m_recent_files;
InputChain m_inputChain;
clipboard::ClipboardManager m_clipboardManager;
Extensions m_extensions;
// This is a raw pointer because we want to delete this explicitly.
app::crash::DataRecovery* m_recovery;
@ -208,7 +210,7 @@ void App::initialize(const AppOptions& options)
ui::Manager::getDefault()->invalidate();
}
// Procress options
// Process options
LOG("APP: Processing options...\n");
{
base::UniquePtr<CliDelegate> delegate;
@ -413,6 +415,11 @@ Preferences& App::preferences() const
return m_coreModules->m_preferences;
}
Extensions& App::extensions() const
{
return m_modules->m_extensions;
}
crash::DataRecovery* App::dataRecovery() const
{
return m_modules->recovery();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -31,6 +31,7 @@ namespace app {
class BackupIndicator;
class ContextBar;
class Document;
class Extensions;
class INotificationDelegate;
class InputChain;
class LegacyModules;
@ -81,6 +82,7 @@ namespace app {
ContextBar* contextBar() const;
Timeline* timeline() const;
Preferences& preferences() const;
Extensions& extensions() const;
crash::DataRecovery* dataRecovery() const;
AppBrushes& brushes() {

View File

@ -14,7 +14,8 @@
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/context_access.h"
#include "app/file/file.h"
#include "app/extensions.h"
#include "app/load_matrix.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
@ -376,45 +377,21 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
m_ditheringAlgorithm = render::DitheringAlgorithm::None;
std::string matrix = params.get("dithering-matrix");
// TODO object slicing here (from BayerMatrix -> DitheringMatrix
if (!matrix.empty()) {
if (matrix == "bayer2x2")
m_ditheringMatrix = render::BayerMatrix(2);
else if (matrix == "bayer4x4")
m_ditheringMatrix = render::BayerMatrix(4);
else if (matrix == "bayer8x8")
m_ditheringMatrix = render::BayerMatrix(8);
else {
// Load a matrix from a file
base::UniquePtr<doc::Document> doc(load_document(nullptr, matrix));
if (doc) {
// Flatten layers
doc::Sprite* spr = doc->sprite();
app::Context ctx;
cmd::FlattenLayers(spr).execute(&ctx);
const doc::Layer* lay = spr->root()->firstLayer();
const doc::Image* img = (lay && lay->cel(0) ?
lay->cel(0)->image(): nullptr);
if (img) {
const int w = spr->width();
const int h = spr->height();
m_ditheringMatrix = render::DitheringMatrix(h, w);
for (int i=0; i<h; ++i)
for (int j=0; j<w; ++j)
m_ditheringMatrix(i, j) = img->getPixel(j, i);
m_ditheringMatrix.calcMaxValue();
}
else {
m_ditheringMatrix = render::DitheringMatrix();
}
}
else
throw std::runtime_error("Invalid matrix name");
// Try to get the matrix from the extensions
const render::DitheringMatrix* knownMatrix =
App::instance()->extensions().ditheringMatrix(matrix);
if (knownMatrix) {
m_ditheringMatrix = *knownMatrix;
}
// Then, if the matrix doesn't exist we try to load it from a file
else if (!load_dithering_matrix_from_sprite(matrix, m_ditheringMatrix)) {
throw std::runtime_error("Invalid matrix name");
}
}
// Default dithering matrix is BayerMatrix(8)
else {
// TODO object slicing here (from BayerMatrix -> DitheringMatrix)
m_ditheringMatrix = render::BayerMatrix(8);
}
}

View File

@ -10,7 +10,10 @@
#include "app/app.h"
#include "app/commands/command.h"
#include "app/console.h"
#include "app/context.h"
#include "app/extensions.h"
#include "app/file_selector.h"
#include "app/ini_file.h"
#include "app/launcher.h"
#include "app/pref/preferences.h"
@ -34,6 +37,7 @@ namespace app {
static const char* kSectionBgId = "section_bg";
static const char* kSectionGridId = "section_grid";
static const char* kSectionThemeId = "section_theme";
static const char* kSectionExtensionsId = "section_extensions";
using namespace ui;
@ -52,8 +56,7 @@ class OptionsWindow : public app::gen::Options {
const std::string& themeName() const { return m_name; }
void openFolder() const {
app::launcher::open_folder(
m_name.empty() ? m_path: base::join_path(m_path, m_name));
app::launcher::open_folder(m_path);
}
bool canSelect() const {
@ -64,6 +67,57 @@ class OptionsWindow : public app::gen::Options {
std::string m_path;
std::string m_name;
};
class ExtensionItem : public ListItem {
public:
ExtensionItem(Extension* extension)
: ListItem(extension->displayName())
, m_extension(extension) {
setEnabled(extension->isEnabled());
}
bool isEnabled() const {
ASSERT(m_extension);
return m_extension->isEnabled();
}
bool isInstalled() const {
ASSERT(m_extension);
return m_extension->isInstalled();
}
bool canBeDisabled() const {
ASSERT(m_extension);
return m_extension->canBeDisabled();
}
bool canBeUninstalled() const {
ASSERT(m_extension);
return m_extension->canBeUninstalled();
}
void enable(bool state) {
ASSERT(m_extension);
App::instance()->extensions().enableExtension(m_extension, state);
setEnabled(m_extension->isEnabled());
}
void uninstall() {
ASSERT(m_extension);
ASSERT(canBeUninstalled());
App::instance()->extensions().uninstallExtension(m_extension);
m_extension = nullptr;
}
void openFolder() const {
ASSERT(m_extension);
app::launcher::open_folder(m_extension->path());
}
private:
Extension* m_extension;
};
public:
OptionsWindow(Context* context, int& curSection)
: m_pref(Preferences::instance())
@ -254,12 +308,24 @@ public:
selectTheme()->Click.connect(base::Bind<void>(&OptionsWindow::onSelectTheme, this));
openThemeFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onOpenThemeFolder, this));
// Extensions buttons
extensionsList()->Change.connect(base::Bind<void>(&OptionsWindow::onExtensionChange, this));
addExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onAddExtension, this));
disableExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onDisableExtension, this));
uninstallExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onUninstallExtension, this));
openExtensionFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onOpenExtensionFolder, this));
// Apply button
buttonApply()->Click.connect(base::Bind<void>(&OptionsWindow::saveConfig, this));
onChangeBgScope();
onChangeGridScope();
sectionListbox()->selectIndex(m_curSection);
// Reload themes when extensions are enabled/disabled
m_extThemesChanges =
App::instance()->extensions().ThemesChange.connect(
base::Bind<void>(&OptionsWindow::reloadThemes, this));
}
bool ok() {
@ -408,6 +474,9 @@ private:
// Load themes
else if (item->getValue() == kSectionThemeId)
loadThemes();
// Load extension
else if (item->getValue() == kSectionExtensionsId)
loadExtensions();
}
void onChangeBgScope() {
@ -514,15 +583,25 @@ private:
app::launcher::open_folder(app::main_config_filename());
}
void reloadThemes() {
while (themeList()->firstChild())
delete themeList()->lastChild();
loadThemes();
}
void loadThemes() {
// Themes already loaded
if (themeList()->getItemsCount() > 0)
return;
auto theme = skin::SkinTheme::instance();
auto userFolder = userThemeFolder();
auto folders = themeFolders();
std::sort(folders.begin(), folders.end());
const auto& selectedPath = theme->path();
bool first = true;
for (const auto& path : folders) {
auto files = base::list_files(path);
@ -530,17 +609,52 @@ private:
if (files.empty() && path != userFolder)
continue;
themeList()->addChild(new ThemeItem(path, std::string()));
std::sort(files.begin(), files.end());
for (auto& fn : files) {
if (!base::is_directory(base::join_path(path, fn)))
std::string fullPath =
base::normalize_path(
base::join_path(path, fn));
if (!base::is_directory(fullPath))
continue;
ThemeItem* item = new ThemeItem(path, fn);
if (first) {
first = false;
auto sep = new Separator(base::normalize_path(path), HORIZONTAL);
sep->setStyle(theme->styles.separatorInView());
themeList()->addChild(sep);
}
ThemeItem* item = new ThemeItem(fullPath, fn);
themeList()->addChild(item);
// Selected theme
if (fn == m_pref.theme.selected())
if (fullPath == selectedPath)
themeList()->selectChild(item);
}
}
// Themes from extensions
first = true;
for (auto ext : App::instance()->extensions()) {
if (!ext->isEnabled())
continue;
if (ext->themes().empty())
continue;
if (first) {
first = false;
auto sep = new Separator("Extension Themes", HORIZONTAL);
sep->setStyle(theme->styles.separatorInView());
themeList()->addChild(sep);
}
for (auto it : ext->themes()) {
ThemeItem* item = new ThemeItem(it.second, it.first);
themeList()->addChild(item);
// Selected theme
if (it.second == selectedPath)
themeList()->selectChild(item);
}
}
@ -548,9 +662,24 @@ private:
themeList()->layout();
}
void loadExtensions() {
// Extensions already loaded
if (extensionsList()->getItemsCount() > 0)
return;
for (auto extension : App::instance()->extensions()) {
ExtensionItem* item = new ExtensionItem(extension);
extensionsList()->addChild(item);
}
onExtensionChange();
extensionsList()->layout();
}
void onThemeChange() {
ThemeItem* item = dynamic_cast<ThemeItem*>(themeList()->getSelectedChild());
selectTheme()->setEnabled(item && item->canSelect());
openThemeFolder()->setEnabled(item != nullptr);
}
void onSelectTheme() {
@ -571,6 +700,86 @@ private:
item->openFolder();
}
void onExtensionChange() {
ExtensionItem* item = dynamic_cast<ExtensionItem*>(extensionsList()->getSelectedChild());
if (item && item->isInstalled()) {
disableExtension()->setText(item->isEnabled() ? "&Disable": "&Enable");
disableExtension()->processMnemonicFromText();
disableExtension()->setEnabled(item->isEnabled() ? item->canBeDisabled(): true);
uninstallExtension()->setEnabled(item->canBeUninstalled());
openExtensionFolder()->setEnabled(true);
}
else {
disableExtension()->setEnabled(false);
uninstallExtension()->setEnabled(false);
openExtensionFolder()->setEnabled(false);
}
}
void onAddExtension() {
FileSelectorFiles filename;
if (!app::show_file_selector(
"Add Extension", "", "zip",
FileSelectorType::Open, filename))
return;
ASSERT(!filename.empty());
try {
Extension* extension =
App::instance()->extensions().installCompressedExtension(filename.front());
// Add the new extension in the listbox
ExtensionItem* item = new ExtensionItem(extension);
extensionsList()->addChild(item);
extensionsList()->selectChild(item);
extensionsList()->layout();
}
catch (std::exception& ex) {
Console::showException(ex);
}
}
void onDisableExtension() {
ExtensionItem* item = dynamic_cast<ExtensionItem*>(extensionsList()->getSelectedChild());
if (item) {
item->enable(!item->isEnabled());
onExtensionChange();
}
}
void onUninstallExtension() {
ExtensionItem* item = dynamic_cast<ExtensionItem*>(extensionsList()->getSelectedChild());
if (!item)
return;
if (ui::Alert::show(
"Warning"
"<<Do you really want to uninstall '%s' extension?"
"||&Yes||&No",
item->text().c_str()) != 1)
return;
try {
item->uninstall();
// Remove the item from the list
extensionsList()->removeChild(item);
extensionsList()->layout();
item->deferDelete();
}
catch (std::exception& ex) {
Console::showException(ex);
}
}
void onOpenExtensionFolder() {
ExtensionItem* item = dynamic_cast<ExtensionItem*>(extensionsList()->getSelectedChild());
if (item)
item->openFolder();
}
void onCursorColorType() {
switch (cursorColorType()->getSelectedItemIndex()) {
case 0:
@ -594,7 +803,7 @@ private:
ResourceFinder rf;
rf.includeDataDir(skin::SkinTheme::kThemesFolderName);
// Create user folder to store skins
#if 0 // Don't create the user folder to store themes because now we prefer extensions
try {
if (!base::is_directory(rf.defaultFilename()))
base::make_all_directories(rf.defaultFilename());
@ -602,6 +811,7 @@ private:
catch (...) {
// Ignore errors
}
#endif
return base::normalize_path(rf.defaultFilename());
}
@ -621,6 +831,7 @@ private:
DocumentPreferences& m_docPref;
DocumentPreferences* m_curPref;
int& m_curSection;
obs::scoped_connection m_extThemesChanges;
};
class OptionsCommand : public Command {

609
src/app/extensions.cpp Normal file
View File

@ -0,0 +1,609 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/extensions.h"
#include "app/ini_file.h"
#include "app/load_matrix.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
#include "base/fstream_path.h"
#include "base/unique_ptr.h"
#include "render/dithering_matrix.h"
#include "archive.h"
#include "archive_entry.h"
#include "json11.hpp"
#include <fstream>
#include <queue>
#include <sstream>
#include <string>
namespace app {
namespace {
const char* kPackageJson = "package.json";
const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme";
class ReadArchive {
public:
ReadArchive(const std::string& filename)
: m_arch(nullptr), m_open(false) {
m_arch = archive_read_new();
archive_read_support_format_zip(m_arch);
m_file = base::open_file(filename, "rb");
if (!m_file)
throw base::Exception("Error loading file %s",
filename.c_str());
int err;
if ((err = archive_read_open_FILE(m_arch, m_file.get())))
throw base::Exception("Error uncompressing extension\n%s (%d)",
archive_error_string(m_arch), err);
m_open = true;
}
~ReadArchive() {
if (m_arch) {
if (m_open)
archive_read_close(m_arch);
archive_read_free(m_arch);
}
}
archive_entry* readEntry() {
archive_entry* entry;
int err = archive_read_next_header(m_arch, &entry);
if (err == ARCHIVE_EOF)
return nullptr;
if (err != ARCHIVE_OK)
throw base::Exception("Error uncompressing extension\n%s",
archive_error_string(m_arch));
return entry;
}
int copyDataTo(archive* out) {
const void* buf;
size_t size;
int64_t offset;
for (;;) {
int err = archive_read_data_block(m_arch, &buf, &size, &offset);
if (err == ARCHIVE_EOF)
break;
if (err != ARCHIVE_OK)
return err;
err = archive_write_data_block(out, buf, size, offset);
if (err != ARCHIVE_OK) {
throw base::Exception("Error writing data blocks\n%s (%d)",
archive_error_string(out), err);
return err;
}
}
return ARCHIVE_OK;
}
int copyDataTo(std::ostream& dst) {
const void* buf;
size_t size;
int64_t offset;
for (;;) {
int err = archive_read_data_block(m_arch, &buf, &size, &offset);
if (err == ARCHIVE_EOF)
break;
if (err != ARCHIVE_OK)
return err;
dst.write((const char*)buf, size);
}
return ARCHIVE_OK;
}
private:
base::FileHandle m_file;
archive* m_arch;
bool m_open;
};
class WriteArchive {
public:
WriteArchive()
: m_arch(nullptr)
, m_open(false) {
m_arch = archive_write_disk_new();
m_open = true;
}
~WriteArchive() {
if (m_arch) {
if (m_open)
archive_write_close(m_arch);
archive_write_free(m_arch);
}
}
void writeEntry(ReadArchive& in, archive_entry* entry) {
int err = archive_write_header(m_arch, entry);
if (err != ARCHIVE_OK)
throw base::Exception("Error writing file into disk\n%s (%d)",
archive_error_string(m_arch), err);
in.copyDataTo(m_arch);
err = archive_write_finish_entry(m_arch);
if (err != ARCHIVE_OK)
throw base::Exception("Error saving the last part of a file entry in disk\n%s (%d)",
archive_error_string(m_arch), err);
}
private:
archive* m_arch;
bool m_open;
};
} // anonymous namespace
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
{
if (!m_matrix) {
m_matrix = new render::DitheringMatrix;
load_dithering_matrix_from_sprite(m_path, *m_matrix);
}
return *m_matrix;
}
void Extension::DitheringMatrixInfo::destroyMatrix()
{
if (m_matrix)
delete m_matrix;
}
Extension::Extension(const std::string& path,
const std::string& name,
const std::string& displayName,
const bool isEnabled,
const bool isBuiltinExtension)
: m_path(path)
, m_name(name)
, m_displayName(displayName)
, m_isEnabled(isEnabled)
, m_isInstalled(true)
, m_isBuiltinExtension(isBuiltinExtension)
{
}
Extension::~Extension()
{
// Delete all matrices
for (auto& it : m_ditheringMatrices)
it.second.destroyMatrix();
}
void Extension::addTheme(const std::string& id, const std::string& path)
{
m_themes[id] = path;
}
void Extension::addPalette(const std::string& id, const std::string& path)
{
m_palettes[id] = path;
}
void Extension::addDitheringMatrix(const std::string& id,
const std::string& path,
const std::string& name)
{
DitheringMatrixInfo info(path, name);
m_ditheringMatrices[id] = info;
}
bool Extension::canBeDisabled() const
{
return (m_isEnabled &&
!isCurrentTheme() &&
!isDefaultTheme()); // Default theme cannot be disabled or uninstalled
}
bool Extension::canBeUninstalled() const
{
return (!m_isBuiltinExtension &&
!isCurrentTheme() &&
!isDefaultTheme());
}
void Extension::enable(const bool state)
{
// Do nothing
if (m_isEnabled == state)
return;
set_config_bool("extensions", m_name.c_str(), state);
flush_config_file();
m_isEnabled = state;
}
void Extension::uninstall()
{
if (!m_isInstalled)
return;
ASSERT(canBeUninstalled());
if (!canBeUninstalled())
return;
TRACE("EXT: Uninstall extension '%s' from '%s'...\n",
m_name.c_str(), m_path.c_str());
// Remove all files inside the extension path
uninstallFiles(m_path);
ASSERT(!base::is_directory(m_path));
m_isEnabled = false;
m_isInstalled = false;
}
void Extension::uninstallFiles(const std::string& path)
{
for (auto& item : base::list_files(path)) {
std::string fn = base::join_path(path, item);
if (base::is_file(fn)) {
TRACE("EXT: Deleting file '%s'\n", fn.c_str());
base::delete_file(fn);
}
else if (base::is_directory(fn)) {
uninstallFiles(fn);
}
}
TRACE("EXT: Deleting directory '%s'\n", path.c_str());
base::remove_directory(path);
}
bool Extension::isCurrentTheme() const
{
auto it = m_themes.find(Preferences::instance().theme.selected());
return (it != m_themes.end());
}
bool Extension::isDefaultTheme() const
{
return (name() == kAsepriteDefaultThemeExtensionName);
}
Extensions::Extensions()
{
// Create and get the user extensions directory
{
ResourceFinder rf2;
rf2.includeUserDir("extensions/.");
m_userExtensionsPath = rf2.getFirstOrCreateDefault();
m_userExtensionsPath = base::normalize_path(m_userExtensionsPath);
m_userExtensionsPath = base::get_file_path(m_userExtensionsPath);
LOG("EXT: User extensions path '%s'\n", m_userExtensionsPath.c_str());
}
ResourceFinder rf;
rf.includeUserDir("extensions");
rf.includeDataDir("extensions");
// Load extensions from data/ directory on all possible locations
// (installed folder and user folder)
while (rf.next()) {
auto extensionsDir = rf.filename();
if (base::is_directory(extensionsDir)) {
for (auto fn : base::list_files(extensionsDir)) {
const auto dir = base::join_path(extensionsDir, fn);
if (!base::is_directory(dir))
continue;
const bool isBuiltinExtension =
(m_userExtensionsPath != base::get_file_path(dir));
auto fullFn = base::join_path(dir, kPackageJson);
fullFn = base::normalize_path(fullFn);
LOG("EXT: Loading extension '%s'...\n", fullFn.c_str());
if (!base::is_file(fullFn)) {
LOG("EXT: File '%s' not found\n", fullFn.c_str());
continue;
}
try {
loadExtension(dir, fullFn, isBuiltinExtension);
}
catch (const std::exception& ex) {
LOG("EXT: Error loading JSON file: %s\n",
ex.what());
}
}
}
}
}
Extensions::~Extensions()
{
for (auto ext : m_extensions)
delete ext;
}
std::string Extensions::themePath(const std::string& themeId)
{
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled extensions
continue;
auto it = ext->themes().find(themeId);
if (it != ext->themes().end())
return it->second;
}
return std::string();
}
std::string Extensions::palettePath(const std::string& palId)
{
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled extensions
continue;
auto it = ext->palettes().find(palId);
if (it != ext->palettes().end())
return it->second;
}
return std::string();
}
ExtensionItems Extensions::palettes() const
{
ExtensionItems palettes;
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled themes
continue;
for (auto item : ext->palettes())
palettes[item.first] = item.second;
}
return palettes;
}
const render::DitheringMatrix* Extensions::ditheringMatrix(const std::string& matrixId)
{
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled themes
continue;
auto it = ext->m_ditheringMatrices.find(matrixId);
if (it != ext->m_ditheringMatrices.end())
return &it->second.matrix();
}
return nullptr;
}
std::vector<Extension::DitheringMatrixInfo> Extensions::ditheringMatrices()
{
std::vector<Extension::DitheringMatrixInfo> result;
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled themes
continue;
for (auto it : ext->m_ditheringMatrices)
result.push_back(it.second);
}
return result;
}
void Extensions::enableExtension(Extension* extension, const bool state)
{
extension->enable(state);
generateExtensionSignals(extension);
}
void Extensions::uninstallExtension(Extension* extension)
{
extension->uninstall();
generateExtensionSignals(extension);
}
Extension* Extensions::installCompressedExtension(const std::string& zipFn)
{
std::string dstExtensionPath =
base::join_path(m_userExtensionsPath,
base::get_file_title(zipFn));
// First of all we read the package.json file inside the .zip to
// know 1) the extension name, 2) that the .json file can be parsed
// correctly, 3) the final destination directory.
std::string commonPath;
{
ReadArchive in(zipFn);
archive_entry* entry;
while ((entry = in.readEntry()) != nullptr) {
const std::string entryFn = archive_entry_pathname(entry);
if (base::get_file_name(entryFn) != kPackageJson)
continue;
commonPath = base::get_file_path(entryFn);
if (!commonPath.empty() &&
entryFn.size() > commonPath.size())
commonPath.push_back(entryFn[commonPath.size()]);
std::stringstream out;
in.copyDataTo(out);
std::string err;
auto json = json11::Json::parse(out.str(), err);
if (err.empty()) {
auto name = json["name"].string_value();
dstExtensionPath = base::join_path(m_userExtensionsPath, name);
}
break;
}
}
// Uncompress zipFn in dstExtensionPath
{
ReadArchive in(zipFn);
WriteArchive out;
archive_entry* entry;
while ((entry = in.readEntry()) != nullptr) {
// Fix the entry filename to write the file in the disk
std::string fn = archive_entry_pathname(entry);
LOG("EXT: Original filename in zip <%s>...\n", fn.c_str());
if (!commonPath.empty()) {
// Check mismatch with package.json common path
if (fn.compare(0, commonPath.size(), commonPath) != 0)
continue;
fn.erase(0, commonPath.size());
if (fn.empty())
continue;
}
const std::string fullFn = base::join_path(dstExtensionPath, fn);
archive_entry_set_pathname(entry, fullFn.c_str());
LOG("EXT: Uncompressing file <%s> to <%s>\n",
fn.c_str(), fullFn.c_str());
out.writeEntry(in, entry);
}
}
Extension* extension = loadExtension(
dstExtensionPath,
base::join_path(dstExtensionPath, kPackageJson),
false);
if (!extension)
throw base::Exception("Error adding the new extension");
// Generate signals
NewExtension(extension);
generateExtensionSignals(extension);
return extension;
}
Extension* Extensions::loadExtension(const std::string& path,
const std::string& fullPackageFilename,
const bool isBuiltinExtension)
{
json11::Json json;
{
std::string jsonText, line;
std::ifstream in(FSTREAM_PATH(fullPackageFilename), std::ifstream::binary);
while (std::getline(in, line)) {
jsonText += line;
jsonText.push_back('\n');
}
std::string err;
json = json11::Json::parse(jsonText, err);
if (!err.empty())
throw base::Exception("Error parsing JSON file: %s\n",
err.c_str());
}
auto name = json["name"].string_value();
auto displayName = json["displayName"].string_value();
LOG("EXT: Extension '%s' loaded\n", name.c_str());
base::UniquePtr<Extension> extension(
new Extension(path,
name,
displayName,
// Extensions are enabled by default
get_config_bool("extensions", name.c_str(), true),
isBuiltinExtension));
auto contributes = json["contributes"];
if (contributes.is_object()) {
// Themes
auto themes = contributes["themes"];
if (themes.is_array()) {
for (const auto& theme : themes.array_items()) {
std::string themeId = theme["id"].string_value();
std::string themePath = theme["path"].string_value();
// The path must be always relative to the extension
themePath = base::join_path(path, themePath);
LOG("EXT: New theme '%s' in '%s'\n",
themeId.c_str(),
themePath.c_str());
extension->addTheme(themeId, themePath);
}
}
// Palettes
auto palettes = contributes["palettes"];
if (palettes.is_array()) {
for (const auto& palette : palettes.array_items()) {
std::string palId = palette["id"].string_value();
std::string palPath = palette["path"].string_value();
// The path must be always relative to the extension
palPath = base::join_path(path, palPath);
LOG("EXT: New palette '%s' in '%s'\n",
palId.c_str(),
palPath.c_str());
extension->addPalette(palId, palPath);
}
}
// Dithering matrices
auto ditheringMatrices = contributes["ditheringMatrices"];
if (ditheringMatrices.is_array()) {
for (const auto& ditheringMatrix : ditheringMatrices.array_items()) {
std::string matId = ditheringMatrix["id"].string_value();
std::string matPath = ditheringMatrix["path"].string_value();
std::string matName = ditheringMatrix["name"].string_value();
if (matName.empty())
matName = matId;
// The path must be always relative to the extension
matPath = base::join_path(path, matPath);
LOG("EXT: New dithering matrix '%s' in '%s'\n",
matId.c_str(),
matPath.c_str());
extension->addDitheringMatrix(matId, matPath, matName);
}
}
}
if (extension)
m_extensions.push_back(extension.get());
return extension.release();
}
void Extensions::generateExtensionSignals(Extension* extension)
{
if (extension->hasThemes()) ThemesChange(extension);
if (extension->hasPalettes()) PalettesChange(extension);
if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension);
}
} // namespace app

135
src/app/extensions.h Normal file
View File

@ -0,0 +1,135 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_EXTENSIONS_H_INCLUDED
#define APP_EXTENSIONS_H_INCLUDED
#pragma once
#include "base/unique_ptr.h"
#include "obs/signal.h"
#include <map>
#include <string>
#include <vector>
namespace render {
class DitheringMatrix;
}
namespace app {
// Key=theme/palette/etc. id
// Value=theme/palette/etc. path
typedef std::map<std::string, std::string> ExtensionItems;
class Extensions;
class Extension {
friend class Extensions;
public:
class DitheringMatrixInfo {
public:
DitheringMatrixInfo() : m_matrix(nullptr) { }
DitheringMatrixInfo(const std::string& path,
const std::string& name)
: m_path(path), m_name(name), m_matrix(nullptr) { }
const std::string& name() const { return m_name; }
const render::DitheringMatrix& matrix() const;
void destroyMatrix();
private:
std::string m_path;
std::string m_name;
mutable render::DitheringMatrix* m_matrix;
};
Extension(const std::string& path,
const std::string& name,
const std::string& displayName,
const bool isEnabled,
const bool isBuiltinExtension);
~Extension();
const std::string& path() const { return m_path; }
const std::string& name() const { return m_name; }
const std::string& displayName() const { return m_displayName; }
const ExtensionItems& themes() const { return m_themes; }
const ExtensionItems& palettes() const { return m_palettes; }
void addTheme(const std::string& id, const std::string& path);
void addPalette(const std::string& id, const std::string& path);
void addDitheringMatrix(const std::string& id,
const std::string& path,
const std::string& name);
bool isEnabled() const { return m_isEnabled; }
bool isInstalled() const { return m_isInstalled; }
bool canBeDisabled() const;
bool canBeUninstalled() const;
bool hasThemes() const { return !m_themes.empty(); }
bool hasPalettes() const { return !m_palettes.empty(); }
bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); }
private:
void enable(const bool state);
void uninstall();
void uninstallFiles(const std::string& path);
bool isCurrentTheme() const;
bool isDefaultTheme() const;
ExtensionItems m_themes;
ExtensionItems m_palettes;
std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices;
std::string m_path;
std::string m_name;
std::string m_displayName;
bool m_isEnabled;
bool m_isInstalled;
bool m_isBuiltinExtension;
};
class Extensions {
public:
typedef std::vector<Extension*> List;
typedef List::iterator iterator;
Extensions();
~Extensions();
iterator begin() { return m_extensions.begin(); }
iterator end() { return m_extensions.end(); }
void enableExtension(Extension* extension, const bool state);
void uninstallExtension(Extension* extension);
Extension* installCompressedExtension(const std::string& zipFn);
std::string themePath(const std::string& themeId);
std::string palettePath(const std::string& palId);
ExtensionItems palettes() const;
const render::DitheringMatrix* ditheringMatrix(const std::string& matrixId);
std::vector<Extension::DitheringMatrixInfo> ditheringMatrices();
obs::signal<void(Extension*)> NewExtension;
obs::signal<void(Extension*)> ThemesChange;
obs::signal<void(Extension*)> PalettesChange;
obs::signal<void(Extension*)> DitheringMatricesChange;
private:
Extension* loadExtension(const std::string& path,
const std::string& fullPackageFilename,
const bool isBuiltinExtension);
void generateExtensionSignals(Extension* extension);
List m_extensions;
std::string m_userExtensionsPath;
};
} // namespace app
#endif

52
src/app/load_matrix.cpp Normal file
View File

@ -0,0 +1,52 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/load_matrix.h"
#include "app/context.h"
#include "app/document.h"
#include "app/file/file.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "render/dithering_matrix.h"
namespace app {
bool load_dithering_matrix_from_sprite(
const std::string& filename,
render::DitheringMatrix& matrix)
{
base::UniquePtr<doc::Document> doc(load_document(nullptr, filename));
if (!doc)
return false;
doc::Sprite* spr = doc->sprite();
const doc::Layer* lay = (spr && spr->root() ? spr->root()->firstLayer():
nullptr);
const doc::Image* img = (lay && lay->cel(0) ? lay->cel(0)->image():
nullptr);
if (img) {
const int w = spr->width();
const int h = spr->height();
matrix = render::DitheringMatrix(h, w);
for (int i=0; i<h; ++i)
for (int j=0; j<w; ++j)
matrix(i, j) = img->getPixel(j, i);
matrix.calcMaxValue();
}
else {
matrix = render::DitheringMatrix();
}
return true;
}
} // namespace app

25
src/app/load_matrix.h Normal file
View File

@ -0,0 +1,25 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_LOAD_MATRIX_H_INCLUDED
#define APP_LOAD_MATRIX_H_INCLUDED
#pragma once
#include <string>
namespace render {
class DitheringMatrix;
};
namespace app {
bool load_dithering_matrix_from_sprite(
const std::string& filename,
render::DitheringMatrix& matrix);
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -11,6 +11,7 @@
#include "app/modules/palettes.h"
#include "app/app.h"
#include "app/extensions.h"
#include "app/file/palette_file.h"
#include "app/resource_finder.h"
#include "base/fs.h"
@ -98,11 +99,11 @@ void load_default_palette()
// If the default palette file doesn't exist, we copy db32.gpl
// as the default one (default.ase).
else {
ResourceFinder rf;
rf.includeDataDir("palettes/db32.gpl");
if (rf.findFirst()) {
pal.reset(load_palette(rf.filename().c_str()));
}
std::string path = App::instance()->extensions().palettePath("db32");
if (path.empty())
path = App::instance()->extensions().palettePath("vga-13h");
if (!path.empty())
pal.reset(load_palette(path.c_str()));
}
// Save default.ase file

View File

@ -17,7 +17,7 @@ void RenderTaskJob::onJob()
try {
m_func();
}
catch (std::exception& ex) {
catch (const std::exception&) {
// TODO show the exception
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -18,17 +18,22 @@ namespace app {
class PaletteResource : public Resource {
public:
PaletteResource(doc::Palette* palette, const std::string& name)
: m_palette(palette)
, m_name(name) {
PaletteResource(const std::string& id,
const std::string& path,
doc::Palette* palette)
: m_id(id)
, m_path(path)
, m_palette(palette) {
}
virtual ~PaletteResource() { }
virtual const std::string& id() const override { return m_id; }
virtual const std::string& path() const override { return m_path; }
virtual doc::Palette* palette() { return m_palette; }
virtual const std::string& name() const override { return m_name; }
private:
std::string m_id;
std::string m_path;
doc::Palette* m_palette;
std::string m_name;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,6 +10,8 @@
#include "app/res/palettes_loader_delegate.h"
#include "app/app.h"
#include "app/extensions.h"
#include "app/file/palette_file.h"
#include "app/file_system.h"
#include "app/res/palette_resource.h"
@ -21,27 +23,43 @@
namespace app {
std::string PalettesLoaderDelegate::resourcesLocation() const
void PalettesLoaderDelegate::getResourcesPaths(std::map<std::string, std::string>& idAndPath) const
{
// Include extension palettes
idAndPath = App::instance()->extensions().palettes();
// Search old palettes too
std::string path;
ResourceFinder rf;
rf.includeDataDir("palettes");
rf.includeDataDir("palettes"); // data/palettes/ in all places
rf.includeUserDir("palettes"); // palettes/ in user home
while (rf.next()) {
if (base::is_directory(rf.filename())) {
path = rf.filename();
break;
path = base::fix_path_separators(path);
for (const auto& fn : base::list_files(path)) {
// Ignore the default palette that is inside the palettes/ dir
// in the user home dir.
if (fn == "default.ase" ||
fn == "default.gpl")
continue;
std::string fullFn = base::join_path(path, fn);
if (base::is_file(fullFn))
idAndPath[base::get_file_title(fn)] = fullFn;
}
}
}
return base::fix_path_separators(path);
}
Resource* PalettesLoaderDelegate::loadResource(const std::string& filename)
Resource* PalettesLoaderDelegate::loadResource(const std::string& id,
const std::string& path)
{
doc::Palette* palette = load_palette(filename.c_str());
if (!palette)
return NULL;
return new PaletteResource(palette, base::get_file_title(filename));
doc::Palette* palette = load_palette(path.c_str());
if (palette)
return new PaletteResource(id, path, palette);
else
return nullptr;
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -15,8 +15,9 @@ namespace app {
class PalettesLoaderDelegate : public ResourcesLoaderDelegate {
public:
// ResourcesLoaderDelegate impl
virtual std::string resourcesLocation() const override;
virtual Resource* loadResource(const std::string& filename) override;
virtual void getResourcesPaths(std::map<std::string, std::string>& idAndPath) const override;
virtual Resource* loadResource(const std::string& id,
const std::string& path) override;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,7 +13,8 @@ namespace app {
class Resource {
public:
virtual ~Resource() { }
virtual const std::string& name() const = 0;
virtual const std::string& id() const = 0;
virtual const std::string& path() const = 0;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -24,13 +24,16 @@ ResourcesLoader::ResourcesLoader(ResourcesLoaderDelegate* delegate)
: m_delegate(delegate)
, m_done(false)
, m_cancel(false)
, m_thread(base::Bind<void>(&ResourcesLoader::threadLoadResources, this))
, m_thread(
new base::thread(
base::Bind<void>(&ResourcesLoader::threadLoadResources, this)))
{
}
ResourcesLoader::~ResourcesLoader()
{
m_thread.join();
if (m_thread)
m_thread->join();
}
void ResourcesLoader::cancel()
@ -49,31 +52,43 @@ bool ResourcesLoader::next(base::UniquePtr<Resource>& resource)
return false;
}
void ResourcesLoader::reload()
{
if (m_thread) {
m_thread->join();
m_thread.reset(nullptr);
}
m_thread.reset(createThread());
}
void ResourcesLoader::threadLoadResources()
{
base::ScopedValue<bool> scoped(m_done, false, true);
std::string path = m_delegate->resourcesLocation();
TRACE("RESLOAD: Loading resources from %s...\n", path.c_str());
if (path.empty())
return;
FileSystemModule* fs = FileSystemModule::instance();
LockFS lock(fs);
IFileItem* item = fs->getFileItemFromPath(path);
if (!item)
return;
FileItemList list = item->children();
for (auto child : list) {
// Load resources from extensions
std::map<std::string, std::string> idAndPaths;
m_delegate->getResourcesPaths(idAndPaths);
for (const auto& idAndPath : idAndPaths) {
if (m_cancel)
break;
Resource* resource = m_delegate->loadResource((child)->fileName());
TRACE("RESLOAD: Loading resource '%s' from '%s'...\n",
idAndPath.first.c_str(),
idAndPath.second.c_str());
Resource* resource =
m_delegate->loadResource(idAndPath.first,
idAndPath.second);
if (resource)
m_queue.push(resource);
}
}
base::thread* ResourcesLoader::createThread()
{
return new base::thread(
base::Bind<void>(&ResourcesLoader::threadLoadResources, this));
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -24,11 +24,12 @@ namespace app {
void cancel();
bool isDone() const { return m_done; }
bool next(base::UniquePtr<Resource>& resource);
void reload();
private:
void threadLoadResources();
base::thread* createThread();
typedef base::concurrent_queue<Resource*> Queue;
@ -36,7 +37,7 @@ namespace app {
bool m_done;
bool m_cancel;
Queue m_queue;
base::thread m_thread;
base::UniquePtr<base::thread> m_thread;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,6 +8,7 @@
#define APP_RES_RESOURCES_LOADER_DELEGATE_H_INCLUDED
#pragma once
#include <map>
#include <string>
namespace app {
@ -17,8 +18,9 @@ namespace app {
class ResourcesLoaderDelegate {
public:
virtual ~ResourcesLoaderDelegate() { }
virtual std::string resourcesLocation() const = 0;
virtual Resource* loadResource(const std::string& filename) = 0;
virtual void getResourcesPaths(std::map<std::string, std::string>& idAndPath) const = 0;
virtual Resource* loadResource(const std::string& id,
const std::string& path) = 0;
};
} // namespace app

View File

@ -10,6 +10,8 @@
#include "app/ui/dithering_selector.h"
#include "app/app.h"
#include "app/extensions.h"
#include "app/modules/palettes.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
@ -157,35 +159,53 @@ private:
} // anonymous namespace
DitheringSelector::DitheringSelector(Type type)
: m_type(type)
{
setUseCustomWidget(true);
Extensions& extensions = App::instance()->extensions();
switch (type) {
// If an extension with "ditheringMatrices" is disable/enable, we
// regenerate this DitheringSelector
m_extChanges =
extensions.DitheringMatricesChange.connect(
base::Bind<void>(&DitheringSelector::regenerate, this));
setUseCustomWidget(true);
regenerate();
}
void DitheringSelector::regenerate()
{
removeAllItems();
Extensions& extensions = App::instance()->extensions();
auto ditheringMatrices = extensions.ditheringMatrices();
switch (m_type) {
case SelectBoth:
addItem(new DitherItem(render::DitheringAlgorithm::None,
render::DitheringMatrix(), "No Dithering"));
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
render::BayerMatrix(8), "Ordered Dithering - Bayer Matrix 8x8"));
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
render::BayerMatrix(4), "Ordered Dithering - Bayer Matrix 4x4"));
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
render::BayerMatrix(2), "Ordered Dithering - Bayer Matrix 2x2"));
addItem(new DitherItem(render::DitheringAlgorithm::Old,
render::BayerMatrix(8), "Old Dithering - Bayer Matrix 8x8"));
addItem(new DitherItem(render::DitheringAlgorithm::Old,
render::BayerMatrix(4), "Old Dithering - Bayer Matrix 4x4"));
addItem(new DitherItem(render::DitheringAlgorithm::Old,
render::BayerMatrix(2), "Old Dithering - Bayer Matrix 2x2"));
for (const auto& it : ditheringMatrices) {
addItem(
new DitherItem(
render::DitheringAlgorithm::Ordered,
it.matrix(),
"Ordered Dithering+" + it.name()));
}
for (const auto& it : ditheringMatrices) {
addItem(
new DitherItem(
render::DitheringAlgorithm::Old,
it.matrix(),
"Old Dithering+" + it.name()));
}
break;
case SelectMatrix:
addItem(new DitherItem(render::DitheringMatrix(), "No Dithering"));
addItem(new DitherItem(render::BayerMatrix(8), "Bayer Matrix 8x8"));
addItem(new DitherItem(render::BayerMatrix(4), "Bayer Matrix 4x4"));
addItem(new DitherItem(render::BayerMatrix(2), "Bayer Matrix 2x2"));
for (auto& it : ditheringMatrices)
addItem(new DitherItem(it.matrix(), it.name()));
break;
}
setSelectedItemIndex(0);
setSizeHint(getItem(0)->sizeHint());
}

View File

@ -8,6 +8,7 @@
#define APP_UI_DITHERING_SELECTOR_H_INCLUDED
#pragma once
#include "obs/connection.h"
#include "render/dithering_algorithm.h"
#include "render/ordered_dither.h"
#include "ui/box.h"
@ -26,6 +27,12 @@ namespace app {
render::DitheringAlgorithm ditheringAlgorithm();
render::DitheringMatrix ditheringMatrix();
private:
void regenerate();
Type m_type;
obs::scoped_connection m_extChanges;
};
} // namespace app

View File

@ -15,6 +15,7 @@
#include "app/launcher.h"
#include "app/match_words.h"
#include "app/res/palettes_loader_delegate.h"
#include "app/res/resource.h"
#include "app/ui/palettes_listbox.h"
#include "app/ui/search_entry.h"
#include "app/ui_context.h"
@ -53,6 +54,7 @@ PalettePopup::PalettePopup()
void PalettePopup::showPopup(const gfx::Rect& bounds)
{
m_popup->loadPal()->setEnabled(false);
m_popup->openFolder()->setEnabled(false);
m_paletteListBox.selectChild(NULL);
moveWindow(bounds);
@ -62,9 +64,12 @@ void PalettePopup::showPopup(const gfx::Rect& bounds)
void PalettePopup::onPalChange(doc::Palette* palette)
{
m_popup->loadPal()->setEnabled(
UIContext::instance()->activeDocument() &&
palette != NULL);
const bool state =
(UIContext::instance()->activeDocument() &&
palette != nullptr);
m_popup->loadPal()->setEnabled(state);
m_popup->openFolder()->setEnabled(state);
}
void PalettePopup::onSearchChange()
@ -107,7 +112,11 @@ void PalettePopup::onLoadPal()
void PalettePopup::onOpenFolder()
{
launcher::open_folder(PalettesLoaderDelegate().resourcesLocation());
Resource* res = m_paletteListBox.selectedResource();
if (!res)
return;
launcher::open_folder(res->path());
}
} // namespace app

View File

@ -10,7 +10,9 @@
#include "app/ui/palettes_listbox.h"
#include "app/app.h"
#include "app/document.h"
#include "app/extensions.h"
#include "app/modules/palettes.h"
#include "app/res/palette_resource.h"
#include "app/res/palettes_loader_delegate.h"
@ -112,6 +114,10 @@ PalettesListBox::PalettesListBox()
: ResourcesListBox(new ResourcesLoader(new PalettesLoaderDelegate))
{
addChild(&m_tooltips);
m_extPaletteChanges =
App::instance()->extensions().PalettesChange.connect(
base::Bind<void>(&PalettesListBox::reload, this));
}
doc::Palette* PalettesListBox::selectedPalette()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -9,6 +9,7 @@
#pragma once
#include "app/ui/resources_listbox.h"
#include "obs/connection.h"
#include "ui/tooltips.h"
namespace doc {
@ -32,6 +33,7 @@ namespace app {
virtual void onResourceSizeHint(Resource* resource, gfx::Size& size) override;
ui::TooltipManager m_tooltips;
obs::scoped_connection m_extPaletteChanges;
};
} // namespace app

View File

@ -29,7 +29,7 @@ using namespace skin;
// ResourceListItem
ResourceListItem::ResourceListItem(Resource* resource)
: ListItem(resource->name()), m_resource(resource)
: ListItem(resource->id()), m_resource(resource)
{
}
@ -111,7 +111,8 @@ private:
ResourcesListBox::ResourcesListBox(ResourcesLoader* resourcesLoader)
: m_resourcesLoader(resourcesLoader)
, m_resourcesTimer(100)
, m_loadingItem(NULL)
, m_reload(false)
, m_loadingItem(nullptr)
{
m_resourcesTimer.Tick.connect(base::Bind<void>(&ResourcesListBox::onTick, this));
}
@ -121,7 +122,22 @@ Resource* ResourcesListBox::selectedResource()
if (ResourceListItem* listItem = dynamic_cast<ResourceListItem*>(getSelectedChild()))
return listItem->resource();
else
return NULL;
return nullptr;
}
void ResourcesListBox::reload()
{
auto children = this->children(); // Create a copy because we'll
// modify the list in the for()
// Delete all ResourcesListItem. (PalettesListBox contains a tooltip
// manager too, so we cannot remove just all children.)
for (auto child : children) {
if (dynamic_cast<ResourceListItem*>(child))
delete child;
}
m_reload = true;
}
void ResourcesListBox::paintResource(Graphics* g, gfx::Rect& bounds, Resource* resource)
@ -141,6 +157,11 @@ bool ResourcesListBox::onProcessMessage(ui::Message* msg)
switch (msg->type()) {
case kOpenMessage: {
if (m_reload) {
m_reload = false;
m_resourcesLoader->reload();
}
m_resourcesTimer.start();
break;
}
@ -163,7 +184,7 @@ ResourceListItem* ResourcesListBox::onCreateResourceItem(Resource* resource)
void ResourcesListBox::onTick()
{
if (m_resourcesLoader == NULL) {
if (m_resourcesLoader == nullptr) {
stop();
return;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -40,6 +40,8 @@ class ResourceListItem : public ui::ListItem {
Resource* selectedResource();
void reload();
protected:
virtual bool onProcessMessage(ui::Message* msg) override;
virtual void onChange() override;
@ -57,8 +59,9 @@ class ResourceListItem : public ui::ListItem {
void onTick();
void stop();
ResourcesLoader* m_resourcesLoader;
base::UniquePtr<ResourcesLoader> m_resourcesLoader;
ui::Timer m_resourcesTimer;
bool m_reload;
class LoadingItem;
LoadingItem* m_loadingItem;

View File

@ -8,7 +8,11 @@
#include "config.h"
#endif
#include "app/ui/skin/skin_theme.h"
#include "app/app.h"
#include "app/console.h"
#include "app/extensions.h"
#include "app/font_path.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
@ -18,7 +22,6 @@
#include "app/ui/skin/font_data.h"
#include "app/ui/skin/skin_property.h"
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/skin/skin_theme.h"
#include "app/xml_document.h"
#include "app/xml_exception.h"
#include "base/bind.h"
@ -48,6 +51,7 @@ namespace skin {
using namespace gfx;
using namespace ui;
// TODO For backward compatibility, in future versions we should remove this (extensions are preferred)
const char* SkinTheme::kThemesFolderName = "themes";
static const char* g_cursor_names[kCursorTypes] = {
@ -248,32 +252,31 @@ void SkinTheme::loadFontData()
}
}
void SkinTheme::loadAll(const std::string& skinId)
void SkinTheme::loadAll(const std::string& themeId)
{
LOG("THEME: Loading theme %s\n", skinId.c_str());
LOG("THEME: Loading theme %s\n", themeId.c_str());
if (m_fonts.empty())
loadFontData();
loadSheet(skinId);
loadXml(skinId);
m_path = findThemePath(themeId);
if (m_path.empty())
throw base::Exception("Theme %s not found", themeId.c_str());
loadSheet();
loadXml();
}
void SkinTheme::loadSheet(const std::string& skinId)
void SkinTheme::loadSheet()
{
// Load the skin sheet
std::string sheet_filename(themeFileName(skinId, "sheet.png"));
ResourceFinder rf;
rf.includeDataDir(sheet_filename.c_str());
if (!rf.findFirst())
throw base::Exception("File %s not found", sheet_filename.c_str());
std::string sheet_filename(base::join_path(m_path, "sheet.png"));
try {
if (m_sheet) {
m_sheet->dispose();
m_sheet = nullptr;
}
m_sheet = she::instance()->loadRgbaSurface(rf.filename().c_str());
m_sheet = she::instance()->loadRgbaSurface(sheet_filename.c_str());
if (m_sheet)
m_sheet->applyScale(guiscale());
}
@ -282,18 +285,14 @@ void SkinTheme::loadSheet(const std::string& skinId)
}
}
void SkinTheme::loadXml(const std::string& skinId)
void SkinTheme::loadXml()
{
const int scale = guiscale();
// Load the skin XML
std::string xml_filename(themeFileName(skinId, "theme.xml"));
ResourceFinder rf;
rf.includeDataDir(xml_filename.c_str());
if (!rf.findFirst())
return;
std::string xml_filename(base::join_path(m_path, "theme.xml"));
XmlDocumentRef doc = open_xml(rf.filename());
XmlDocumentRef doc = open_xml(xml_filename);
TiXmlHandle handle(doc.get());
// Load fonts
@ -304,7 +303,7 @@ void SkinTheme::loadXml(const std::string& skinId)
.FirstChild("font").ToElement();
while (xmlFont) {
const char* idStr = xmlFont->Attribute("id");
FontData* fontData = load_font(m_fonts, xmlFont, rf.filename());
FontData* fontData = load_font(m_fonts, xmlFont, xml_filename);
if (idStr && fontData) {
std::string id(idStr);
LOG(VERBOSE) << "THEME: Loading theme font '" << id << "\n";
@ -749,7 +748,7 @@ void SkinTheme::initWidget(Widget* widget)
break;
case kListItemWidget:
BORDER(1 * scale);
widget->setStyle(styles.listItem());
break;
case kComboBoxWidget: {
@ -1064,34 +1063,6 @@ void SkinTheme::paintListBox(PaintEvent& ev)
g->fillRect(colors.background(), g->getClipBounds());
}
void SkinTheme::paintListItem(ui::PaintEvent& ev)
{
Widget* widget = static_cast<Widget*>(ev.getSource());
gfx::Rect bounds = widget->clientBounds();
Graphics* g = ev.graphics();
gfx::Color fg, bg;
if (!widget->isEnabled()) {
bg = colors.face();
fg = colors.disabled();
}
else if (widget->isSelected()) {
fg = colors.listitemSelectedText();
bg = colors.listitemSelectedFace();
}
else {
fg = colors.listitemNormalText();
bg = colors.listitemNormalFace();
}
g->fillRect(bg, bounds);
if (widget->hasText()) {
bounds.shrink(widget->border());
drawText(g, nullptr, fg, bg, widget, bounds, 0, 0);
}
}
void SkinTheme::paintMenu(PaintEvent& ev)
{
Widget* widget = static_cast<Widget*>(ev.getSource());
@ -1607,12 +1578,23 @@ void SkinTheme::paintProgressBar(ui::Graphics* g, const gfx::Rect& rc0, double p
g->fillRect(colors.background(), gfx::Rect(rc.x+u, rc.y, rc.w-u, rc.h));
}
std::string SkinTheme::themeFileName(const std::string& skinId,
const std::string& fileName) const
std::string SkinTheme::findThemePath(const std::string& themeId) const
{
std::string path = base::join_path(SkinTheme::kThemesFolderName, skinId);
path = base::join_path(path, fileName);
return path;
// First we try to find the theme on an extensions
std::string path = App::instance()->extensions().themePath(themeId);
if (path.empty()) {
// Then we try a theme in the old themes/ folder
path = base::join_path(SkinTheme::kThemesFolderName, themeId);
path = base::join_path(path, "theme.xml");
ResourceFinder rf;
rf.includeDataDir(path.c_str());
if (!rf.findFirst())
return std::string();
path = base::get_file_path(rf.filename());
}
return base::normalize_path(path);
}
} // namespace skin

View File

@ -47,6 +47,8 @@ namespace app {
SkinTheme();
~SkinTheme();
const std::string& path() { return m_path; }
she::Font* getDefaultFont() const override { return m_defaultFont; }
she::Font* getWidgetFont(const ui::Widget* widget) const override;
she::Font* getMiniFont() const { return m_miniFont; }
@ -59,7 +61,6 @@ namespace app {
void paintEntry(ui::PaintEvent& ev) override;
void paintListBox(ui::PaintEvent& ev) override;
void paintListItem(ui::PaintEvent& ev) override;
void paintMenu(ui::PaintEvent& ev) override;
void paintMenuItem(ui::PaintEvent& ev) override;
void paintSlider(ui::PaintEvent& ev) override;
@ -129,9 +130,9 @@ namespace app {
private:
void loadFontData();
void loadAll(const std::string& skinId);
void loadSheet(const std::string& skinId);
void loadXml(const std::string& skinId);
void loadAll(const std::string& themeId);
void loadSheet();
void loadXml();
she::Surface* sliceSheet(she::Surface* sur, const gfx::Rect& bounds);
gfx::Color getWidgetBgColor(ui::Widget* widget);
@ -140,9 +141,9 @@ namespace app {
int selected_offset, int mnemonic);
void drawEntryText(ui::Graphics* g, ui::Entry* widget);
std::string themeFileName(const std::string& skinId,
const std::string& fileName) const;
std::string findThemePath(const std::string& themeId) const;
std::string m_path;
she::Surface* m_sheet;
std::map<std::string, SkinPartPtr> m_parts_by_id;
std::map<std::string, gfx::Color> m_colors_by_id;

Some files were not shown because too many files have changed in this diff Show More