mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-03 21:46:20 +00:00
Merge branch 'extensions' (#1403)
This commit is contained in:
commit
dafaf8d59f
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -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
|
||||
|
24
data/extensions/arne-palettes/package.json
Normal file
24
data/extensions/arne-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
20
data/extensions/aseprite-theme/package.json
Normal file
20
data/extensions/aseprite-theme/package.json
Normal 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": "." }
|
||||
]
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -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>
|
BIN
data/extensions/bayer-matrices/bayer2x2.bmp
Normal file
BIN
data/extensions/bayer-matrices/bayer2x2.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
data/extensions/bayer-matrices/bayer4x4.bmp
Normal file
BIN
data/extensions/bayer-matrices/bayer4x4.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
data/extensions/bayer-matrices/bayer8x8.bmp
Normal file
BIN
data/extensions/bayer-matrices/bayer8x8.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
26
data/extensions/bayer-matrices/package.json
Normal file
26
data/extensions/bayer-matrices/package.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
16
data/extensions/davitmasia-palettes/package.json
Normal file
16
data/extensions/davitmasia-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
17
data/extensions/dawnbringer-palettes/package.json
Normal file
17
data/extensions/dawnbringer-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
21
data/extensions/endesga-palettes/package.json
Normal file
21
data/extensions/endesga-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
36
data/extensions/hardware-palettes/package.json
Normal file
36
data/extensions/hardware-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
16
data/extensions/hyohnoo-palettes/package.json
Normal file
16
data/extensions/hyohnoo-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
16
data/extensions/javierguerrero-palettes/package.json
Normal file
16
data/extensions/javierguerrero-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
16
data/extensions/pico8-palette/package.json
Normal file
16
data/extensions/pico8-palette/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
21
data/extensions/software-palettes/package.json
Normal file
21
data/extensions/software-palettes/package.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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/)
|
||||
|
||||
```
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
// Try to get the matrix from the extensions
|
||||
const render::DitheringMatrix* knownMatrix =
|
||||
App::instance()->extensions().ditheringMatrix(matrix);
|
||||
if (knownMatrix) {
|
||||
m_ditheringMatrix = *knownMatrix;
|
||||
}
|
||||
else {
|
||||
m_ditheringMatrix = render::DitheringMatrix();
|
||||
}
|
||||
}
|
||||
else
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -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
609
src/app/extensions.cpp
Normal 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
135
src/app/extensions.h
Normal 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
52
src/app/load_matrix.cpp
Normal 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
25
src/app/load_matrix.h
Normal 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
|
@ -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
|
||||
|
@ -17,7 +17,7 @@ void RenderTaskJob::onJob()
|
||||
try {
|
||||
m_func();
|
||||
}
|
||||
catch (std::exception& ex) {
|
||||
catch (const std::exception&) {
|
||||
// TODO show the exception
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user