mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 10:13:22 +00:00
Merge branch 'master' into tilemap-editor
This commit is contained in:
commit
a80af2b304
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -63,3 +63,6 @@
|
||||
[submodule "third_party/lua"]
|
||||
path = third_party/lua
|
||||
url = https://github.com/aseprite/lua
|
||||
[submodule "src/tga"]
|
||||
path = src/tga
|
||||
url = https://github.com/aseprite/tga.git
|
||||
|
10
.travis.yml
10
.travis.yml
@ -11,7 +11,7 @@ matrix:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
|
||||
env:
|
||||
- ENABLE_UI=OFF
|
||||
- XVFB=xvfb-run
|
||||
@ -19,7 +19,7 @@ matrix:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
|
||||
env:
|
||||
- ENABLE_SCRIPTING=OFF
|
||||
- XVFB=xvfb-run
|
||||
@ -27,7 +27,7 @@ matrix:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
|
||||
env:
|
||||
- ENABLE_SCRIPTING=OFF
|
||||
- ENABLE_UI=OFF
|
||||
@ -37,7 +37,7 @@ matrix:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
|
||||
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
|
||||
env:
|
||||
- ENABLE_UI=ON
|
||||
- XVFB=xvfb-run
|
||||
@ -47,7 +47,7 @@ matrix:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
|
||||
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
|
||||
env:
|
||||
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
|
||||
- ENABLE_UI=ON
|
||||
|
@ -219,7 +219,7 @@ add_definitions(-DPNG_NO_MMX_CODE) # Do not use MMX optimizations in PNG code
|
||||
|
||||
# libwebp
|
||||
if(WITH_WEBP_SUPPORT)
|
||||
set(WEBP_LIBRARIES webp webpdemux webpmux)
|
||||
set(WEBP_LIBRARIES webp)
|
||||
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
|
||||
include_directories(${WEBP_INCLUDE_DIR})
|
||||
endif()
|
||||
|
@ -72,11 +72,11 @@ versions might work).
|
||||
|
||||
You will need the following dependencies on Ubuntu/Debian:
|
||||
|
||||
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
|
||||
|
||||
On Fedora:
|
||||
|
||||
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel mesa-libGL-devel fontconfig-devel
|
||||
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel libXi-devel mesa-libGL-devel fontconfig-devel
|
||||
|
||||
# Compiling
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -418,6 +418,7 @@
|
||||
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
|
||||
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
|
||||
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
|
||||
<part id="dynamics" x="176" y="144" w="16" h="16" />
|
||||
<part id="tiles" x="144" y="208" w="6" h="6" />
|
||||
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
|
||||
<part id="tiles_auto" x="156" y="208" w="6" h="6" />
|
||||
@ -961,5 +962,16 @@
|
||||
<icon part="aseprite_face_mouse" state="mouse" />
|
||||
<icon part="aseprite_face_pushed" state="mouse selected" />
|
||||
</style>
|
||||
<style id="slider" border-top="4" border-bottom="5">
|
||||
<background part="slider_empty" />
|
||||
<background part="slider_empty_focused" state="focus" />
|
||||
<text color="slider_empty_text" align="center middle" />
|
||||
<text color="slider_empty_text" align="center middle" state="focus" y="1" />
|
||||
</style>
|
||||
<style id="mini_slider" extends="slider" font="mini" padding-top="1" padding-bottom="3">
|
||||
<background part="mini_slider_empty" />
|
||||
<text color="slider_empty_text" align="center middle" />
|
||||
<text color="slider_empty_text" align="center middle" state="focus" />
|
||||
</style>
|
||||
</styles>
|
||||
</theme>
|
||||
|
164
data/gui.xml
164
data/gui.xml
@ -629,56 +629,56 @@
|
||||
<!-- main bar menu -->
|
||||
<menu id="main_menu">
|
||||
<menu text="@.file">
|
||||
<item command="NewFile" text="@.file_new" />
|
||||
<item command="OpenFile" text="@.file_open" />
|
||||
<menu text="@.file_open_recent">
|
||||
<item command="ReopenClosedFile" text="@.file_reopen_closed" />
|
||||
<separator id="recent_files_placeholder" />
|
||||
<item command="NewFile" text="@.file_new" group="file_new" />
|
||||
<item command="OpenFile" text="@.file_open" group="file_open" />
|
||||
<menu text="@.file_open_recent" group="file_recent">
|
||||
<item command="ReopenClosedFile" text="@.file_reopen_closed" group="file_recent_reopen" />
|
||||
<separator id="recent_files_placeholder" group="file_recent_list" />
|
||||
<separator />
|
||||
<item command="ClearRecentFiles" text="@.file_clear_recent_files" />
|
||||
<item command="ClearRecentFiles" text="@.file_clear_recent_files" group="file_recent_clear" />
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="SaveFile" text="@.file_save" />
|
||||
<item command="SaveFileAs" text="@.file_save_as" />
|
||||
<item command="SaveFileCopyAs" text="@.file_export" />
|
||||
<item command="SaveFileCopyAs" text="@.file_export" group="file_save" />
|
||||
<item command="CloseFile" text="@.file_close" />
|
||||
<item command="CloseAllFiles" text="@.file_close_all" />
|
||||
<item command="CloseAllFiles" text="@.file_close_all" group="file_close" />
|
||||
<separator />
|
||||
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" />
|
||||
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" />
|
||||
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" group="file_import" />
|
||||
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" group="file_export" />
|
||||
<item command="RepeatLastExport" text="@.file_repeat_last_export" />
|
||||
<separator id="scripts_menu_separator" />
|
||||
<menu id="scripts_menu" text="@.file_scripts">
|
||||
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts">
|
||||
<item command="OpenScriptFolder" text="@.file_open_script_folder" />
|
||||
</menu>
|
||||
<separator />
|
||||
<separator group="file_app" />
|
||||
<item command="Exit" text="@.file_exit" />
|
||||
</menu>
|
||||
<menu text="@.edit">
|
||||
<item command="Undo" text="@.edit_undo" />
|
||||
<item command="Redo" text="@.edit_redo" />
|
||||
<item command="UndoHistory" text="@.edit_undo_history" />
|
||||
<separator />
|
||||
<item command="UndoHistory" text="@.edit_undo_history" group="edit_undo" />
|
||||
<separator group="edit_clipboard" />
|
||||
<item command="Cut" text="@.edit_cut" />
|
||||
<item command="Copy" text="@.edit_copy" />
|
||||
<item command="CopyMerged" text="@.edit_copy_merged" />
|
||||
<item command="CopyMerged" text="@.edit_copy_merged" group="edit_copy" />
|
||||
<item command="Paste" text="@.edit_paste" />
|
||||
<menu text="@.edit_paste_special">
|
||||
<menu text="@.edit_paste_special" group="edit_paste">
|
||||
<item command="NewFile" text="@.edit_paste_special_new_sprite">
|
||||
<param name="fromClipboard" value="true" />
|
||||
</item>
|
||||
<item command="NewLayer" text="@.edit_paste_special_new_layer">
|
||||
<param name="fromClipboard" value="true" />
|
||||
</item>
|
||||
<item command="NewLayer" text="@.edit_paste_special_new_ref_layer">
|
||||
<item command="NewLayer" text="@.edit_paste_special_new_ref_layer" group="edit_paste_special_new">
|
||||
<param name="reference" value="true" />
|
||||
<param name="fromClipboard" value="true" />
|
||||
</item>
|
||||
</menu>
|
||||
<item command="Clear" text="@.edit_clear" />
|
||||
<item command="Clear" text="@.edit_clear" group="edit_clear" />
|
||||
<separator />
|
||||
<item command="Fill" text="@.edit_fill" />
|
||||
<item command="Stroke" text="@.edit_stroke" />
|
||||
<item command="Stroke" text="@.edit_stroke" group="edit_fill" />
|
||||
<separator />
|
||||
<menu text="@.edit_rotate">
|
||||
<item command="Rotate" text="@.edit_rotate_180">
|
||||
@ -703,7 +703,7 @@
|
||||
<param name="orientation" value="vertical" />
|
||||
</item>
|
||||
<item command="MaskContent" text="@.edit_transform" />
|
||||
<menu text="@.edit_shift">
|
||||
<menu text="@.edit_shift" group="edit_transform">
|
||||
<item command="MoveMask" text="@.edit_shift_left">
|
||||
<param name="target" value="content" />
|
||||
<param name="direction" value="left" />
|
||||
@ -735,7 +735,7 @@
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="NewBrush" text="@.edit_new_brush" />
|
||||
<item command="NewSpriteFromSelection" text="@.edit_new_sprite_from_selection" />
|
||||
<item command="NewSpriteFromSelection" text="@.edit_new_sprite_from_selection" group="edit_new" />
|
||||
<separator />
|
||||
<item command="ReplaceColor" text="@.edit_replace_color" />
|
||||
<item command="InvertColor" text="@.edit_invert_color" />
|
||||
@ -747,16 +747,16 @@
|
||||
<menu text="@.edit_fx" id="fx_popup_menu">
|
||||
<item command="Outline" text="@.edit_fx_outline" />
|
||||
<item command="ConvolutionMatrix" text="@.edit_fx_convolution_matrix" />
|
||||
<item command="Despeckle" text="@.edit_fx_despeckle" />
|
||||
<item command="Despeckle" text="@.edit_fx_despeckle" group="edit_fx" />
|
||||
</menu>
|
||||
<item command="PasteText" text="@.edit_insert_text" />
|
||||
<item command="PasteText" text="@.edit_insert_text" group="edit_insert" />
|
||||
<separator />
|
||||
<item command="KeyboardShortcuts" text="@.edit_keyboard_shortcuts" />
|
||||
<item command="Options" text="@.edit_preferences" />
|
||||
</menu>
|
||||
<menu text="@.sprite">
|
||||
<item command="SpriteProperties" text="@.sprite_properties" />
|
||||
<menu text="@.sprite_color_mode">
|
||||
<item command="SpriteProperties" text="@.sprite_properties" group="sprite_properties" />
|
||||
<menu text="@.sprite_color_mode" group="sprite_color">
|
||||
<item command="ChangePixelFormat" text="@.sprite_color_mode_rgb">
|
||||
<param name="format" value="rgb" />
|
||||
</item>
|
||||
@ -770,10 +770,10 @@
|
||||
<item command="ChangePixelFormat" text="@.sprite_color_mode_more_options" />
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="DuplicateSprite" text="@.sprite_duplicate" />
|
||||
<item command="DuplicateSprite" text="@.sprite_duplicate" group="sprite_duplicate" />
|
||||
<separator />
|
||||
<item command="SpriteSize" text="@.sprite_sprite_size" />
|
||||
<item command="CanvasSize" text="@.sprite_canvas_size" />
|
||||
<item command="CanvasSize" text="@.sprite_canvas_size" group="sprite_size" />
|
||||
<menu text="@.sprite_rotate_canvas">
|
||||
<item command="Rotate" text="@.sprite_rotate_180">
|
||||
<param name="target" value="canvas" />
|
||||
@ -792,20 +792,20 @@
|
||||
<param name="target" value="canvas" />
|
||||
<param name="orientation" value="horizontal" />
|
||||
</item>
|
||||
<item command="Flip" text="@.sprite_flip_canvas_vertical">
|
||||
<item command="Flip" text="@.sprite_flip_canvas_vertical" group="sprite_transformation">
|
||||
<param name="target" value="canvas" />
|
||||
<param name="orientation" value="vertical" />
|
||||
</item>
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="CropSprite" text="@.sprite_crop" />
|
||||
<item command="AutocropSprite" text="@.sprite_trim" />
|
||||
<item command="AutocropSprite" text="@.sprite_trim" group="sprite_crop" />
|
||||
</menu>
|
||||
<menu text="@.layer">
|
||||
<item command="LayerProperties" text="@.layer_properties" />
|
||||
<item command="LayerVisibility" text="@.layer_visible" />
|
||||
<item command="LayerLock" text="@.layer_lock_layers" />
|
||||
<item command="OpenGroup" text="@.layer_open_group" />
|
||||
<item command="OpenGroup" text="@.layer_open_group" group="layer_properties" />
|
||||
<separator />
|
||||
<menu text="@.layer_new">
|
||||
<item command="NewLayer" text="@.layer_new_layer" />
|
||||
@ -820,7 +820,7 @@
|
||||
<param name="viaCut" value="true" />
|
||||
</item>
|
||||
<separator />
|
||||
<item command="NewLayer" text="@.layer_new_reference_layer_from_file">
|
||||
<item command="NewLayer" text="@.layer_new_reference_layer_from_file" group="layer_new">
|
||||
<param name="reference" value="true" />
|
||||
<param name="fromFile" value="true" />
|
||||
</item>
|
||||
@ -829,14 +829,14 @@
|
||||
<param name="ask" value="true" />
|
||||
</item>
|
||||
</menu>
|
||||
<item command="RemoveLayer" text="@.layer_delete_layer" />
|
||||
<item command="RemoveLayer" text="@.layer_delete_layer" group="layer_remove" />
|
||||
<item command="BackgroundFromLayer" text="@.layer_background_from_layer" />
|
||||
<item command="LayerFromBackground" text="@.layer_layer_from_background" />
|
||||
<item command="LayerFromBackground" text="@.layer_layer_from_background" group="layer_background" />
|
||||
<separator />
|
||||
<item command="DuplicateLayer" text="@.layer_duplicate" />
|
||||
<item command="DuplicateLayer" text="@.layer_duplicate" group="layer_duplicate" />
|
||||
<item command="MergeDownLayer" text="@.layer_merge_down" />
|
||||
<item command="FlattenLayers" text="@.layer_flatten" />
|
||||
<item command="FlattenLayers" text="@.layer_flatten_visible">
|
||||
<item command="FlattenLayers" text="@.layer_flatten_visible" group="layer_merge">
|
||||
<param name="visibleOnly" value="true" />
|
||||
</item>
|
||||
</menu>
|
||||
@ -844,7 +844,7 @@
|
||||
<item command="FrameProperties" text="@.frame_properties">
|
||||
<param name="frame" value="current" />
|
||||
</item>
|
||||
<item command="CelProperties" text="@.frame_cel_properties" />
|
||||
<item command="CelProperties" text="@.frame_cel_properties" group="cel_properties" />
|
||||
<separator />
|
||||
<item command="NewFrame" text="@.frame_new_frame" />
|
||||
<item command="NewFrame" text="@.frame_new_empty_frame">
|
||||
@ -853,10 +853,10 @@
|
||||
<item command="NewFrame" text="@.frame_duplicate_cels">
|
||||
<param name="content" value="celcopies" />
|
||||
</item>
|
||||
<item command="NewFrame" text="@.frame_duplicate_linked_cels">
|
||||
<item command="NewFrame" text="@.frame_duplicate_linked_cels" group="cel_new">
|
||||
<param name="content" value="cellinked" />
|
||||
</item>
|
||||
<item command="RemoveFrame" text="@.frame_delete_frame" />
|
||||
<item command="RemoveFrame" text="@.frame_delete_frame" group="cel_delete" />
|
||||
<separator />
|
||||
<menu text="@.frame_tags">
|
||||
<item command="FrameTagProperties" text="@.frame_tags_tag_properties" />
|
||||
@ -875,21 +875,21 @@
|
||||
<separator />
|
||||
<item command="GotoFrame" text="@.frame_go_to_frame" />
|
||||
</menu>
|
||||
<item command="PlayAnimation" text="@.frame_play_animation" />
|
||||
<item command="PlayAnimation" text="@.frame_play_animation" group="cel_animation" />
|
||||
<separator />
|
||||
<item command="FrameProperties" text="@.frame_constant_frame_rate">
|
||||
<param name="frame" value="all" />
|
||||
</item>
|
||||
<item command="ReverseFrames" text="@.frame_reverse_frames" />
|
||||
<item command="ReverseFrames" text="@.frame_reverse_frames" group="cel_frames" />
|
||||
</menu>
|
||||
<menu text="@.select">
|
||||
<item command="MaskAll" text="@.select_all" />
|
||||
<item command="DeselectMask" text="@.select_deselect" />
|
||||
<item command="ReselectMask" text="@.select_reselect" />
|
||||
<item command="InvertMask" text="@.select_inverse" />
|
||||
<item command="InvertMask" text="@.select_inverse" group="select_simple" />
|
||||
<separator />
|
||||
<item command="MaskByColor" text="@.select_color_range" />
|
||||
<menu text="@.select_modify">
|
||||
<menu text="@.select_modify" group="select_complex">
|
||||
<item command="ModifySelection" text="@.select_modify_border">
|
||||
<param name="modifier" value="border" />
|
||||
</item>
|
||||
@ -902,13 +902,13 @@
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="LoadMask" text="@.select_load_from_file" />
|
||||
<item command="SaveMask" text="@.select_save_to_file" />
|
||||
<item command="SaveMask" text="@.select_save_to_file" group="select_files" />
|
||||
</menu>
|
||||
<menu text="@.view">
|
||||
<item command="DuplicateView" text="@.view_duplicate_view" />
|
||||
<item command="DuplicateView" text="@.view_duplicate_view" group="view_new" />
|
||||
<separator />
|
||||
<item command="ShowExtras" text="@.view_show_extras" />
|
||||
<menu text="@.view_show">
|
||||
<menu text="@.view_show" group="view_extras">
|
||||
<item command="ShowLayerEdges" text="@.view_show_layer_edges" />
|
||||
<item command="ShowSelectionEdges" text="@.view_show_selection_edges" />
|
||||
<item command="ShowGrid" text="@.view_show_grid" />
|
||||
@ -938,10 +938,10 @@
|
||||
<param name="axis" value="y" />
|
||||
</item>
|
||||
</menu>
|
||||
<item command="SymmetryMode" text="@.view_symmetry_options" />
|
||||
<item command="SymmetryMode" text="@.view_symmetry_options" group="view_canvas_helpers" />
|
||||
<separator />
|
||||
<item command="SetLoopSection" text="@.view_set_loop_section" />
|
||||
<item command="ShowOnionSkin" text="@.view_show_onion_skin" />
|
||||
<item command="ShowOnionSkin" text="@.view_show_onion_skin" group="view_animation_helpers" />
|
||||
<separator />
|
||||
<item command="Timeline" text="@.view_timeline">
|
||||
<param name="switch" value="true" />
|
||||
@ -949,12 +949,12 @@
|
||||
<item command="TogglePreview" text="@.view_preview" />
|
||||
<item command="AdvancedMode" text="@.view_full_screen_mode" />
|
||||
<item command="FullscreenPreview" text="@.view_full_screen_preview" />
|
||||
<item command="Home" text="@.view_home" />
|
||||
<item command="Home" text="@.view_home" group="view_controls" />
|
||||
<separator />
|
||||
<item command="Refresh" text="@.view_refresh" />
|
||||
<item command="Refresh" text="@.view_refresh" group="view_screen" />
|
||||
</menu>
|
||||
<menu text="@.help" id="help_menu">
|
||||
<item command="OpenBrowser" text="@.help_readme">
|
||||
<item command="OpenBrowser" text="@.help_readme" group="help_readme">
|
||||
<param name="filename" value="README.md" />
|
||||
</item>
|
||||
<separator />
|
||||
@ -966,7 +966,7 @@
|
||||
<param name="type" value="url" />
|
||||
<param name="path" value="/docs/" />
|
||||
</item>
|
||||
<item command="Launch" text="@.help_tutorial">
|
||||
<item command="Launch" text="@.help_tutorial" group="help_docs">
|
||||
<param name="type" value="url" />
|
||||
<param name="path" value="/tutorial/" />
|
||||
</item>
|
||||
@ -975,48 +975,48 @@
|
||||
<param name="type" value="url" />
|
||||
<param name="path" value="/release-notes/" />
|
||||
</item>
|
||||
<item command="Launch" text="@.help_twitter">
|
||||
<item command="Launch" text="@.help_twitter" group="help_news">
|
||||
<param name="type" value="url" />
|
||||
<param name="path" value="http://twitter.com/aseprite" />
|
||||
</item>
|
||||
<separator />
|
||||
<item command="About" text="@.help_about" />
|
||||
<item command="About" text="@.help_about" group="help_about" />
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
<menu id="tab_popup_menu">
|
||||
<item command="CloseFile" text="@.close" />
|
||||
<item command="CloseFile" text="@.close" group="tab_close" />
|
||||
</menu>
|
||||
|
||||
<menu id="document_tab_popup_menu">
|
||||
<item command="CloseFile" text="@tab_popup_menu.close" />
|
||||
<item command="DuplicateView" text="@.duplicate_view" />
|
||||
<item command="CloseFile" text="@tab_popup_menu.close" group="document_tab_close" />
|
||||
<item command="DuplicateView" text="@.duplicate_view" group="document_tab_view" />
|
||||
<separator />
|
||||
<item command="OpenWithApp" text="@.open_with_os" />
|
||||
<item command="OpenInFolder" text="@.open_in_folder" />
|
||||
<item command="OpenInFolder" text="@.open_in_folder" group="document_tab_open" />
|
||||
</menu>
|
||||
|
||||
<menu id="layer_popup_menu">
|
||||
<item command="LayerProperties" text="@main_menu.layer_properties" />
|
||||
<item command="LayerProperties" text="@main_menu.layer_properties" group="layer_popup_properties" />
|
||||
<separator />
|
||||
<item command="NewLayer" text="@main_menu.layer_new_layer" />
|
||||
<item command="NewLayer" text="@main_menu.layer_new_group">
|
||||
<item command="NewLayer" text="@main_menu.layer_new_group" group="layer_popup_new">
|
||||
<param name="group" value="true" />
|
||||
</item>
|
||||
<item command="RemoveLayer" text="@main_menu.layer_delete_layer" />
|
||||
<item command="BackgroundFromLayer" text="@main_menu.layer_background_from_layer" />
|
||||
<item command="LayerFromBackground" text="@main_menu.layer_layer_from_background" />
|
||||
<item command="LayerFromBackground" text="@main_menu.layer_layer_from_background" group="layer_popup_background" />
|
||||
<separator />
|
||||
<item command="DuplicateLayer" text="@main_menu.layer_duplicate" />
|
||||
<item command="MergeDownLayer" text="@main_menu.layer_merge_down" />
|
||||
<item command="FlattenLayers" text="@main_menu.layer_flatten" />
|
||||
<item command="FlattenLayers" text="@main_menu.layer_flatten_visible">
|
||||
<item command="FlattenLayers" text="@main_menu.layer_flatten_visible" group="layer_popup_merge">
|
||||
<param name="visibleOnly" value="true" />
|
||||
</item>
|
||||
</menu>
|
||||
|
||||
<menu id="frame_popup_menu">
|
||||
<item command="FrameProperties" text="@main_menu.frame_properties">
|
||||
<item command="FrameProperties" text="@main_menu.frame_properties" group="frame_popup_properties">
|
||||
<param name="frame" value="current" />
|
||||
</item>
|
||||
<separator />
|
||||
@ -1024,25 +1024,25 @@
|
||||
<item command="NewFrame" text="@main_menu.frame_new_empty_frame">
|
||||
<param name="content" value="empty" />
|
||||
</item>
|
||||
<item command="NewFrameTag" text="@main_menu.frame_tags_new_tag" />
|
||||
<item command="RemoveFrame" text="@main_menu.frame_delete_frame" />
|
||||
<item command="NewFrameTag" text="@main_menu.frame_tags_new_tag" group="frame_popup_new" />
|
||||
<item command="RemoveFrame" text="@main_menu.frame_delete_frame" group="frame_popup_delete" />
|
||||
<separator />
|
||||
<item command="SetLoopSection" text="@main_menu.view_set_loop_section" />
|
||||
<item command="SetLoopSection" text="@main_menu.view_set_loop_section" group="frame_popup_loop" />
|
||||
<separator />
|
||||
<item command="ReverseFrames" text="@main_menu.frame_reverse_frames" />
|
||||
<item command="ReverseFrames" text="@main_menu.frame_reverse_frames" group="frame_popup_reverse" />
|
||||
</menu>
|
||||
|
||||
<menu id="cel_popup_menu">
|
||||
<item command="CelProperties" text="@main_menu.frame_cel_properties" />
|
||||
<separator />
|
||||
<item command="ClearCel" text="@.clear" />
|
||||
<item command="CelProperties" text="@main_menu.frame_cel_properties" group="cel_popup_properties" />
|
||||
<separator group="cel_popup_edit" />
|
||||
<item command="ClearCel" text="@.clear" group="cel_popup_clear" />
|
||||
<item command="UnlinkCel" text="@.unlink" />
|
||||
<item command="LinkCels" text="@.link_cels" />
|
||||
<item command="LinkCels" text="@.link_cels" group="cel_popup_links" />
|
||||
<separator />
|
||||
<item command="NewFrame" text="@main_menu.frame_duplicate_cels">
|
||||
<param name="content" value="celcopies" />
|
||||
</item>
|
||||
<item command="NewFrame" text="@main_menu.frame_duplicate_linked_cels">
|
||||
<item command="NewFrame" text="@main_menu.frame_duplicate_linked_cels" group="cel_popup_new">
|
||||
<param name="content" value="cellinked" />
|
||||
</item>
|
||||
</menu>
|
||||
@ -1057,18 +1057,18 @@
|
||||
</menu>
|
||||
|
||||
<menu id="tag_popup_menu">
|
||||
<item command="FrameTagProperties" text="@main_menu.frame_tags_tag_properties" />
|
||||
<item command="RemoveFrameTag" text="@main_menu.frame_tags_delete_tag" />
|
||||
<item command="FrameTagProperties" text="@main_menu.frame_tags_tag_properties" group="tag_popup_properties" />
|
||||
<item command="RemoveFrameTag" text="@main_menu.frame_tags_delete_tag" group="tag_popup_delete" />
|
||||
</menu>
|
||||
|
||||
<menu id="slice_popup_menu">
|
||||
<item command="SliceProperties" text="@.properties" />
|
||||
<item command="RemoveSlice" text="@.delete" />
|
||||
<item command="SliceProperties" text="@.properties" grou="slice_popup_properties" />
|
||||
<item command="RemoveSlice" text="@.delete" group="slice_popup_delete" />
|
||||
</menu>
|
||||
|
||||
<menu id="palette_popup_menu">
|
||||
<item command="PaletteEditor" text="@.edit_palette" />
|
||||
<item command="PaletteSize" text="@.palette_size" />
|
||||
<item command="PaletteSize" text="@.palette_size" group="palette_main" />
|
||||
<separator />
|
||||
<item command="SetPaletteEntrySize" text="@.small_size">
|
||||
<param name="size" value="7" />
|
||||
@ -1076,7 +1076,7 @@
|
||||
<item command="SetPaletteEntrySize" text="@.medium_size">
|
||||
<param name="size" value="11" />
|
||||
</item>
|
||||
<item command="SetPaletteEntrySize" text="@.large_size">
|
||||
<item command="SetPaletteEntrySize" text="@.large_size" group="palette_view">
|
||||
<param name="size" value="17" />
|
||||
</item>
|
||||
<separator />
|
||||
@ -1092,7 +1092,7 @@
|
||||
<item command="SetColorSelector" text="@.ryb_color_wheel">
|
||||
<param name="type" value="ryb-wheel" />
|
||||
</item>
|
||||
<item command="SetColorSelector" text="@.normal_map_color_wheel">
|
||||
<item command="SetColorSelector" text="@.normal_map_color_wheel" group="palette_selector">
|
||||
<param name="type" value="normal-map-wheel" />
|
||||
</item>
|
||||
<separator />
|
||||
@ -1101,11 +1101,11 @@
|
||||
<item command="LoadPalette" text="@.load_default_palette">
|
||||
<param name="preset" value="default" />
|
||||
</item>
|
||||
<item command="SavePalette" text="@.save_as_default_palette">
|
||||
<item command="SavePalette" text="@.save_as_default_palette" group="palette_files">
|
||||
<param name="preset" value="default" />
|
||||
</item>
|
||||
<separator />
|
||||
<item command="ColorQuantization" text="@.create_palette_from_current_sprite" />
|
||||
<item command="ColorQuantization" text="@.create_palette_from_current_sprite" group="palette_generation" />
|
||||
</menu>
|
||||
|
||||
<menu id="ink_popup_menu">
|
||||
@ -1121,11 +1121,11 @@
|
||||
<item command="SetInkType" text="@inks.lock_alpha">
|
||||
<param name="type" value="lock-alpha" />
|
||||
</item>
|
||||
<item command="SetInkType" text="@inks.shading">
|
||||
<item command="SetInkType" text="@inks.shading" group="ink_types">
|
||||
<param name="type" value="shading" />
|
||||
</item>
|
||||
<separator />
|
||||
<item command="SetSameInk" text="@.same_in_all_tools" />
|
||||
<item command="SetSameInk" text="@.same_in_all_tools" group="ink_options" />
|
||||
</menu>
|
||||
|
||||
</menus>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2018-2019 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2014-2018 David Capello -->
|
||||
<preferences>
|
||||
|
||||
@ -101,10 +101,6 @@
|
||||
<value id="LEFT" value="1" />
|
||||
<value id="RIGHT" value="2" />
|
||||
</enum>
|
||||
<enum id="HueSaturationMode">
|
||||
<value id="HSV" value="0" />
|
||||
<value id="HSL" value="1" />
|
||||
</enum>
|
||||
<enum id="ColorProfileBehavior">
|
||||
<value id="DISABLE" value="0" />
|
||||
<value id="EMBEDDED" value="1" />
|
||||
@ -117,6 +113,12 @@
|
||||
<value id="SRGB" value="1" />
|
||||
<value id="SPECIFIC" value="2" />
|
||||
</enum>
|
||||
<enum id="ToGrayAlgorithm">
|
||||
<value id="DEFAULT" value="0" />
|
||||
<value id="LUMA" value="0" />
|
||||
<value id="HSV" value="1" />
|
||||
<value id="HSL" value="2" />
|
||||
</enum>
|
||||
</types>
|
||||
|
||||
<global>
|
||||
@ -157,9 +159,14 @@
|
||||
<option id="auto_scroll" type="bool" default="true" />
|
||||
<option id="right_click_mode" type="RightClickMode" default="RightClickMode::PAINT_BGCOLOR" />
|
||||
<option id="auto_select_layer" type="bool" default="false" />
|
||||
<option id="auto_select_layer_quick" type="bool" default="true" />
|
||||
<option id="straight_line_preview" type="bool" default="true" />
|
||||
<option id="play_once" type="bool" default="false" />
|
||||
<option id="play_all" type="bool" default="false" />
|
||||
<!-- TODO this would be nice to be "true" but we have to fix
|
||||
some performance issue rendering huge sprites with small
|
||||
zoom levels -->
|
||||
<option id="auto_fit" type="bool" default="false" />
|
||||
</section>
|
||||
<section id="cursor">
|
||||
<option id="use_native_cursor" type="bool" default="false" />
|
||||
@ -254,6 +261,7 @@
|
||||
<option id="with_alpha" type="bool" default="true" />
|
||||
<option id="dithering_algorithm" type="std::string" />
|
||||
<option id="dithering_factor" type="int" default="100" />
|
||||
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
|
||||
</section>
|
||||
<section id="eyedropper" text="Editor">
|
||||
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />
|
||||
@ -326,6 +334,11 @@
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
<option id="pixel_scale" type="int" default="1" />
|
||||
</section>
|
||||
<section id="tga">
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
<option id="bits_per_pixel" type="int" default="0" />
|
||||
<option id="compress" type="bool" default="true" />
|
||||
</section>
|
||||
<section id="webp">
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
<option id="loop" type="bool" default="true" />
|
||||
@ -336,7 +349,7 @@
|
||||
<option id="image_preset" type="int" default="0" />
|
||||
</section>
|
||||
<section id="hue_saturation">
|
||||
<option id="mode" type="HueSaturationMode" default="HueSaturationMode::HSL" />
|
||||
<option id="mode" type="filters::HueSaturationFilter::Mode" default="filters::HueSaturationFilter::Mode::HSL_MUL" />
|
||||
</section>
|
||||
<section id="filters">
|
||||
<option id="cels_target" type="CelsTarget" default="CelsTarget::Selected" />
|
||||
|
@ -456,6 +456,7 @@ SetSameInk = Same Ink in All Tools
|
||||
ShowAutoGuides = Show Auto Guides
|
||||
ShowBrushPreview = Show Brush Preview
|
||||
ShowBrushes = Show Brushes
|
||||
ShowDynamics = Show Dynamics
|
||||
ShowExtras = Show Extras
|
||||
ShowGrid = Show Grid
|
||||
ShowLayerEdges = Show Layer Edges
|
||||
@ -559,6 +560,33 @@ duplicate = Duplicate:
|
||||
as = As:
|
||||
merged_layers = Duplicate merged layers only
|
||||
|
||||
[dynamics]
|
||||
pressure = Pressure
|
||||
pressure_tooltip = Control parameters through the pen pressure sensor
|
||||
velocity = Velocity
|
||||
velocity_tooltip = Control parameters through the mouse velocity
|
||||
size = Size
|
||||
size_tooltip = <<<END
|
||||
Change the brush size
|
||||
depending on the sensor value
|
||||
END
|
||||
angle = Angle
|
||||
angle_tooltip = <<<END
|
||||
Change the brush angle
|
||||
depending on the sensor value
|
||||
END
|
||||
min_size_tooltip = Brush size when the sensor has its minimum value
|
||||
max_size_tooltip = Brush size when the sensor has its maximum value
|
||||
min_angle_tooltip = Brush angle when the sensor has its minimum value
|
||||
max_angle_tooltip = Brush angle when the sensor has its maximum value
|
||||
gradient = Gradient
|
||||
gradient_tooltip = <<<END
|
||||
Gradient between foreground
|
||||
and background colors
|
||||
END
|
||||
max_point_value = Min/Max Values
|
||||
sensors_tweaks = Sensor Threshold
|
||||
|
||||
[export_file]
|
||||
title = Export File
|
||||
output_file = Output File:
|
||||
@ -1114,6 +1142,7 @@ zoom_from_center_with_keys = Zoom from center with keys
|
||||
show_scrollbars = Show scroll-bars in sprite editor
|
||||
show_scrollbars_tooltip = Show scroll-bars in all sprite editors.
|
||||
auto_scroll = Auto-scroll on editor edges
|
||||
auto_fit = Auto-fit on screen when a sprite is opened
|
||||
straight_line_preview = Preview straight line immediately on Pencil tool
|
||||
straight_line_preview_tooltip = <<<END
|
||||
On Pencil tool you can draw straight lines
|
||||
@ -1232,9 +1261,7 @@ file_format_doesnt_support_alert = Show warning when saving a file with unsuppor
|
||||
export_animation_in_sequence_alert = Show warning when saving an animation as a sequence of static images
|
||||
overwrite_files_on_export_alert = Show warning when overwriting files on File > Export
|
||||
overwrite_files_on_export_sprite_sheet_alert = Show warning when overwriting files on Export Sprite Sheet
|
||||
gif_options_alert = Show GIF options when saving .gif files
|
||||
jpeg_options_alert = Show JPEG options when saving .jpeg files
|
||||
svg_options_alert = Show SVG options when saving .svg files
|
||||
image_format_alerts = Show options when saving files:
|
||||
advanced_mode_alert = Show alert when we enter to Advanced Mode
|
||||
invalid_fg_bg_color_alert = Show alert when drawing with index out of palette bounds
|
||||
run_script_alert = Show alert when we try to run a script
|
||||
@ -1256,6 +1283,12 @@ available_themes = Available Themes
|
||||
select_theme = &Select
|
||||
download_themes = Download Themes
|
||||
open_theme_folder = Open &Folder
|
||||
language_extensions = Languages
|
||||
theme_extensions = Themes
|
||||
script_extensions = Scripts
|
||||
palette_extensions = Palettes
|
||||
dithering_matrix_extensions = Dithering Matrices
|
||||
multiple_extensions = Multiple Categories
|
||||
add_extension = &Add Extension
|
||||
add_extension_title = Add Extension
|
||||
enable_extension = &Enable
|
||||
@ -1472,6 +1505,11 @@ to = To:
|
||||
color = Color:
|
||||
ani_dir = Animation Direction:
|
||||
|
||||
[tga_options]
|
||||
title = TGA Options
|
||||
bits_per_pixel = Bits Per Pixel
|
||||
compress = Compress
|
||||
|
||||
[timeline_conf]
|
||||
position = Position:
|
||||
left = &Left
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2019 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2019-2020 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2017 David Capello -->
|
||||
<gui>
|
||||
<window id="color_mode" text="@.title">
|
||||
@ -13,6 +13,11 @@
|
||||
<slider min="0" max="100" id="factor" minwidth="100" />
|
||||
<label text="%" />
|
||||
</hbox>
|
||||
<combobox id="to_gray_combobox">
|
||||
<listitem text="!Luminance" />
|
||||
<listitem text="!HSV" />
|
||||
<listitem text="!HSL" />
|
||||
</combobox>
|
||||
<check text="@.flatten" id="flatten" />
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
|
55
data/widgets/dynamics.xml
Normal file
55
data/widgets/dynamics.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2020 Igara Studio S.A. -->
|
||||
<gui>
|
||||
<vbox id="dynamics">
|
||||
<hbox>
|
||||
<buttonset id="values" columns="3">
|
||||
<item text="" />
|
||||
<item text="@.pressure" tooltip="@.pressure_tooltip" tooltip_dir="bottom" />
|
||||
<item text="@.velocity" tooltip="@.velocity_tooltip" tooltip_dir="bottom" />
|
||||
|
||||
<item text="@.size" tooltip="@.size_tooltip" tooltip_dir="right" />
|
||||
<item text="" maxheight="1" />
|
||||
<item text="" maxheight="1" />
|
||||
|
||||
<item text="@.angle" tooltip="@.angle_tooltip" tooltip_dir="right" />
|
||||
<item text="" maxheight="1" />
|
||||
<item text="" maxheight="1" />
|
||||
|
||||
<item text="@.gradient" tooltip="@.gradient_tooltip" tooltip_dir="right" />
|
||||
<item text="" maxheight="1" />
|
||||
<item text="" maxheight="1" />
|
||||
</buttonset>
|
||||
</hbox>
|
||||
|
||||
<grid id="options" columns="2" childspacing="0" expansive="true">
|
||||
<separator id="separator" text="@.max_point_value" horizontal="true" cell_hspan="2" />
|
||||
|
||||
<label id="size_label" text="@.size" style="mini_label" />
|
||||
<slider id="min_size" value="1" min="1" max="64" cell_align="horizontal" style="mini_slider"
|
||||
tooltip="@.min_size_tooltip" tooltip_dir="left" />
|
||||
<boxfiller />
|
||||
<slider id="max_size" value="64" min="1" max="64" cell_align="horizontal" style="mini_slider"
|
||||
tooltip="@.max_size_tooltip" tooltip_dir="left" />
|
||||
|
||||
<label id="angle_label" text="@.angle" style="mini_label" />
|
||||
<slider id="min_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
|
||||
tooltip="@.min_angle_tooltip" tooltip_dir="left" />
|
||||
<boxfiller />
|
||||
<slider id="max_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
|
||||
tooltip="@.max_angle_tooltip" tooltip_dir="left" />
|
||||
|
||||
<label id="gradient_label" text="@.gradient" style="mini_label" />
|
||||
<hbox id="gradient_placeholder" cell_vspan="2" />
|
||||
<link id="gradient_from_to" style="mini_label" />
|
||||
|
||||
<separator id="separator2" text="@.sensors_tweaks" horizontal="true" cell_hspan="2" />
|
||||
|
||||
<label id="pressure_label" text="@.pressure" style="mini_label" />
|
||||
<hbox id="pressure_placeholder" cell_align="horizontal" />
|
||||
<label id="velocity_label" text="@.velocity" style="mini_label" />
|
||||
<hbox id="velocity_placeholder" cell_align="horizontal" />
|
||||
</grid>
|
||||
|
||||
</vbox>
|
||||
</gui>
|
@ -1,5 +1,5 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2018-2019 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2001-2018 David Capello -->
|
||||
<gui>
|
||||
<window id="options" text="@.title">
|
||||
@ -203,6 +203,8 @@
|
||||
<check text="@.zoom_from_center_with_keys" id="zoom_from_center_with_keys" />
|
||||
<check text="@.show_scrollbars" id="show_scrollbars" tooltip="@.show_scrollbars_tooltip" />
|
||||
<check text="@.auto_scroll" id="auto_scroll" />
|
||||
<check text="@.auto_fit" id="auto_fit"
|
||||
pref="editor.auto_fit" />
|
||||
<check text="@.straight_line_preview" id="straight_line_preview" tooltip="@.straight_line_preview_tooltip" />
|
||||
<check text="@.discard_brush" id="discard_brush" />
|
||||
<hbox>
|
||||
@ -408,18 +410,19 @@
|
||||
pref="export_file.show_overwrite_files_alert" />
|
||||
<check id="overwrite_files_on_export_sprite_sheet_alert" text="@.overwrite_files_on_export_sprite_sheet_alert"
|
||||
pref="sprite_sheet.show_overwrite_files_alert" />
|
||||
<check id="gif_options_alert" text="@.gif_options_alert"
|
||||
pref="gif.show_alert" />
|
||||
<check id="jpeg_options_alert" text="@.jpeg_options_alert"
|
||||
pref="jpeg.show_alert" />
|
||||
<check id="svg_options_alert" text="@.svg_options_alert"
|
||||
pref="svg.show_alert" />
|
||||
<check id="advanced_mode_alert" text="@.advanced_mode_alert"
|
||||
pref="advanced_mode.show_alert" />
|
||||
<check id="invalid_fg_bg_color_alert" text="@.invalid_fg_bg_color_alert"
|
||||
pref="color_bar.show_invalid_fg_bg_color_alert" />
|
||||
<check id="run_script_alert" text="@.run_script_alert"
|
||||
pref="scripts.show_run_script_alert" />
|
||||
<hbox>
|
||||
<label text="@.image_format_alerts" />
|
||||
<check id="gif_options_alert" text="!gif" pref="gif.show_alert" />
|
||||
<check id="jpeg_options_alert" text="!jpeg" pref="jpeg.show_alert" />
|
||||
<check id="svg_options_alert" text="!svg" pref="svg.show_alert" />
|
||||
<check id="tga_options_alert" text="!tga" pref="tga.show_alert" />
|
||||
</hbox>
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
<hbox expansive="true" />
|
||||
|
23
data/widgets/tga_options.xml
Normal file
23
data/widgets/tga_options.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2020 Igara Studio S.A. -->
|
||||
<gui>
|
||||
<window id="tga_options" text="@.title">
|
||||
<grid columns="2">
|
||||
<label text="@.bits_per_pixel" id="bits_per_pixel_label" />
|
||||
<combobox id="bits_per_pixel" cell_align="horizontal" />
|
||||
|
||||
<check text="@.compress" id="compress" cell_hspan="2" />
|
||||
|
||||
<separator horizontal="true" cell_hspan="2" />
|
||||
|
||||
<hbox cell_hspan="2">
|
||||
<check text="@general.dont_show" id="dont_show" tooltip="@general.dont_show_tooltip" />
|
||||
<boxfiller />
|
||||
<hbox homogeneous="true">
|
||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||
<button text="@general.cancel" closewindow="true" />
|
||||
</hbox>
|
||||
</hbox>
|
||||
</grid>
|
||||
</window>
|
||||
</gui>
|
@ -85,9 +85,10 @@ We are using some C++11 features, mainly:
|
||||
* Use range-based for loops (`for (const auto& item : values) { ... }`)
|
||||
* Use template alias (`template<typename T> alias = orig<T>;`)
|
||||
* Use non-generic lambda functions
|
||||
* Use `std::shared_ptr` and `std::unique_ptr` (currently we're using
|
||||
`base::SharedPtr` and `base::UniquePtr` but you should use the STL
|
||||
ones now)
|
||||
* Use `std::shared_ptr` and `std::unique_ptr`
|
||||
* Use `base::clamp` (no `std::clamp` yet)
|
||||
* Use `static constexpr T v = ...;`
|
||||
* You can use `<atomic>`, `<thread>`, `<mutex>`, and `<condition_variable>`
|
||||
* We use GCC 4.8 on Linux, so check the features available since GCC 4.8 in
|
||||
* We can use `using T = ...;` instead of `typedef ... T`
|
||||
* We use gcc 9.2 or clang 9.0 on Linux, so check the features available in
|
||||
https://developer.mozilla.org/en-US/docs/Mozilla/Using_CXX_in_Mozilla_code
|
||||
|
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 98506341d68a318f4916a891ab12351a5db05f98
|
||||
Subproject commit 8032d186a751326d0fc6436d69570ad4a3c4aaf1
|
@ -19,6 +19,7 @@ if(MSVC)
|
||||
|
||||
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
|
||||
add_definitions(-wd4267) # disable warnings about signed/unsigned comparison
|
||||
add_definitions(-wd4244) # disable warnings about possible loss of data
|
||||
else()
|
||||
# disable warnings about signed/unsigned comparison
|
||||
add_definitions(-Wno-sign-compare)
|
||||
@ -96,6 +97,7 @@ add_subdirectory(doc)
|
||||
add_subdirectory(filters)
|
||||
add_subdirectory(fixmath)
|
||||
add_subdirectory(flic)
|
||||
add_subdirectory(tga)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(dio)
|
||||
add_subdirectory(ui)
|
||||
|
@ -171,6 +171,7 @@ if(ENABLE_SCRIPTING)
|
||||
script/palette_class.cpp
|
||||
script/palettes_class.cpp
|
||||
script/pixel_color_object.cpp
|
||||
script/plugin_class.cpp
|
||||
script/point_class.cpp
|
||||
script/preferences_object.cpp
|
||||
script/range_class.cpp
|
||||
@ -224,7 +225,6 @@ if(ENABLE_UI)
|
||||
commands/cmd_eyedropper.cpp
|
||||
commands/cmd_fill_and_stroke.cpp
|
||||
commands/cmd_fit_screen.cpp
|
||||
commands/cmd_flip.cpp
|
||||
commands/cmd_frame_properties.cpp
|
||||
commands/cmd_frame_tag_properties.cpp
|
||||
commands/cmd_fullscreen_preview.cpp
|
||||
@ -324,6 +324,7 @@ if(ENABLE_UI)
|
||||
ui/dithering_selector.cpp
|
||||
ui/doc_view.cpp
|
||||
ui/drop_down_button.cpp
|
||||
ui/dynamics_popup.cpp
|
||||
ui/editor/brush_preview.cpp
|
||||
ui/editor/drawing_state.cpp
|
||||
ui/editor/editor.cpp
|
||||
@ -512,6 +513,7 @@ add_library(app-lib
|
||||
commands/cmd_crop.cpp
|
||||
commands/cmd_export_sprite_sheet.cpp
|
||||
commands/cmd_flatten_layers.cpp
|
||||
commands/cmd_flip.cpp
|
||||
commands/cmd_layer_from_background.cpp
|
||||
commands/cmd_load_palette.cpp
|
||||
commands/cmd_merge_down_layer.cpp
|
||||
@ -601,6 +603,7 @@ add_library(app-lib
|
||||
tools/symmetries.cpp
|
||||
tools/tool_box.cpp
|
||||
tools/tool_loop_manager.cpp
|
||||
tools/velocity.cpp
|
||||
transaction.cpp
|
||||
transformation.cpp
|
||||
ui/editor/tool_loop_impl.cpp
|
||||
@ -612,6 +615,7 @@ add_library(app-lib
|
||||
util/filetoks.cpp
|
||||
util/freetype_utils.cpp
|
||||
util/layer_boundaries.cpp
|
||||
util/layer_utils.cpp
|
||||
util/msk_file.cpp
|
||||
util/new_image_from_mask.cpp
|
||||
util/pal_ops.cpp
|
||||
@ -642,6 +646,7 @@ target_link_libraries(app-lib
|
||||
filters-lib
|
||||
fixmath-lib
|
||||
flic-lib
|
||||
tga-lib
|
||||
laf-gfx
|
||||
render-lib
|
||||
laf-ft
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -12,6 +12,7 @@
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "app/site.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "doc/layer.h"
|
||||
|
||||
namespace app {
|
||||
@ -64,6 +65,7 @@ void ActiveSiteHandler::getActiveSiteForDoc(Doc* doc, Site* site)
|
||||
site->sprite(doc->sprite());
|
||||
site->layer(doc::get<doc::Layer>(data.layer));
|
||||
site->frame(data.frame);
|
||||
site->range(data.range);
|
||||
site->selectedColors(data.selectedColors);
|
||||
site->selectedTiles(data.selectedTiles);
|
||||
}
|
||||
@ -80,6 +82,28 @@ void ActiveSiteHandler::setActiveFrameInDoc(Doc* doc, doc::frame_t frame)
|
||||
data.frame = frame;
|
||||
}
|
||||
|
||||
void ActiveSiteHandler::setRangeInDoc(Doc* doc, const DocRange& range)
|
||||
{
|
||||
Data& data = getData(doc);
|
||||
data.range = range;
|
||||
|
||||
// Select at least the active layer
|
||||
if (data.range.selectedLayers().empty()) {
|
||||
if (auto layer = doc::get<Layer>(data.layer)) {
|
||||
data.range.selectLayer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
// Select at least the active frame
|
||||
if (data.range.selectedFrames().empty()) {
|
||||
SelectedFrames frames;
|
||||
frames.insert(data.frame);
|
||||
data.range.setSelectedFrames(frames);
|
||||
}
|
||||
|
||||
data.range.setType(range.type());
|
||||
}
|
||||
|
||||
void ActiveSiteHandler::setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks& picks)
|
||||
{
|
||||
Data& data = getData(doc);
|
||||
@ -104,30 +128,20 @@ void ActiveSiteHandler::onAddFrame(DocEvent& ev)
|
||||
data.frame = ev.frame();
|
||||
}
|
||||
|
||||
// TODO similar to Timeline::onBeforeRemoveLayer()
|
||||
// TODO similar to Timeline::onBeforeRemoveLayer() and Editor::onBeforeRemoveLayer()
|
||||
void ActiveSiteHandler::onBeforeRemoveLayer(DocEvent& ev)
|
||||
{
|
||||
Data& data = getData(ev.document());
|
||||
doc::Layer* selectedLayer = (data.layer != doc::NullId ?
|
||||
doc::get<doc::Layer>(data.layer):
|
||||
nullptr);
|
||||
if (!selectedLayer)
|
||||
return;
|
||||
|
||||
Layer* layer = ev.layer();
|
||||
|
||||
// If the layer that was removed is the selected one
|
||||
ASSERT(layer);
|
||||
if (layer && data.layer == layer->id()) {
|
||||
LayerGroup* parent = layer->parent();
|
||||
Layer* layer_select = nullptr;
|
||||
|
||||
// Select previous layer, or next layer, or the parent (if it is
|
||||
// not the main layer of sprite set).
|
||||
if (layer->getPrevious()) {
|
||||
layer_select = layer->getPrevious();
|
||||
}
|
||||
else if (layer->getNext())
|
||||
layer_select = layer->getNext();
|
||||
else if (parent != layer->sprite()->root())
|
||||
layer_select = parent;
|
||||
|
||||
data.layer = (layer_select ? layer_select->id(): 0);
|
||||
doc::Layer* layerToSelect = candidate_if_layer_is_deleted(selectedLayer, ev.layer());
|
||||
if (selectedLayer != layerToSelect) {
|
||||
data.layer = (layerToSelect ? layerToSelect->id():
|
||||
doc::NullId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/doc_observer.h"
|
||||
#include "app/doc_range.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/object_id.h"
|
||||
#include "doc/palette_picks.h"
|
||||
@ -38,6 +39,7 @@ namespace app {
|
||||
void getActiveSiteForDoc(Doc* doc, Site* site);
|
||||
void setActiveLayerInDoc(Doc* doc, doc::Layer* layer);
|
||||
void setActiveFrameInDoc(Doc* doc, doc::frame_t frame);
|
||||
void setRangeInDoc(Doc* doc, const DocRange& range);
|
||||
void setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks& picks);
|
||||
void setSelectedTilesInDoc(Doc* doc, const doc::PalettePicks& picks);
|
||||
|
||||
@ -52,6 +54,7 @@ namespace app {
|
||||
struct Data {
|
||||
doc::ObjectId layer;
|
||||
doc::frame_t frame;
|
||||
DocRange range;
|
||||
doc::PalettePicks selectedColors;
|
||||
doc::PalettePicks selectedTiles;
|
||||
};
|
||||
|
@ -217,6 +217,8 @@ App::App(AppMod* mod)
|
||||
|
||||
int App::initialize(const AppOptions& options)
|
||||
{
|
||||
os::System* system = os::instance();
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
m_isGui = options.startUI() && !options.previewCLI();
|
||||
#else
|
||||
@ -228,12 +230,13 @@ int App::initialize(const AppOptions& options)
|
||||
#ifdef _WIN32
|
||||
if (options.disableWintab() ||
|
||||
!preferences().experimental.loadWintabDriver()) {
|
||||
os::instance()->useWintabAPI(false);
|
||||
system->useWintabAPI(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
os::instance()->setAppMode(m_isGui ? os::AppMode::GUI:
|
||||
os::AppMode::CLI);
|
||||
system->setAppName(get_app_name());
|
||||
system->setAppMode(m_isGui ? os::AppMode::GUI:
|
||||
os::AppMode::CLI);
|
||||
|
||||
if (m_isGui)
|
||||
m_uiSystem.reset(new ui::UISystem);
|
||||
@ -309,6 +312,12 @@ int App::initialize(const AppOptions& options)
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Call the init() function from all plugins
|
||||
LOG("APP: Initializing scripts...\n");
|
||||
extensions().executeInitActions();
|
||||
#endif
|
||||
|
||||
// Process options
|
||||
LOG("APP: Processing options...\n");
|
||||
{
|
||||
@ -324,7 +333,7 @@ int App::initialize(const AppOptions& options)
|
||||
return code;
|
||||
}
|
||||
|
||||
os::instance()->finishLaunching();
|
||||
system->finishLaunching();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -395,7 +404,7 @@ void App::run()
|
||||
// we've to print errors).
|
||||
Console console;
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Use the app::Console() for script erros
|
||||
// Use the app::Console() for script errors
|
||||
ConsoleEngineDelegate delegate;
|
||||
script::ScopedEngineDelegate setEngineDelegate(m_engine.get(), &delegate);
|
||||
#endif
|
||||
@ -414,6 +423,13 @@ void App::run()
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Call the exit() function from all plugins
|
||||
extensions().executeExitActions();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
if (isGui()) {
|
||||
// Select no document
|
||||
|
@ -38,8 +38,12 @@
|
||||
#include "tinyxml.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#define MENUS_TRACE(...) // TRACEARGS
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -61,6 +65,8 @@ const int kUnicodeRight = 0xF703; // NSRightArrowFunctionKey
|
||||
const int kUnicodeUp = 0xF700; // NSUpArrowFunctionKey
|
||||
const int kUnicodeDown = 0xF701; // NSDownArrowFunctionKey
|
||||
|
||||
const char* kFileRecentListGroup = "file_recent_list";
|
||||
|
||||
void destroy_instance(AppMenus* instance)
|
||||
{
|
||||
delete instance;
|
||||
@ -328,10 +334,24 @@ AppMenus::~AppMenus()
|
||||
|
||||
void AppMenus::reload()
|
||||
{
|
||||
MENUS_TRACE("MENUS: AppMenus::reload()");
|
||||
|
||||
XmlDocumentRef doc(GuiXml::instance()->doc());
|
||||
TiXmlHandle handle(doc.get());
|
||||
const char* path = GuiXml::instance()->filename();
|
||||
|
||||
////////////////////////////////////////
|
||||
// Remove all menu items added to groups from recent files and
|
||||
// scripts so we can re-add them later in the new menus.
|
||||
|
||||
for (auto& it : m_groups) {
|
||||
GroupInfo& group = it.second;
|
||||
MENUS_TRACE("MENUS: - groups", it.first, "with", group.items.size(), "item(s)");
|
||||
group.end = nullptr; // This value will be restored later
|
||||
for (auto& item : group.items)
|
||||
item->parent()->removeChild(item);
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// Load menus
|
||||
|
||||
@ -370,10 +390,39 @@ void AppMenus::reload()
|
||||
if (scriptsMenu) {
|
||||
delete scriptsMenu;
|
||||
delete m_rootMenu->findItemById("scripts_menu_separator");
|
||||
|
||||
// Remove scripts group
|
||||
auto it = m_groups.find("file_scripts");
|
||||
if (it != m_groups.end())
|
||||
m_groups.erase(it);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// Re-add menu items in groups (recent files & scripts)
|
||||
|
||||
for (auto& it : m_groups) {
|
||||
GroupInfo& group = it.second;
|
||||
if (group.end) {
|
||||
MENUS_TRACE("MENUS: - re-adding group ", it.first, "with", group.items.size(), "item(s)");
|
||||
|
||||
auto menu = group.end->parent();
|
||||
int insertIndex = menu->getChildIndex(group.end);
|
||||
for (auto& item : group.items) {
|
||||
menu->insertChild(++insertIndex, item);
|
||||
group.end = item;
|
||||
}
|
||||
}
|
||||
// Delete items that don't have a group now
|
||||
else {
|
||||
MENUS_TRACE("MENUS: - deleting group ", it.first, "with", group.items.size(), "item(s)");
|
||||
for (auto& item : group.items)
|
||||
item->deferDelete();
|
||||
group.items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// Load keyboard shortcuts for commands
|
||||
|
||||
@ -458,6 +507,8 @@ void AppMenus::initTheme()
|
||||
|
||||
bool AppMenus::rebuildRecentList()
|
||||
{
|
||||
MENUS_TRACE("MENUS: AppMenus::rebuildRecentList m_recentFilesPlaceholder=", m_recentFilesPlaceholder);
|
||||
|
||||
if (!m_recentFilesPlaceholder)
|
||||
return true;
|
||||
|
||||
@ -469,13 +520,10 @@ bool AppMenus::rebuildRecentList()
|
||||
if (!owner || owner->hasSubmenuOpened())
|
||||
return false;
|
||||
|
||||
int insertIndex = menu->getChildIndex(m_recentFilesPlaceholder)+1;
|
||||
|
||||
// Remove active items
|
||||
while (auto appItem = dynamic_cast<AppMenuItem*>(menu->at(insertIndex))) {
|
||||
menu->removeChild(appItem);
|
||||
appItem->deferDelete();
|
||||
}
|
||||
for (auto item : m_recentMenuItems)
|
||||
removeMenuItemFromGroup(item);
|
||||
m_recentMenuItems.clear();
|
||||
|
||||
Command* openFile = Commands::instance()->byId(CommandId::OpenFile());
|
||||
|
||||
@ -492,18 +540,24 @@ bool AppMenus::rebuildRecentList()
|
||||
for (const auto& fn : files) {
|
||||
params.set("filename", fn.c_str());
|
||||
|
||||
auto menuitem = new AppMenuItem(base::get_file_name(fn).c_str(),
|
||||
openFile, params);
|
||||
std::unique_ptr<AppMenuItem> menuitem(
|
||||
new AppMenuItem(base::get_file_name(fn).c_str(),
|
||||
openFile, params));
|
||||
menuitem->setIsRecentFileItem(true);
|
||||
menu->insertChild(insertIndex++, menuitem);
|
||||
|
||||
m_recentMenuItems.push_back(menuitem.get());
|
||||
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto menuitem = new AppMenuItem(
|
||||
Strings::main_menu_file_no_recent_file(), nullptr);
|
||||
std::unique_ptr<AppMenuItem> menuitem(
|
||||
new AppMenuItem(
|
||||
Strings::main_menu_file_no_recent_file(), nullptr));
|
||||
menuitem->setIsRecentFileItem(true);
|
||||
menuitem->setEnabled(false);
|
||||
menu->insertChild(insertIndex++, menuitem);
|
||||
|
||||
m_recentMenuItems.push_back(menuitem.get());
|
||||
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
|
||||
}
|
||||
|
||||
// Sync native menus
|
||||
@ -520,6 +574,65 @@ bool AppMenus::rebuildRecentList()
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppMenus::addMenuItemIntoGroup(const std::string& groupId,
|
||||
std::unique_ptr<MenuItem>&& menuItem)
|
||||
{
|
||||
GroupInfo& group = m_groups[groupId];
|
||||
Widget* menu = group.end->parent();
|
||||
ASSERT(menu);
|
||||
int insertIndex = menu->getChildIndex(group.end);
|
||||
menu->insertChild(insertIndex+1, menuItem.get());
|
||||
|
||||
group.end = menuItem.get();
|
||||
group.items.push_back(menuItem.get());
|
||||
|
||||
menuItem.release();
|
||||
}
|
||||
|
||||
template<typename Pred>
|
||||
void AppMenus::removeMenuItemFromGroup(Pred pred)
|
||||
{
|
||||
for (auto& it : m_groups) {
|
||||
GroupInfo& group = it.second;
|
||||
for (auto it=group.items.begin(); it != group.items.end(); ) {
|
||||
auto& item = *it;
|
||||
if (pred(item)) {
|
||||
if (item == group.end)
|
||||
group.end = group.end->previousSibling();
|
||||
|
||||
item->parent()->removeChild(item);
|
||||
if (auto appItem = dynamic_cast<AppMenuItem*>(item)) {
|
||||
if (appItem)
|
||||
appItem->disposeNative();
|
||||
}
|
||||
item->deferDelete();
|
||||
|
||||
it = group.items.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppMenus::removeMenuItemFromGroup(Command* cmd)
|
||||
{
|
||||
removeMenuItemFromGroup(
|
||||
[cmd](Widget* item){
|
||||
auto appMenuItem = dynamic_cast<AppMenuItem*>(item);
|
||||
return (appMenuItem && appMenuItem->getCommand() == cmd);
|
||||
});
|
||||
}
|
||||
|
||||
void AppMenus::removeMenuItemFromGroup(Widget* menuItem)
|
||||
{
|
||||
removeMenuItemFromGroup(
|
||||
[menuItem](Widget* item){
|
||||
return (item == menuItem);
|
||||
});
|
||||
}
|
||||
|
||||
Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
|
||||
{
|
||||
ASSERT(id != NULL);
|
||||
@ -565,6 +678,7 @@ Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
|
||||
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
|
||||
{
|
||||
const char* id = elem->Attribute("id");
|
||||
const char* group = elem->Attribute("group");
|
||||
|
||||
// is it a <separator>?
|
||||
if (strcmp(elem->Value(), "separator") == 0) {
|
||||
@ -577,6 +691,8 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
|
||||
m_recentFilesPlaceholder = item;
|
||||
}
|
||||
}
|
||||
if (group)
|
||||
m_groups[group].end = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -608,6 +724,8 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
|
||||
|
||||
if (id) menuitem->setId(id);
|
||||
menuitem->processMnemonicFromText();
|
||||
if (group)
|
||||
m_groups[group].end = menuitem;
|
||||
|
||||
// Has it a ID?
|
||||
if (id) {
|
||||
@ -708,8 +826,7 @@ void AppMenus::createNativeMenus()
|
||||
if (!menus) // This platform doesn't support native menu items
|
||||
return;
|
||||
|
||||
if (m_osMenu)
|
||||
m_osMenu->dispose();
|
||||
os::Menu* oldOSMenu = m_osMenu;
|
||||
m_osMenu = menus->createMenu();
|
||||
|
||||
#ifdef __APPLE__ // Create default macOS app menus (App ... Window)
|
||||
@ -795,6 +912,8 @@ void AppMenus::createNativeMenus()
|
||||
#endif
|
||||
|
||||
menus->setAppMenu(m_osMenu);
|
||||
if (oldOSMenu)
|
||||
oldOSMenu->dispose();
|
||||
}
|
||||
|
||||
void AppMenus::createNativeSubmenus(os::Menu* osMenu, const ui::Menu* uiMenu)
|
||||
|
@ -65,7 +65,16 @@ namespace app {
|
||||
const KeyPtr& key);
|
||||
void syncNativeMenuItemKeyShortcuts();
|
||||
|
||||
// Menu item handling in groups
|
||||
void addMenuItemIntoGroup(const std::string& groupId,
|
||||
std::unique_ptr<MenuItem>&& menuItem);
|
||||
void removeMenuItemFromGroup(Command* cmd);
|
||||
void removeMenuItemFromGroup(Widget* menuItem);
|
||||
|
||||
private:
|
||||
template<typename Pred>
|
||||
void removeMenuItemFromGroup(Pred pred);
|
||||
|
||||
Menu* loadMenuById(TiXmlHandle& handle, const char *id);
|
||||
Menu* convertXmlelemToMenu(TiXmlElement* elem);
|
||||
Widget* convertXmlelemToMenuitem(TiXmlElement* elem);
|
||||
@ -82,6 +91,11 @@ namespace app {
|
||||
const bool rootLevel);
|
||||
#endif
|
||||
|
||||
struct GroupInfo {
|
||||
Widget* end = nullptr;
|
||||
WidgetsList items;
|
||||
};
|
||||
|
||||
std::unique_ptr<Menu> m_rootMenu;
|
||||
Widget* m_recentFilesPlaceholder;
|
||||
MenuItem* m_helpMenuitem;
|
||||
@ -97,6 +111,13 @@ namespace app {
|
||||
std::unique_ptr<Menu> m_inkPopupMenu;
|
||||
obs::scoped_connection m_recentFilesConn;
|
||||
std::vector<Menu*> m_menus;
|
||||
// List of recent menu items pointing to recent files.
|
||||
WidgetsList m_recentMenuItems;
|
||||
// Extension points for plugins (each group is a place where new
|
||||
// menu items can be added).
|
||||
std::map<std::string, GroupInfo> m_groups;
|
||||
// Native main menu bar (== nullptr if the platform doesn't
|
||||
// support native menus)
|
||||
os::Menu* m_osMenu;
|
||||
XmlTranslator m_xmlTranslator;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -23,6 +23,7 @@
|
||||
#include "app/filename_formatter.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/split_string.h"
|
||||
@ -34,6 +35,7 @@
|
||||
#include "doc/tags.h"
|
||||
#include "render/dithering_algorithm.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
@ -517,7 +519,7 @@ int CliProcessor::process(Context* ctx)
|
||||
scaleWidth = (doc->width() > maxWidth ? maxWidth / doc->width() : 1.0);
|
||||
scaleHeight = (doc->height() > maxHeight ? maxHeight / doc->height() : 1.0);
|
||||
if (scaleWidth < 1.0 || scaleHeight < 1.0) {
|
||||
scale = MIN(scaleWidth, scaleHeight);
|
||||
scale = std::min(scaleWidth, scaleHeight);
|
||||
Params params;
|
||||
params.set("scale", base::convert_to<std::string>(scale).c_str());
|
||||
ctx->executeCommand(Commands::instance()->byId(CommandId::SpriteSize()),
|
||||
@ -642,8 +644,8 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
|
||||
// --frame-range with --frame-tag
|
||||
if (tag) {
|
||||
selFrames.insert(
|
||||
tag->fromFrame()+MID(0, cof.fromFrame, tag->frames()-1),
|
||||
tag->fromFrame()+MID(0, cof.toFrame, tag->frames()-1));
|
||||
tag->fromFrame()+base::clamp(cof.fromFrame, 0, tag->frames()-1),
|
||||
tag->fromFrame()+base::clamp(cof.toFrame, 0, tag->frames()-1));
|
||||
}
|
||||
// --frame-range without --frame-tag
|
||||
else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +18,7 @@
|
||||
#include "app/cmd/set_cel_opacity.h"
|
||||
#include "app/cmd/set_cel_position.h"
|
||||
#include "app/doc.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
@ -64,7 +65,7 @@ void BackgroundFromLayer::onExecute()
|
||||
bg_image.get(), cel_image,
|
||||
sprite->palette(cel->frame()),
|
||||
cel->x(), cel->y(),
|
||||
MID(0, cel->opacity(), 255),
|
||||
base::clamp(cel->opacity(), 0, 255),
|
||||
static_cast<LayerImage*>(layer)->blendMode());
|
||||
|
||||
// now we have to copy the new image (bg_image) to the cel...
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -37,11 +37,11 @@ SetPalette::SetPalette(Sprite* sprite, frame_t frame, const Palette* newPalette)
|
||||
ASSERT(diffs > 0);
|
||||
|
||||
if (m_from >= 0 && m_to >= m_from) {
|
||||
int oldColors = MIN(m_to+1, m_oldNColors)-m_from;
|
||||
int oldColors = std::min(m_to+1, m_oldNColors)-m_from;
|
||||
if (oldColors > 0)
|
||||
m_oldColors.resize(oldColors);
|
||||
|
||||
int newColors = MIN(m_to+1, m_newNColors)-m_from;
|
||||
int newColors = std::min(m_to+1, m_newNColors)-m_from;
|
||||
if (newColors > 0)
|
||||
m_newColors.resize(newColors);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -70,6 +70,7 @@ private:
|
||||
SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
const PixelFormat newFormat,
|
||||
const render::Dithering& dithering,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate)
|
||||
: WithSprite(sprite)
|
||||
, m_oldFormat(sprite->pixelFormat())
|
||||
@ -101,6 +102,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
oldImage,
|
||||
cel->frame(),
|
||||
cel->layer()->isBackground(),
|
||||
toGray,
|
||||
&superDel);
|
||||
|
||||
superDel.nextImage();
|
||||
@ -116,6 +118,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
oldImage,
|
||||
0, // TODO select a frame or generate other tilesets?
|
||||
false, // TODO is background? it depends of the layer where this tileset is used
|
||||
toGray,
|
||||
&superDel);
|
||||
}
|
||||
superDel.nextImage();
|
||||
@ -188,6 +191,7 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
|
||||
const doc::ImageRef& oldImage,
|
||||
const doc::frame_t frame,
|
||||
const bool isBackground,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate)
|
||||
{
|
||||
ASSERT(oldImage);
|
||||
@ -201,6 +205,7 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
|
||||
sprite->palette(frame),
|
||||
isBackground,
|
||||
oldImage->maskColor(),
|
||||
toGray,
|
||||
delegate));
|
||||
|
||||
m_seq.add(new cmd::ReplaceImage(sprite, oldImage, newImage));
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/cmd/with_sprite.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/pixel_format.h"
|
||||
@ -33,6 +34,7 @@ namespace cmd {
|
||||
SetPixelFormat(doc::Sprite* sprite,
|
||||
const doc::PixelFormat newFormat,
|
||||
const render::Dithering& dithering,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate);
|
||||
|
||||
protected:
|
||||
@ -50,6 +52,7 @@ namespace cmd {
|
||||
const doc::ImageRef& oldImage,
|
||||
const doc::frame_t frame,
|
||||
const bool isBackground,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate);
|
||||
|
||||
doc::PixelFormat m_oldFormat;
|
||||
|
@ -24,12 +24,16 @@ SetUserData::SetUserData(doc::WithUserData* obj, const doc::UserData& userData)
|
||||
|
||||
void SetUserData::onExecute()
|
||||
{
|
||||
doc::get<doc::WithUserData>(m_objId)->setUserData(m_newUserData);
|
||||
auto obj = doc::get<doc::WithUserData>(m_objId);
|
||||
obj->setUserData(m_newUserData);
|
||||
obj->incrementVersion();
|
||||
}
|
||||
|
||||
void SetUserData::onUndo()
|
||||
{
|
||||
doc::get<doc::WithUserData>(m_objId)->setUserData(m_oldUserData);
|
||||
auto obj = doc::get<doc::WithUserData>(m_objId);
|
||||
obj->setUserData(m_oldUserData);
|
||||
obj->incrementVersion();
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -88,10 +88,7 @@ std::istream* CmdTransaction::documentRangeAfterExecute() const
|
||||
|
||||
void CmdTransaction::onExecute()
|
||||
{
|
||||
CmdSequence::onExecute();
|
||||
|
||||
// The execution of CmdTransaction is called by Transaction at the
|
||||
// very beginning, just to save the current sprite position.
|
||||
// Save the current site and doc range
|
||||
m_spritePositionBefore = calcSpritePosition();
|
||||
#ifdef ENABLE_UI
|
||||
if (isDocRangeEnabled()) {
|
||||
@ -100,6 +97,9 @@ void CmdTransaction::onExecute()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Execute the sequence of "cmds"
|
||||
CmdSequence::onExecute();
|
||||
|
||||
if (m_changeSavedState)
|
||||
++(*m_savedCounter);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "app/color_utils.h"
|
||||
#include "app/modules/palettes.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/debug.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/palette.h"
|
||||
@ -203,8 +204,8 @@ std::string Color::toString() const
|
||||
<< std::setprecision(2)
|
||||
<< std::fixed
|
||||
<< m_value.hsv.h << ","
|
||||
<< MID(0.0, m_value.hsv.s*100.0, 100.0) << ","
|
||||
<< MID(0.0, m_value.hsv.v*100.0, 100.0) << ","
|
||||
<< base::clamp(m_value.hsv.s*100.0, 0.0, 100.0) << ","
|
||||
<< base::clamp(m_value.hsv.v*100.0, 0.0, 100.0) << ","
|
||||
<< m_value.hsv.a << "}";
|
||||
break;
|
||||
|
||||
@ -213,8 +214,8 @@ std::string Color::toString() const
|
||||
<< std::setprecision(2)
|
||||
<< std::fixed
|
||||
<< m_value.hsl.h << ","
|
||||
<< MID(0.0, m_value.hsl.s*100.0, 100.0) << ","
|
||||
<< MID(0.0, m_value.hsl.l*100.0, 100.0) << ","
|
||||
<< base::clamp(m_value.hsl.s*100.0, 0.0, 100.0) << ","
|
||||
<< base::clamp(m_value.hsl.l*100.0, 0.0, 100.0) << ","
|
||||
<< m_value.hsl.a << "}";
|
||||
break;
|
||||
|
||||
@ -267,8 +268,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
|
||||
else {
|
||||
result << "HSV "
|
||||
<< int(m_value.hsv.h) << "\xc2\xb0 "
|
||||
<< MID(0, int(m_value.hsv.s*100.0), 100) << "% "
|
||||
<< MID(0, int(m_value.hsv.v*100.0), 100) << "%";
|
||||
<< base::clamp(int(m_value.hsv.s*100.0), 0, 100) << "% "
|
||||
<< base::clamp(int(m_value.hsv.v*100.0), 0, 100) << "%";
|
||||
|
||||
if (pixelFormat == IMAGE_INDEXED)
|
||||
result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);
|
||||
@ -287,8 +288,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
|
||||
else {
|
||||
result << "HSL "
|
||||
<< int(m_value.hsl.h) << "\xc2\xb0 "
|
||||
<< MID(0, int(m_value.hsl.s*100.0), 100) << "% "
|
||||
<< MID(0, int(m_value.hsl.l*100.0), 100) << "%";
|
||||
<< base::clamp(int(m_value.hsl.s*100.0), 0, 100) << "% "
|
||||
<< base::clamp(int(m_value.hsl.l*100.0), 0, 100) << "%";
|
||||
|
||||
if (pixelFormat == IMAGE_INDEXED)
|
||||
result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);
|
||||
@ -357,8 +358,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
|
||||
}
|
||||
else {
|
||||
result << int(m_value.hsv.h) << "\xc2\xb0"
|
||||
<< MID(0, int(m_value.hsv.s*100.0), 100) << ","
|
||||
<< MID(0, int(m_value.hsv.v*100.0), 100);
|
||||
<< base::clamp(int(m_value.hsv.s*100.0), 0, 100) << ","
|
||||
<< base::clamp(int(m_value.hsv.v*100.0), 0, 100);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -368,8 +369,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
|
||||
}
|
||||
else {
|
||||
result << int(m_value.hsl.h) << "\xc2\xb0"
|
||||
<< MID(0, int(m_value.hsl.s*100.0), 100) << ","
|
||||
<< MID(0, int(m_value.hsl.l*100.0), 100);
|
||||
<< base::clamp(int(m_value.hsl.s*100.0), 0, 100) << ","
|
||||
<< base::clamp(int(m_value.hsl.l*100.0), 0, 100);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -907,7 +908,7 @@ int Color::getAlpha() const
|
||||
|
||||
void Color::setAlpha(int alpha)
|
||||
{
|
||||
alpha = MID(0, alpha, 255);
|
||||
alpha = base::clamp(alpha, 0, 255);
|
||||
|
||||
switch (getType()) {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,6 +24,7 @@
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/sprite.h"
|
||||
@ -368,8 +369,8 @@ void CanvasSizeCommand::onExecute(Context* context)
|
||||
|
||||
api.cropSprite(sprite,
|
||||
gfx::Rect(x1, y1,
|
||||
MID(1, x2-x1, DOC_SPRITE_MAX_WIDTH),
|
||||
MID(1, y2-y1, DOC_SPRITE_MAX_HEIGHT)),
|
||||
base::clamp(x2-x1, 1, DOC_SPRITE_MAX_WIDTH),
|
||||
base::clamp(y2-y1, 1, DOC_SPRITE_MAX_HEIGHT)),
|
||||
params.trimOutside());
|
||||
tx.commit();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +19,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/cels_range.h"
|
||||
#include "doc/sprite.h"
|
||||
@ -51,7 +53,7 @@ CelOpacityCommand::CelOpacityCommand()
|
||||
void CelOpacityCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_opacity = params.get_as<int>("opacity");
|
||||
m_opacity = MID(0, m_opacity, 255);
|
||||
m_opacity = base::clamp(m_opacity, 0, 255);
|
||||
}
|
||||
|
||||
bool CelOpacityCommand::onEnabled(Context* context)
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "ui/size_hint_event.h"
|
||||
|
||||
#include "color_mode.xml.h"
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -51,6 +52,15 @@ using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
rgba_to_graya_func get_gray_func(gen::ToGrayAlgorithm toGray) {
|
||||
switch (toGray) {
|
||||
case gen::ToGrayAlgorithm::LUMA: return &rgba_to_graya_using_luma;
|
||||
case gen::ToGrayAlgorithm::HSV: return &rgba_to_graya_using_hsv;
|
||||
case gen::ToGrayAlgorithm::HSL: return &rgba_to_graya_using_hsl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class ConversionItem : public ListItem {
|
||||
public:
|
||||
ConversionItem(const doc::PixelFormat pixelFormat)
|
||||
@ -79,6 +89,7 @@ public:
|
||||
const doc::frame_t frame,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::Dithering& dithering,
|
||||
const gen::ToGrayAlgorithm toGray,
|
||||
const gfx::Point& pos,
|
||||
const bool newBlend)
|
||||
: m_image(dstImage)
|
||||
@ -91,10 +102,12 @@ public:
|
||||
sprite, frame,
|
||||
pixelFormat,
|
||||
dithering,
|
||||
toGray,
|
||||
newBlend]() { // Copy the matrix
|
||||
run(sprite, frame,
|
||||
pixelFormat,
|
||||
dithering,
|
||||
toGray,
|
||||
newBlend);
|
||||
})
|
||||
{
|
||||
@ -118,6 +131,7 @@ private:
|
||||
const doc::frame_t frame,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::Dithering& dithering,
|
||||
const gen::ToGrayAlgorithm toGray,
|
||||
const bool newBlend) {
|
||||
doc::ImageRef tmp(
|
||||
Image::create(sprite->pixelFormat(),
|
||||
@ -142,6 +156,7 @@ private:
|
||||
sprite->palette(frame),
|
||||
(sprite->backgroundLayer() != nullptr),
|
||||
0,
|
||||
get_gray_func(toGray),
|
||||
this);
|
||||
|
||||
m_running = false;
|
||||
@ -211,9 +226,12 @@ public:
|
||||
else {
|
||||
amount()->setVisible(false);
|
||||
}
|
||||
if (from != IMAGE_GRAYSCALE)
|
||||
if (from != IMAGE_GRAYSCALE) {
|
||||
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
|
||||
|
||||
toGrayCombobox()->Change.connect(base::Bind<void>(&ColorModeWindow::onToGrayChange, this));
|
||||
}
|
||||
|
||||
colorModeView()->setMinSize(
|
||||
colorModeView()->sizeHint() +
|
||||
colorMode()->sizeHint());
|
||||
@ -249,6 +267,15 @@ public:
|
||||
return d;
|
||||
}
|
||||
|
||||
gen::ToGrayAlgorithm toGray() const {
|
||||
static_assert(
|
||||
int(gen::ToGrayAlgorithm::LUMA) == 0 &&
|
||||
int(gen::ToGrayAlgorithm::HSV) == 1 &&
|
||||
int(gen::ToGrayAlgorithm::HSL) == 2,
|
||||
"Check that 'to_gray_combobox' combobox items matches these indexes in color_mode.xml");
|
||||
return (gen::ToGrayAlgorithm)toGrayCombobox()->getSelectedItemIndex();
|
||||
}
|
||||
|
||||
bool flattenEnabled() const {
|
||||
return flatten()->isSelected();
|
||||
}
|
||||
@ -307,6 +334,11 @@ private:
|
||||
amount()->setVisible(toIndexed && errorDiff);
|
||||
}
|
||||
|
||||
{
|
||||
const bool toGray = (dstPixelFormat == doc::IMAGE_GRAYSCALE);
|
||||
toGrayCombobox()->setVisible(toGray);
|
||||
}
|
||||
|
||||
m_image.reset(
|
||||
Image::create(dstPixelFormat,
|
||||
visibleBounds.w,
|
||||
@ -336,6 +368,7 @@ private:
|
||||
m_editor->frame(),
|
||||
dstPixelFormat,
|
||||
dithering(),
|
||||
toGray(),
|
||||
visibleBounds.origin(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
|
||||
@ -348,6 +381,12 @@ private:
|
||||
onChangeColorMode();
|
||||
}
|
||||
|
||||
void onToGrayChange() {
|
||||
stop();
|
||||
m_selectedItem = nullptr;
|
||||
onChangeColorMode();
|
||||
}
|
||||
|
||||
void onMonitorProgress() {
|
||||
ASSERT(m_bgThread);
|
||||
if (!m_bgThread)
|
||||
@ -402,6 +441,7 @@ private:
|
||||
bool m_useUI;
|
||||
doc::PixelFormat m_format;
|
||||
render::Dithering m_dithering;
|
||||
gen::ToGrayAlgorithm m_toGray;
|
||||
};
|
||||
|
||||
ChangePixelFormatCommand::ChangePixelFormatCommand()
|
||||
@ -410,6 +450,7 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
|
||||
m_useUI = true;
|
||||
m_format = IMAGE_RGB;
|
||||
m_dithering = render::Dithering();
|
||||
m_toGray = gen::ToGrayAlgorithm::DEFAULT;
|
||||
}
|
||||
|
||||
void ChangePixelFormatCommand::onLoadParams(const Params& params)
|
||||
@ -418,7 +459,8 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
|
||||
|
||||
std::string format = params.get("format");
|
||||
if (format == "rgb") m_format = IMAGE_RGB;
|
||||
else if (format == "grayscale") m_format = IMAGE_GRAYSCALE;
|
||||
else if (format == "grayscale" ||
|
||||
format == "gray") m_format = IMAGE_GRAYSCALE;
|
||||
else if (format == "indexed") m_format = IMAGE_INDEXED;
|
||||
else
|
||||
m_useUI = true;
|
||||
@ -454,6 +496,16 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
|
||||
// TODO object slicing here (from BayerMatrix -> DitheringMatrix)
|
||||
m_dithering.matrix(render::BayerMatrix(8));
|
||||
}
|
||||
|
||||
std::string toGray = params.get("toGray");
|
||||
if (toGray == "luma")
|
||||
m_toGray = gen::ToGrayAlgorithm::LUMA;
|
||||
else if (dithering == "hsv")
|
||||
m_toGray = gen::ToGrayAlgorithm::HSV;
|
||||
else if (dithering == "hsl")
|
||||
m_toGray = gen::ToGrayAlgorithm::HSL;
|
||||
else
|
||||
m_toGray = gen::ToGrayAlgorithm::DEFAULT;
|
||||
}
|
||||
|
||||
bool ChangePixelFormatCommand::onEnabled(Context* context)
|
||||
@ -518,6 +570,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
|
||||
m_format = window.pixelFormat();
|
||||
m_dithering = window.dithering();
|
||||
m_toGray = window.toGray();
|
||||
flatten = window.flattenEnabled();
|
||||
|
||||
window.saveDitheringOptions();
|
||||
@ -554,6 +607,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
new cmd::SetPixelFormat(
|
||||
sprite, m_format,
|
||||
m_dithering,
|
||||
get_gray_func(m_toGray),
|
||||
&job)); // SpriteJob is a render::TaskDelegate
|
||||
});
|
||||
job.waitJob();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -63,23 +63,23 @@ void FlipCommand::onLoadParams(const Params& params)
|
||||
doc::algorithm::FlipHorizontal);
|
||||
}
|
||||
|
||||
bool FlipCommand::onEnabled(Context* context)
|
||||
bool FlipCommand::onEnabled(Context* ctx)
|
||||
{
|
||||
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
|
||||
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable);
|
||||
}
|
||||
|
||||
void FlipCommand::onExecute(Context* context)
|
||||
void FlipCommand::onExecute(Context* ctx)
|
||||
{
|
||||
Site site = context->activeSite();
|
||||
Timeline* timeline = App::instance()->timeline();
|
||||
LockTimelineRange lockRange(timeline);
|
||||
Site site = ctx->activeSite();
|
||||
LockTimelineRange lockRange(App::instance()->timeline());
|
||||
|
||||
CelList cels;
|
||||
if (m_flipMask) {
|
||||
// If we want to flip the visible mask we can go to
|
||||
// MovingPixelsState (even when the range is enabled, because now
|
||||
// PixelsMovement support ranges).
|
||||
if (site.document()->isMaskVisible()) {
|
||||
if (site.document()->isMaskVisible() &&
|
||||
ctx->isUIAvailable()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
@ -89,7 +89,7 @@ void FlipCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
auto range = timeline->range();
|
||||
auto range = site.range();
|
||||
if (range.enabled()) {
|
||||
cels = get_unlocked_unique_cels(site.sprite(), range);
|
||||
}
|
||||
@ -101,7 +101,7 @@ void FlipCommand::onExecute(Context* context)
|
||||
|
||||
if (cels.empty()) {
|
||||
StatusBar::instance()->showTip(
|
||||
1000, Strings::statusbar_tips_all_layers_are_locked().c_str());
|
||||
1000, Strings::statusbar_tips_all_layers_are_locked());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -111,10 +111,10 @@ void FlipCommand::onExecute(Context* context)
|
||||
cels.push_back(cel);
|
||||
}
|
||||
|
||||
ContextWriter writer(context);
|
||||
ContextWriter writer(ctx);
|
||||
Doc* document = writer.document();
|
||||
Sprite* sprite = writer.sprite();
|
||||
Tx tx(writer.context(), friendlyName());
|
||||
Tx tx(ctx, friendlyName());
|
||||
DocApi api = document->getApi(tx);
|
||||
|
||||
Mask* mask = document->mask();
|
||||
@ -228,7 +228,11 @@ void FlipCommand::onExecute(Context* context)
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
update_screen_for_document(document);
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
if (ctx->isUIAvailable())
|
||||
update_screen_for_document(document);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FlipCommand::onGetFriendlyName() const
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +18,7 @@
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/editor/editor_customization_delegate.h"
|
||||
#include "app/ui/search_entry.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tag.h"
|
||||
#include "ui/combobox.h"
|
||||
@ -256,7 +257,9 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
return MID(0, m_frame-docPref.timeline.firstFrame(), editor->sprite()->lastFrame());
|
||||
return base::clamp(
|
||||
m_frame-docPref.timeline.firstFrame(),
|
||||
0, editor->sprite()->lastFrame());
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +19,7 @@
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -73,9 +75,9 @@ protected:
|
||||
void updateStatusBar(Site& site) {
|
||||
if (site.layer() != NULL)
|
||||
StatusBar::instance()->setStatusText(
|
||||
1000, "%s '%s' selected",
|
||||
(site.layer()->isGroup() ? "Group": "Layer"),
|
||||
site.layer()->name().c_str());
|
||||
1000, fmt::format("{} '{}' selected",
|
||||
(site.layer()->isGroup() ? "Group": "Layer"),
|
||||
site.layer()->name()));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -45,6 +45,7 @@
|
||||
|
||||
#include "keyboard_shortcuts.xml.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
@ -260,7 +261,7 @@ private:
|
||||
m_headerItem->contextXPos() +
|
||||
Graphics::measureUITextLength(
|
||||
convertKeyContextToUserFriendlyString(m_key->keycontext()), font());
|
||||
size.w = MAX(size.w, w);
|
||||
size.w = std::max(size.w, w);
|
||||
}
|
||||
|
||||
if (m_key && !m_key->accels().empty()) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +19,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/layer.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
@ -49,7 +51,7 @@ LayerOpacityCommand::LayerOpacityCommand()
|
||||
void LayerOpacityCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_opacity = params.get_as<int>("opacity");
|
||||
m_opacity = MID(0, m_opacity, 255);
|
||||
m_opacity = base::clamp(m_opacity, 0, 255);
|
||||
}
|
||||
|
||||
bool LayerOpacityCommand::onEnabled(Context* context)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2015-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +18,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tx.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "doc/algorithm/modify_selection.h"
|
||||
#include "doc/brush_type.h"
|
||||
@ -112,7 +113,7 @@ void ModifySelectionCommand::onExecute(Context* context)
|
||||
return;
|
||||
|
||||
quantity = window.quantity()->textInt();
|
||||
quantity = MID(1, quantity, 100);
|
||||
quantity = base::clamp(quantity, 1, 100);
|
||||
|
||||
brush = (window.circle()->isSelected() ? doc::kCircleBrushType:
|
||||
doc::kSquareBrushType);
|
||||
|
@ -42,7 +42,7 @@ protected:
|
||||
void onExecute(Context* context) override;
|
||||
|
||||
// SelectBoxDelegate impl
|
||||
void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButtons buttons) override;
|
||||
void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button) override;
|
||||
void onQuickboxCancel(Editor* editor) override;
|
||||
|
||||
std::string onGetContextBarHelp() override {
|
||||
@ -101,7 +101,7 @@ void NewBrushCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButtons buttons)
|
||||
void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button)
|
||||
{
|
||||
Mask mask;
|
||||
mask.replace(rect);
|
||||
@ -109,7 +109,7 @@ void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::M
|
||||
selectPencilTool();
|
||||
|
||||
// If the right-button was used, we clear the selected area.
|
||||
if (buttons & ui::kButtonRight) {
|
||||
if (button == ui::kButtonRight) {
|
||||
try {
|
||||
ContextWriter writer(UIContext::instance());
|
||||
if (writer.cel()) {
|
||||
@ -175,7 +175,7 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
|
||||
std::string tooltip;
|
||||
tooltip += "Shortcut: ";
|
||||
tooltip += key->accels().front().toString();
|
||||
StatusBar::instance()->showTip(2000, tooltip.c_str());
|
||||
StatusBar::instance()->showTip(2000, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -129,7 +129,7 @@ void NewFileCommand::onExecute(Context* ctx)
|
||||
int w = pref.newFile.width();
|
||||
int h = pref.newFile.height();
|
||||
int bg = pref.newFile.backgroundColor();
|
||||
bg = MID(0, bg, 2);
|
||||
bg = base::clamp(bg, 0, 2);
|
||||
|
||||
// If the clipboard contains an image, we can show the size of the
|
||||
// clipboard as default image size.
|
||||
|
@ -172,10 +172,10 @@ void NewFrameCommand::onExecute(Context* context)
|
||||
if (context->isUIAvailable()) {
|
||||
update_screen_for_document(document);
|
||||
|
||||
StatusBar::instance()
|
||||
->showTip(1000, "New frame %d/%d",
|
||||
(int)context->activeSite().frame()+1,
|
||||
(int)sprite->totalFrames());
|
||||
StatusBar::instance()->showTip(
|
||||
1000, fmt::format("New frame {}/{}",
|
||||
(int)context->activeSite().frame()+1,
|
||||
(int)sprite->totalFrames()));
|
||||
|
||||
App::instance()->mainWindow()->popTimeline();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,6 +24,7 @@
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/load_widget.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
@ -31,6 +32,7 @@
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/range_utils.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/primitives.h"
|
||||
@ -44,8 +46,10 @@
|
||||
|
||||
#include "new_layer.xml.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -400,28 +404,54 @@ void NewLayerCommand::onExecute(Context* context)
|
||||
#endif // ENABLE_UI
|
||||
// Paste new layer from selection
|
||||
else if ((params().viaCut() || params().viaCopy())
|
||||
&& layer->isImage()
|
||||
&& document->isMaskVisible()) {
|
||||
const doc::Mask* mask = document->mask();
|
||||
ASSERT(mask);
|
||||
ImageRef image(new_image_from_mask(site, mask, true));
|
||||
if (image) {
|
||||
Cel* cel = api.addCel(static_cast<LayerImage*>(layer),
|
||||
site.frame(), image);
|
||||
if (cel) {
|
||||
|
||||
RestoreVisibleLayers restore;
|
||||
SelectedLayers layers;
|
||||
SelectedFrames frames;
|
||||
bool merged;
|
||||
if (site.range().enabled()) {
|
||||
merged = true;
|
||||
layers = site.range().selectedLayers();
|
||||
frames = site.range().selectedFrames();
|
||||
restore.showSelectedLayers(site.sprite(), layers);
|
||||
}
|
||||
else {
|
||||
merged = false;
|
||||
layers.insert(site.layer());
|
||||
frames.insert(site.frame());
|
||||
}
|
||||
|
||||
for (frame_t frame : frames) {
|
||||
ImageRef newImage(new_image_from_mask(site, mask, true, merged));
|
||||
if (!newImage)
|
||||
continue;
|
||||
|
||||
Cel* newCel = api.addCel(static_cast<LayerImage*>(layer),
|
||||
frame, newImage);
|
||||
if (newCel) {
|
||||
gfx::Point pos = mask->bounds().origin();
|
||||
cel->setPosition(pos.x, pos.y);
|
||||
newCel->setPosition(pos.x, pos.y);
|
||||
}
|
||||
|
||||
if (params().viaCut() &&
|
||||
site.cel() && site.layer()) {
|
||||
tx(new cmd::ClearMask(site.cel()));
|
||||
for (Layer* layer : layers) {
|
||||
if (!layer->isImage() ||
|
||||
!layer->isEditable()) // Locked layers will not be modified
|
||||
continue;
|
||||
|
||||
if (site.layer()->isTransparent()) {
|
||||
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
|
||||
cel = site.layer()->cel(site.frame());
|
||||
if (cel)
|
||||
tx(new cmd::TrimCel(cel));
|
||||
Cel* origCel = layer->cel(site.frame());
|
||||
if (origCel &&
|
||||
params().viaCut()) {
|
||||
tx(new cmd::ClearMask(origCel));
|
||||
|
||||
if (layer->isTransparent()) {
|
||||
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
|
||||
origCel = layer->cel(frame);
|
||||
if (origCel)
|
||||
tx(new cmd::TrimCel(origCel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -436,9 +466,9 @@ void NewLayerCommand::onExecute(Context* context)
|
||||
|
||||
StatusBar::instance()->invalidate();
|
||||
StatusBar::instance()->showTip(
|
||||
1000, "%s '%s' created",
|
||||
layerPrefix().c_str(),
|
||||
name.c_str());
|
||||
1000, fmt::format("{} '{}' created",
|
||||
layerPrefix(),
|
||||
name));
|
||||
|
||||
App::instance()->mainWindow()->popTimeline();
|
||||
}
|
||||
@ -466,8 +496,8 @@ std::string NewLayerCommand::onGetFriendlyName() const
|
||||
void NewLayerCommand::adjustRefCelBounds(Cel* cel, gfx::RectF bounds)
|
||||
{
|
||||
Sprite* sprite = cel->sprite();
|
||||
double scale = MIN(double(sprite->width()) / bounds.w,
|
||||
double(sprite->height()) / bounds.h);
|
||||
double scale = std::min(double(sprite->width()) / bounds.w,
|
||||
double(sprite->height()) / bounds.h);
|
||||
bounds.w *= scale;
|
||||
bounds.h *= scale;
|
||||
bounds.x = sprite->width()/2 - bounds.w/2;
|
||||
@ -494,7 +524,7 @@ int NewLayerCommand::getMaxLayerNum(const Layer* layer) const
|
||||
if (layer->isGroup()) {
|
||||
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
|
||||
int tmp = getMaxLayerNum(child);
|
||||
max = MAX(tmp, max);
|
||||
max = std::max(tmp, max);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "app/ui/separator_in_view.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
@ -70,6 +71,25 @@ app::gen::ColorProfileBehavior missingCsMap[] = {
|
||||
app::gen::ColorProfileBehavior::ASK,
|
||||
};
|
||||
|
||||
class ExtensionCategorySeparator : public SeparatorInView {
|
||||
public:
|
||||
ExtensionCategorySeparator(const Extension::Category category,
|
||||
const std::string& text)
|
||||
: SeparatorInView(text, ui::HORIZONTAL)
|
||||
, m_category(category) {
|
||||
InitTheme.connect(
|
||||
[this]{
|
||||
auto b = this->border();
|
||||
b.top(2*b.top());
|
||||
b.bottom(2*b.bottom());
|
||||
this->setBorder(b);
|
||||
});
|
||||
}
|
||||
Extension::Category category() const { return m_category; }
|
||||
private:
|
||||
Extension::Category m_category;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
using namespace ui;
|
||||
@ -122,6 +142,11 @@ class OptionsWindow : public app::gen::Options {
|
||||
|
||||
Extension* extension() { return m_extension; }
|
||||
|
||||
Extension::Category category() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->category();
|
||||
}
|
||||
|
||||
bool isEnabled() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->isEnabled();
|
||||
@ -620,7 +645,7 @@ public:
|
||||
|
||||
int undo_size_limit_value;
|
||||
undo_size_limit_value = undoSizeLimit()->textInt();
|
||||
undo_size_limit_value = MID(0, undo_size_limit_value, 999999);
|
||||
undo_size_limit_value = base::clamp(undo_size_limit_value, 0, 999999);
|
||||
|
||||
m_pref.undo.sizeLimit(undo_size_limit_value);
|
||||
m_pref.undo.gotoModified(undoGotoModified()->isSelected());
|
||||
@ -894,12 +919,13 @@ private:
|
||||
exportAnimationInSequenceAlert()->resetWithDefaultValue();
|
||||
overwriteFilesOnExportAlert()->resetWithDefaultValue();
|
||||
overwriteFilesOnExportSpriteSheetAlert()->resetWithDefaultValue();
|
||||
gifOptionsAlert()->resetWithDefaultValue();
|
||||
jpegOptionsAlert()->resetWithDefaultValue();
|
||||
svgOptionsAlert()->resetWithDefaultValue();
|
||||
advancedModeAlert()->resetWithDefaultValue();
|
||||
invalidFgBgColorAlert()->resetWithDefaultValue();
|
||||
runScriptAlert()->resetWithDefaultValue();
|
||||
gifOptionsAlert()->resetWithDefaultValue();
|
||||
jpegOptionsAlert()->resetWithDefaultValue();
|
||||
svgOptionsAlert()->resetWithDefaultValue();
|
||||
tgaOptionsAlert()->resetWithDefaultValue();
|
||||
}
|
||||
|
||||
void onChangeBgScope() {
|
||||
@ -1131,16 +1157,51 @@ private:
|
||||
themeList()->layout();
|
||||
}
|
||||
|
||||
void loadExtensionsByCategory(const Extension::Category category,
|
||||
const std::string& label) {
|
||||
bool hasItems = false;
|
||||
auto sep = new ExtensionCategorySeparator(category, label);
|
||||
extensionsList()->addChild(sep);
|
||||
for (auto e : App::instance()->extensions()) {
|
||||
if (e->category() == category) {
|
||||
ExtensionItem* item = new ExtensionItem(e);
|
||||
extensionsList()->addChild(item);
|
||||
hasItems = true;
|
||||
}
|
||||
}
|
||||
sep->setVisible(hasItems);
|
||||
}
|
||||
|
||||
void loadExtensions() {
|
||||
// Extensions already loaded
|
||||
if (extensionsList()->getItemsCount() > 0)
|
||||
return;
|
||||
|
||||
for (auto extension : App::instance()->extensions()) {
|
||||
ExtensionItem* item = new ExtensionItem(extension);
|
||||
extensionsList()->addChild(item);
|
||||
}
|
||||
extensionsList()->sortItems();
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::Languages,
|
||||
Strings::options_language_extensions());
|
||||
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::Themes,
|
||||
Strings::options_theme_extensions());
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::Scripts,
|
||||
Strings::options_script_extensions());
|
||||
#endif
|
||||
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::Palettes,
|
||||
Strings::options_palette_extensions());
|
||||
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::DitheringMatrices,
|
||||
Strings::options_dithering_matrix_extensions());
|
||||
|
||||
loadExtensionsByCategory(
|
||||
Extension::Category::Multiple,
|
||||
Strings::options_multiple_extensions());
|
||||
|
||||
onExtensionChange();
|
||||
extensionsList()->layout();
|
||||
@ -1297,7 +1358,33 @@ private:
|
||||
// Add the new extension in the listbox
|
||||
ExtensionItem* item = new ExtensionItem(ext);
|
||||
extensionsList()->addChild(item);
|
||||
extensionsList()->sortItems();
|
||||
updateCategoryVisibility();
|
||||
extensionsList()->sortItems(
|
||||
[](Widget* a, Widget* b){
|
||||
auto aSep = dynamic_cast<ExtensionCategorySeparator*>(a);
|
||||
auto bSep = dynamic_cast<ExtensionCategorySeparator*>(b);
|
||||
auto aItem = dynamic_cast<ExtensionItem*>(a);
|
||||
auto bItem = dynamic_cast<ExtensionItem*>(b);
|
||||
auto aCat = (aSep ? aSep->category():
|
||||
aItem ? aItem->category(): Extension::Category::None);
|
||||
auto bCat = (bSep ? bSep->category():
|
||||
bItem ? bItem->category(): Extension::Category::None);
|
||||
if (aCat < bCat)
|
||||
return true;
|
||||
else if (aCat == bCat) {
|
||||
// There are no two separators with same category.
|
||||
ASSERT(!(aSep && bSep));
|
||||
|
||||
if (aSep && !bSep)
|
||||
return true;
|
||||
else if (!aSep && bSep)
|
||||
return false;
|
||||
else
|
||||
return (base::compare_filenames(a->text(), b->text()) < 0);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
extensionsList()->layout();
|
||||
extensionsList()->selectChild(item);
|
||||
}
|
||||
@ -1337,6 +1424,7 @@ private:
|
||||
void deleteExtensionItem(ExtensionItem* item) {
|
||||
// Remove the item from the list
|
||||
extensionsList()->removeChild(item);
|
||||
updateCategoryVisibility();
|
||||
extensionsList()->layout();
|
||||
item->deferDelete();
|
||||
}
|
||||
@ -1402,6 +1490,21 @@ private:
|
||||
return paths;
|
||||
}
|
||||
|
||||
void updateCategoryVisibility() {
|
||||
bool visibleCategories[int(Extension::Category::Max)];
|
||||
for (auto& v : visibleCategories)
|
||||
v = false;
|
||||
for (auto w : extensionsList()->children()) {
|
||||
if (auto item = dynamic_cast<ExtensionItem*>(w)) {
|
||||
visibleCategories[int(item->category())] = true;
|
||||
}
|
||||
}
|
||||
for (auto w : extensionsList()->children()) {
|
||||
if (auto sep = dynamic_cast<ExtensionCategorySeparator*>(w))
|
||||
sep->setVisible(visibleCategories[int(sep->category())]);
|
||||
}
|
||||
}
|
||||
|
||||
Context* m_context;
|
||||
Preferences& m_pref;
|
||||
DocumentPreferences& m_globPref;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,6 +14,7 @@
|
||||
#include "app/commands/params.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/tx.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/sprite.h"
|
||||
|
||||
@ -69,7 +70,7 @@ void PaletteSizeCommand::onExecute(Context* context)
|
||||
if (ncolors == palette.size())
|
||||
return;
|
||||
|
||||
palette.resize(MID(1, ncolors, std::numeric_limits<int>::max()));
|
||||
palette.resize(base::clamp(ncolors, 1, std::numeric_limits<int>::max()));
|
||||
|
||||
ContextWriter writer(reader);
|
||||
Tx tx(context, "Palette Size", ModifyDocument);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "app/util/freetype_utils.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
#include "doc/image.h"
|
||||
@ -80,7 +81,7 @@ public:
|
||||
|
||||
int sizeValue() const {
|
||||
int size = fontSize()->textInt();
|
||||
size = MID(1, size, 5000);
|
||||
size = base::clamp(size, 1, 5000);
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -165,7 +166,7 @@ void PasteTextCommand::onExecute(Context* ctx)
|
||||
bool antialias = window.antialias()->isSelected();
|
||||
std::string faceName = window.faceValue();
|
||||
int size = window.sizeValue();
|
||||
size = MID(1, size, 999);
|
||||
size = base::clamp(size, 1, 999);
|
||||
pref.textTool.fontFace(faceName);
|
||||
pref.textTool.fontSize(size);
|
||||
pref.textTool.antialias(antialias);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,6 +12,7 @@
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/scale.h"
|
||||
#include "ui/system.h"
|
||||
#include "ui/theme.h"
|
||||
@ -47,12 +49,12 @@ void RefreshCommand::onExecute(Context* context)
|
||||
{
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
if (::GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
StatusBar::instance()
|
||||
->showTip(1000,
|
||||
"Current memory: %.16g KB (%lu)\n"
|
||||
"Peak of memory: %.16g KB (%lu)",
|
||||
pmc.WorkingSetSize / 1024.0, pmc.WorkingSetSize,
|
||||
pmc.PeakWorkingSetSize / 1024.0, pmc.PeakWorkingSetSize);
|
||||
StatusBar::instance()->showTip(
|
||||
1000,
|
||||
fmt::format("Current memory: {:.2f} MB ({})\n"
|
||||
"Peak of memory: {:.2f} MB ({})",
|
||||
pmc.WorkingSetSize / 1024.0 / 1024.0, pmc.WorkingSetSize,
|
||||
pmc.PeakWorkingSetSize / 1024.0 / 1024.0, pmc.PeakWorkingSetSize));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -18,6 +19,7 @@
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/alert.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
@ -95,7 +97,7 @@ void RemoveLayerCommand::onExecute(Context* context)
|
||||
|
||||
StatusBar::instance()->invalidate();
|
||||
if (!layerName.empty())
|
||||
StatusBar::instance()->showTip(1000, "Layer '%s' removed", layerName.c_str());
|
||||
StatusBar::instance()->showTip(1000, fmt::format("Layer '{}' removed", layerName));
|
||||
else
|
||||
StatusBar::instance()->showTip(1000, "Layers removed");
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/alert.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
@ -121,10 +122,10 @@ void RemoveSliceCommand::onExecute(Context* context)
|
||||
StatusBar::instance()->invalidate();
|
||||
if (!sliceName.empty())
|
||||
StatusBar::instance()->showTip(
|
||||
1000, "Slice '%s' removed", sliceName.c_str());
|
||||
1000, fmt::format("Slice '{}' removed", sliceName));
|
||||
else
|
||||
StatusBar::instance()->showTip(
|
||||
1000, "%d slice(s) removed", slicesToDelete.size());
|
||||
1000, fmt::format("{} slice(s) removed", slicesToDelete.size()));
|
||||
}
|
||||
|
||||
Command* CommandFactory::createRemoveSliceCommand()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -223,7 +223,7 @@ void RotateCommand::onExecute(Context* context)
|
||||
|
||||
if (cels.empty()) {
|
||||
StatusBar::instance()->showTip(
|
||||
1000, Strings::statusbar_tips_all_layers_are_locked().c_str());
|
||||
1000, Strings::statusbar_tips_all_layers_are_locked());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -248,9 +248,9 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
document->incrementVersion();
|
||||
}
|
||||
#ifdef ENABLE_UI
|
||||
StatusBar::instance()
|
||||
->setStatusText(2000, "File <%s> saved.",
|
||||
base::get_file_name(filename).c_str());
|
||||
StatusBar::instance()->setStatusText(
|
||||
2000, fmt::format("File <{}> saved.",
|
||||
base::get_file_name(filename)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -363,8 +363,12 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
return result;
|
||||
});
|
||||
|
||||
win.remapWindow();
|
||||
load_window_pos(&win, "ExportFile");
|
||||
again:;
|
||||
if (!win.show())
|
||||
const bool result = win.show();
|
||||
save_window_pos(&win, "ExportFile");
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
outputFilename = win.outputFilenameValue();
|
||||
|
@ -37,7 +37,7 @@ bool ScrollCenterCommand::onEnabled(Context* context)
|
||||
|
||||
void ScrollCenterCommand::onExecute(Context* context)
|
||||
{
|
||||
current_editor->setDefaultScroll();
|
||||
current_editor->setScrollToCenter();
|
||||
}
|
||||
|
||||
Command* CommandFactory::createScrollCenterCommand()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -21,6 +21,7 @@
|
||||
#include "app/sprite_job.h"
|
||||
#include "app/util/resize_image.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "doc/algorithm/resize_image.h"
|
||||
#include "doc/cel.h"
|
||||
@ -35,6 +36,8 @@
|
||||
|
||||
#include "sprite_size.xml.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#define PERC_FORMAT "%.4g"
|
||||
|
||||
namespace app {
|
||||
@ -129,7 +132,9 @@ protected:
|
||||
new_mask->replace(
|
||||
gfx::Rect(
|
||||
scale_x(document()->mask()->bounds().x-1),
|
||||
scale_y(document()->mask()->bounds().y-1), MAX(1, w), MAX(1, h)));
|
||||
scale_y(document()->mask()->bounds().y-1),
|
||||
std::max(1, w),
|
||||
std::max(1, h)));
|
||||
|
||||
// Always use the nearest-neighbor method to resize the bitmap
|
||||
// mask.
|
||||
@ -382,8 +387,8 @@ void SpriteSizeCommand::onExecute(Context* context)
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
|
||||
new_width = MID(1, new_width, DOC_SPRITE_MAX_WIDTH);
|
||||
new_height = MID(1, new_height, DOC_SPRITE_MAX_HEIGHT);
|
||||
new_width = base::clamp(new_width, 1, DOC_SPRITE_MAX_WIDTH);
|
||||
new_height = base::clamp(new_height, 1, DOC_SPRITE_MAX_HEIGHT);
|
||||
|
||||
{
|
||||
SpriteSizeJob job(reader, new_width, new_height, resize_method);
|
||||
|
@ -116,9 +116,9 @@ void UndoCommand::onExecute(Context* context)
|
||||
else
|
||||
msg = "Redid " + undo->nextRedoLabel();
|
||||
if (Preferences::instance().undo.showTooltip())
|
||||
statusbar->showTip(1000, msg.c_str());
|
||||
statusbar->showTip(1000, msg);
|
||||
else
|
||||
statusbar->setStatusText(0, msg.c_str());
|
||||
statusbar->setStatusText(0, msg);
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -71,6 +72,15 @@ Commands* Commands::add(Command* command)
|
||||
return this;
|
||||
}
|
||||
|
||||
void Commands::remove(Command* command)
|
||||
{
|
||||
auto lid = base::string_to_lower(command->id());
|
||||
auto it = m_commands.find(lid);
|
||||
ASSERT(it != m_commands.end());
|
||||
if (it != m_commands.end())
|
||||
m_commands.erase(it);
|
||||
}
|
||||
|
||||
void Commands::getAllIds(std::vector<std::string>& ids)
|
||||
{
|
||||
for (auto& it : m_commands)
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -30,6 +31,9 @@ namespace app {
|
||||
Command* byId(const char* id);
|
||||
Commands* add(Command* command);
|
||||
|
||||
// Remove the command but doesn't delete it
|
||||
void remove(Command* command);
|
||||
|
||||
void getAllIds(std::vector<std::string>& ids);
|
||||
|
||||
private:
|
||||
|
@ -21,6 +21,7 @@ FOR_EACH_COMMAND(CropSprite)
|
||||
FOR_EACH_COMMAND(Despeckle)
|
||||
FOR_EACH_COMMAND(ExportSpriteSheet)
|
||||
FOR_EACH_COMMAND(FlattenLayers)
|
||||
FOR_EACH_COMMAND(Flip)
|
||||
FOR_EACH_COMMAND(HueSaturation)
|
||||
FOR_EACH_COMMAND(InvertColor)
|
||||
FOR_EACH_COMMAND(LayerFromBackground)
|
||||
@ -69,7 +70,6 @@ FOR_EACH_COMMAND(Exit)
|
||||
FOR_EACH_COMMAND(Eyedropper)
|
||||
FOR_EACH_COMMAND(Fill)
|
||||
FOR_EACH_COMMAND(FitScreen)
|
||||
FOR_EACH_COMMAND(Flip)
|
||||
FOR_EACH_COMMAND(FrameProperties)
|
||||
FOR_EACH_COMMAND(FrameTagProperties)
|
||||
FOR_EACH_COMMAND(FullscreenPreview)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -35,10 +35,12 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
using Mode = filters::HueSaturationFilter::Mode;
|
||||
|
||||
struct HueSaturationParams : public NewParams {
|
||||
Param<bool> ui { this, true, "ui" };
|
||||
Param<filters::Target> channels { this, 0, "channels" };
|
||||
Param<filters::HueSaturationFilter::Mode> mode { this, filters::HueSaturationFilter::Mode::HSL, "mode" };
|
||||
Param<Mode> mode { this, Mode::HSL_MUL, "mode" };
|
||||
Param<double> hue { this, 0.0, "hue" };
|
||||
Param<double> saturation { this, 0.0, "saturation" };
|
||||
Param<double> lightness { this, 0.0, { "lightness", "value" } };
|
||||
@ -62,13 +64,17 @@ public:
|
||||
getContainer()->addChild(&m_colorType);
|
||||
getContainer()->addChild(&m_sliders);
|
||||
|
||||
static_assert(int(Mode::HSV_MUL) == 0 &&
|
||||
int(Mode::HSL_MUL) == 1 &&
|
||||
int(Mode::HSV_ADD) == 2 &&
|
||||
int(Mode::HSL_ADD) == 3,
|
||||
"Adjust widget showing filters::HueSaturationFilter::Mode enum");
|
||||
auto mode = Preferences::instance().hueSaturation.mode();
|
||||
m_colorType.addItem("HSV")->setFocusStop(false);
|
||||
m_colorType.addItem("HSL")->setFocusStop(false);
|
||||
if (mode == gen::HueSaturationMode::HSV)
|
||||
m_colorType.setSelectedItem(0);
|
||||
else
|
||||
m_colorType.setSelectedItem(1);
|
||||
m_colorType.addItem("HSV+")->setFocusStop(false);
|
||||
m_colorType.addItem("HSL+")->setFocusStop(false);
|
||||
m_colorType.setSelectedItem(int(mode));
|
||||
m_colorType.ItemChange.connect(base::Bind<void>(&HueSaturationWindow::onChangeMode, this));
|
||||
|
||||
m_sliders.setColorType(app::Color::HslType);
|
||||
@ -78,24 +84,21 @@ public:
|
||||
onChangeMode();
|
||||
}
|
||||
|
||||
Mode mode() const {
|
||||
return Mode(m_colorType.selectedItem());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool isHsl() const {
|
||||
return (m_colorType.selectedItem() == 1);
|
||||
return (mode() == Mode::HSL_MUL ||
|
||||
mode() == Mode::HSL_ADD);
|
||||
}
|
||||
|
||||
void onChangeMode() {
|
||||
const int isHsl = this->isHsl();
|
||||
|
||||
Preferences::instance().hueSaturation.mode
|
||||
(isHsl ? gen::HueSaturationMode::HSL:
|
||||
gen::HueSaturationMode::HSV);
|
||||
|
||||
m_filter.setMode(isHsl ?
|
||||
HueSaturationFilter::Mode::HSL:
|
||||
HueSaturationFilter::Mode::HSV);
|
||||
|
||||
m_sliders.setColorType(isHsl ?
|
||||
Preferences::instance().hueSaturation.mode(mode());
|
||||
m_filter.setMode(mode());
|
||||
m_sliders.setColorType(isHsl() ?
|
||||
app::Color::HslType:
|
||||
app::Color::HsvType);
|
||||
|
||||
@ -139,8 +142,8 @@ public:
|
||||
HueSaturationCommand();
|
||||
|
||||
protected:
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
bool onEnabled(Context* ctx) override;
|
||||
void onExecute(Context* ctx) override;
|
||||
};
|
||||
|
||||
HueSaturationCommand::HueSaturationCommand()
|
||||
@ -148,20 +151,20 @@ HueSaturationCommand::HueSaturationCommand()
|
||||
{
|
||||
}
|
||||
|
||||
bool HueSaturationCommand::onEnabled(Context* context)
|
||||
bool HueSaturationCommand::onEnabled(Context* ctx)
|
||||
{
|
||||
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasActiveSprite);
|
||||
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasActiveSprite);
|
||||
}
|
||||
|
||||
void HueSaturationCommand::onExecute(Context* context)
|
||||
void HueSaturationCommand::onExecute(Context* ctx)
|
||||
{
|
||||
#ifdef ENABLE_UI
|
||||
const bool ui = (params().ui() && context->isUIAvailable());
|
||||
const bool ui = (params().ui() && ctx->isUIAvailable());
|
||||
#endif
|
||||
|
||||
HueSaturationFilter filter;
|
||||
FilterManagerImpl filterMgr(context, &filter);
|
||||
FilterManagerImpl filterMgr(ctx, &filter);
|
||||
if (params().mode.isSet()) filter.setMode(params().mode());
|
||||
if (params().hue.isSet()) filter.setHue(params().hue());
|
||||
if (params().saturation.isSet()) filter.setSaturation(params().saturation() / 100.0);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/commands/filters/color_curve_editor.h"
|
||||
|
||||
#include "base/clamp.h"
|
||||
#include "filters/color_curve.h"
|
||||
#include "ui/alert.h"
|
||||
#include "ui/entry.h"
|
||||
@ -139,8 +140,8 @@ bool ColorCurveEditor::onProcessMessage(Message* msg)
|
||||
if (m_editPoint) {
|
||||
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
|
||||
*m_editPoint = screenToView(mousePos);
|
||||
m_editPoint->x = MID(m_viewBounds.x, m_editPoint->x, m_viewBounds.x+m_viewBounds.w-1);
|
||||
m_editPoint->y = MID(m_viewBounds.y, m_editPoint->y, m_viewBounds.y+m_viewBounds.h-1);
|
||||
m_editPoint->x = base::clamp(m_editPoint->x, m_viewBounds.x, m_viewBounds.x2()-1);
|
||||
m_editPoint->y = base::clamp(m_editPoint->y, m_viewBounds.y, m_viewBounds.y2()-1);
|
||||
|
||||
// TODO this should be optional
|
||||
CurveEditorChange();
|
||||
@ -208,9 +209,9 @@ void ColorCurveEditor::onPaint(ui::PaintEvent& ev)
|
||||
// Draw curve
|
||||
for (c = client.x; c < client.x+client.w; ++c) {
|
||||
pt = clientToView(gfx::Point(c, 0));
|
||||
pt.x = MID(m_viewBounds.x, pt.x, m_viewBounds.x+m_viewBounds.w-1);
|
||||
pt.x = base::clamp(pt.x, m_viewBounds.x, m_viewBounds.x2()-1);
|
||||
pt.y = values[pt.x - m_viewBounds.x];
|
||||
pt.y = MID(m_viewBounds.y, pt.y, m_viewBounds.y+m_viewBounds.h-1);
|
||||
pt.y = base::clamp(pt.y, m_viewBounds.y, m_viewBounds.y2()-1);
|
||||
pt = viewToClient(pt);
|
||||
|
||||
g->putPixel(gfx::rgba(255, 255, 255), c, pt.y);
|
||||
@ -269,8 +270,8 @@ bool ColorCurveEditor::editNodeManually(gfx::Point& viewPt)
|
||||
if (window.closer() == window.ok()) {
|
||||
viewPt.x = window.x()->textInt();
|
||||
viewPt.y = window.y()->textInt();
|
||||
viewPt.x = MID(0, viewPt.x, 255);
|
||||
viewPt.y = MID(0, viewPt.y, 255);
|
||||
viewPt.x = base::clamp(viewPt.x, 0, 255);
|
||||
viewPt.y = base::clamp(viewPt.y, 0, 255);
|
||||
return true;
|
||||
}
|
||||
else if (window.closer() == window.deleteButton()) {
|
||||
|
@ -158,10 +158,15 @@ void Param<filters::OutlineFilter::Matrix>::fromString(const std::string& value)
|
||||
template<>
|
||||
void Param<filters::HueSaturationFilter::Mode>::fromString(const std::string& value)
|
||||
{
|
||||
if (base::utf8_icmp(value, "hsv") == 0)
|
||||
setValue(filters::HueSaturationFilter::Mode::HSV);
|
||||
if (base::utf8_icmp(value, "hsv") == 0 ||
|
||||
base::utf8_icmp(value, "hsv_mul") == 0)
|
||||
setValue(filters::HueSaturationFilter::Mode::HSV_MUL);
|
||||
else if (base::utf8_icmp(value, "hsv_add") == 0)
|
||||
setValue(filters::HueSaturationFilter::Mode::HSV_ADD);
|
||||
else if (base::utf8_icmp(value, "hsl_add") == 0)
|
||||
setValue(filters::HueSaturationFilter::Mode::HSL_ADD);
|
||||
else
|
||||
setValue(filters::HueSaturationFilter::Mode::HSL);
|
||||
setValue(filters::HueSaturationFilter::Mode::HSL_MUL);
|
||||
}
|
||||
|
||||
template<>
|
||||
|
@ -100,6 +100,11 @@ void Context::setActiveFrame(const doc::frame_t frame)
|
||||
onSetActiveFrame(frame);
|
||||
}
|
||||
|
||||
void Context::setRange(const DocRange& range)
|
||||
{
|
||||
onSetRange(range);
|
||||
}
|
||||
|
||||
void Context::setSelectedColors(const doc::PalettePicks& picks)
|
||||
{
|
||||
onSetSelectedColors(picks);
|
||||
@ -260,6 +265,12 @@ void Context::onSetActiveFrame(const doc::frame_t frame)
|
||||
activeSiteHandler()->setActiveFrameInDoc(m_lastSelectedDoc, frame);
|
||||
}
|
||||
|
||||
void Context::onSetRange(const DocRange& range)
|
||||
{
|
||||
if (m_lastSelectedDoc)
|
||||
activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range);
|
||||
}
|
||||
|
||||
void Context::onSetSelectedColors(const doc::PalettePicks& picks)
|
||||
{
|
||||
if (m_lastSelectedDoc)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -32,6 +32,7 @@ namespace app {
|
||||
class ActiveSiteHandler;
|
||||
class Command;
|
||||
class Doc;
|
||||
class DocRange;
|
||||
class DocView;
|
||||
class Preferences;
|
||||
|
||||
@ -88,6 +89,7 @@ namespace app {
|
||||
void setActiveDocument(Doc* document);
|
||||
void setActiveLayer(doc::Layer* layer);
|
||||
void setActiveFrame(doc::frame_t frame);
|
||||
void setRange(const DocRange& range);
|
||||
void setSelectedColors(const doc::PalettePicks& picks);
|
||||
void setSelectedTiles(const doc::PalettePicks& picks);
|
||||
bool hasModifiedDocuments() const;
|
||||
@ -112,6 +114,7 @@ namespace app {
|
||||
virtual void onSetActiveDocument(Doc* doc);
|
||||
virtual void onSetActiveLayer(doc::Layer* layer);
|
||||
virtual void onSetActiveFrame(const doc::frame_t frame);
|
||||
virtual void onSetRange(const DocRange& range);
|
||||
virtual void onSetSelectedColors(const doc::PalettePicks& picks);
|
||||
virtual void onSetSelectedTiles(const doc::PalettePicks& picks);
|
||||
virtual void onCloseDocument(Doc* doc);
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "app/crash/doc_format.h"
|
||||
#include "app/crash/internals.h"
|
||||
#include "app/doc.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/exception.h"
|
||||
#include "base/fs.h"
|
||||
@ -41,6 +42,7 @@
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tileset_io.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "doc/user_data_io.h"
|
||||
#include "fixmath/fixmath.h"
|
||||
|
||||
#include <fstream>
|
||||
@ -411,52 +413,64 @@ private:
|
||||
type == ObjectType::LayerTilemap);
|
||||
|
||||
std::string name = read_string(s);
|
||||
std::unique_ptr<Layer> lay;
|
||||
|
||||
if (type == ObjectType::LayerImage ||
|
||||
type == ObjectType::LayerTilemap) {
|
||||
std::unique_ptr<LayerImage> lay;
|
||||
switch (type) {
|
||||
|
||||
switch (type) {
|
||||
case ObjectType::LayerImage:
|
||||
lay.reset(new LayerImage(m_sprite));
|
||||
break;
|
||||
case ObjectType::LayerTilemap: {
|
||||
tileset_index tilesetIndex = read32(s);
|
||||
lay.reset(new LayerTilemap(m_sprite, tilesetIndex));
|
||||
break;
|
||||
case ObjectType::LayerImage:
|
||||
case ObjectType::LayerTilemap: {
|
||||
std::unique_ptr<LayerImage> lay;
|
||||
|
||||
switch (type) {
|
||||
case ObjectType::LayerImage:
|
||||
lay.reset(new LayerImage(m_sprite));
|
||||
break;
|
||||
case ObjectType::LayerTilemap: {
|
||||
tileset_index tilesetIndex = read32(s);
|
||||
lay.reset(new LayerTilemap(m_sprite, tilesetIndex));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lay->setName(name);
|
||||
lay->setFlags(flags);
|
||||
|
||||
// Blend mode & opacity
|
||||
lay->setBlendMode((BlendMode)read16(s));
|
||||
lay->setOpacity(read8(s));
|
||||
|
||||
// Cels
|
||||
int ncels = read32(s);
|
||||
for (int i=0; i<ncels; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
// Add a new cel to load in the future after we load all layers
|
||||
ObjectId celId = read32(s);
|
||||
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lay->setName(name);
|
||||
lay->setFlags(flags);
|
||||
case ObjectType::LayerGroup:
|
||||
lay.reset(new LayerGroup(m_sprite));
|
||||
lay->setName(name);
|
||||
lay->setFlags(flags);
|
||||
break;
|
||||
|
||||
// Blend mode & opacity
|
||||
lay->setBlendMode((BlendMode)read16(s));
|
||||
lay->setOpacity(read8(s));
|
||||
default:
|
||||
Console().printf("Unable to load layer named '%s', type #%d\n",
|
||||
name.c_str(), (int)type);
|
||||
break;
|
||||
}
|
||||
|
||||
// Cels
|
||||
int ncels = read32(s);
|
||||
for (int i=0; i<ncels; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
// Add a new cel to load in the future after we load all layers
|
||||
ObjectId celId = read32(s);
|
||||
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
|
||||
}
|
||||
if (lay) {
|
||||
UserData userData = read_user_data(s);
|
||||
lay->setUserData(userData);
|
||||
return lay.release();
|
||||
}
|
||||
else if (type == ObjectType::LayerGroup) {
|
||||
std::unique_ptr<LayerGroup> lay(new LayerGroup(m_sprite));
|
||||
lay->setName(name);
|
||||
lay->setFlags(flags);
|
||||
return lay.release();
|
||||
}
|
||||
else {
|
||||
Console().printf("Unable to load layer named '%s', type #%d\n",
|
||||
name.c_str(), (int)type);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Cel* readCel(std::ifstream& s) {
|
||||
@ -559,8 +573,8 @@ Doc* read_document_with_raw_images(const std::string& dir,
|
||||
info.height = 256;
|
||||
info.filename = "Unknown";
|
||||
}
|
||||
info.width = MID(1, info.width, 99999);
|
||||
info.height = MID(1, info.height, 99999);
|
||||
info.width = base::clamp(info.width, 1, 99999);
|
||||
info.height = base::clamp(info.height, 1, 99999);
|
||||
Sprite* spr = new Sprite(ImageSpec(info.mode, info.width, info.height), 256);
|
||||
|
||||
// Load each image as a new frame
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tileset_io.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "doc/user_data_io.h"
|
||||
#include "fixmath/fixmath.h"
|
||||
|
||||
#include <fstream>
|
||||
@ -262,6 +263,9 @@ private:
|
||||
// writeSprite/writeAllLayersID() functions)
|
||||
break;
|
||||
}
|
||||
|
||||
// Save user data
|
||||
write_user_data(s, lay->userData());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -311,9 +311,9 @@ void Doc::generateMaskBoundaries(const Mask* mask)
|
||||
ASSERT(mask);
|
||||
|
||||
if (!mask->isEmpty()) {
|
||||
m_maskBoundaries.reset(new MaskBoundaries(mask->bitmap()));
|
||||
m_maskBoundaries->offset(mask->bounds().x,
|
||||
mask->bounds().y);
|
||||
m_maskBoundaries.regen(mask->bitmap());
|
||||
m_maskBoundaries.offset(mask->bounds().x,
|
||||
mask->bounds().y);
|
||||
}
|
||||
|
||||
notifySelectionBoundariesChanged();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -20,6 +20,7 @@
|
||||
#include "doc/color.h"
|
||||
#include "doc/document.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/mask_boundaries.h"
|
||||
#include "doc/pixel_format.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "obs/observable.h"
|
||||
@ -32,7 +33,6 @@ namespace doc {
|
||||
class Cel;
|
||||
class Layer;
|
||||
class Mask;
|
||||
class MaskBoundaries;
|
||||
class Sprite;
|
||||
class Tileset;
|
||||
}
|
||||
@ -145,8 +145,16 @@ namespace app {
|
||||
void destroyMaskBoundaries();
|
||||
void generateMaskBoundaries(const Mask* mask = nullptr);
|
||||
|
||||
const MaskBoundaries* getMaskBoundaries() const {
|
||||
return m_maskBoundaries.get();
|
||||
const MaskBoundaries& maskBoundaries() const {
|
||||
return m_maskBoundaries;
|
||||
}
|
||||
|
||||
MaskBoundaries& maskBoundaries() {
|
||||
return m_maskBoundaries;
|
||||
}
|
||||
|
||||
bool hasMaskBoundaries() const {
|
||||
return !m_maskBoundaries.isEmpty();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -219,7 +227,7 @@ namespace app {
|
||||
Transaction* m_transaction;
|
||||
|
||||
// Selected mask region boundaries
|
||||
std::unique_ptr<doc::MaskBoundaries> m_maskBoundaries;
|
||||
doc::MaskBoundaries m_maskBoundaries;
|
||||
|
||||
// Data to save the file in the same format that it was loaded
|
||||
FormatOptionsPtr m_format_options;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/snap_to_grid.h"
|
||||
#include "app/util/autocrop.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/fstream_path.h"
|
||||
@ -125,7 +126,7 @@ int DocExporter::Item::frames() const
|
||||
return selFrames->size();
|
||||
else if (tag) {
|
||||
int result = tag->toFrame() - tag->fromFrame() + 1;
|
||||
return MID(1, result, doc->sprite()->totalFrames());
|
||||
return base::clamp(result, 1, doc->sprite()->totalFrames());
|
||||
}
|
||||
else
|
||||
return doc->sprite()->totalFrames();
|
||||
@ -138,8 +139,8 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
|
||||
|
||||
doc::SelectedFrames frames;
|
||||
if (tag) {
|
||||
frames.insert(MID(0, tag->fromFrame(), doc->sprite()->lastFrame()),
|
||||
MID(0, tag->toFrame(), doc->sprite()->lastFrame()));
|
||||
frames.insert(base::clamp(tag->fromFrame(), 0, doc->sprite()->lastFrame()),
|
||||
base::clamp(tag->toFrame(), 0, doc->sprite()->lastFrame()));
|
||||
}
|
||||
else {
|
||||
frames.insert(0, doc->sprite()->lastFrame());
|
||||
@ -1182,7 +1183,8 @@ void DocExporter::renderTexture(Context* ctx,
|
||||
sample.sprite(),
|
||||
textureImage->pixelFormat(),
|
||||
render::Dithering(),
|
||||
nullptr) // TODO add a delegate to show progress
|
||||
nullptr, // toGray is not needed because the texture is Indexed or RGB
|
||||
nullptr) // TODO add a delegate to show progress
|
||||
.execute(ctx);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +11,7 @@
|
||||
|
||||
#include "app/doc_range.h"
|
||||
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "base/serialization.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
@ -93,6 +95,23 @@ void DocRange::selectLayers(const SelectedLayers& selLayers)
|
||||
m_selectedLayers.insert(layer);
|
||||
}
|
||||
|
||||
void DocRange::eraseAndAdjust(const Layer* layer)
|
||||
{
|
||||
if (!enabled())
|
||||
return;
|
||||
|
||||
if (m_selectingFromLayer)
|
||||
m_selectingFromLayer = candidate_if_layer_is_deleted(m_selectingFromLayer, layer);
|
||||
|
||||
SelectedLayers copy = m_selectedLayers;
|
||||
m_selectedLayers.clear();
|
||||
for (Layer* selectedLayer : copy) {
|
||||
Layer* layerToSelect = candidate_if_layer_is_deleted(selectedLayer, layer);
|
||||
if (layerToSelect)
|
||||
m_selectedLayers.insert(layerToSelect);
|
||||
}
|
||||
}
|
||||
|
||||
bool DocRange::contains(const Layer* layer) const
|
||||
{
|
||||
if (enabled())
|
||||
@ -239,4 +258,42 @@ void DocRange::selectFrameRange(frame_t fromFrame, frame_t toFrame)
|
||||
m_selectedFrames.insert(fromFrame, toFrame);
|
||||
}
|
||||
|
||||
void DocRange::setType(const Type type)
|
||||
{
|
||||
if (type != kNone) {
|
||||
m_type = type;
|
||||
m_flags |= type;
|
||||
}
|
||||
else {
|
||||
m_type = kNone;
|
||||
m_flags = kNone;
|
||||
}
|
||||
}
|
||||
|
||||
void DocRange::setSelectedLayers(const SelectedLayers& layers)
|
||||
{
|
||||
if (layers.empty()) {
|
||||
m_type = kNone;
|
||||
m_selectedLayers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_type = kLayers;
|
||||
m_flags |= kLayers;
|
||||
m_selectedLayers = layers;
|
||||
}
|
||||
|
||||
void DocRange::setSelectedFrames(const SelectedFrames& frames)
|
||||
{
|
||||
if (frames.empty()) {
|
||||
m_type = kNone;
|
||||
m_selectedFrames.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_type = kFrames;
|
||||
m_flags |= kFrames;
|
||||
m_selectedFrames = frames;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -31,6 +32,8 @@ namespace app {
|
||||
|
||||
DocRange();
|
||||
DocRange(Cel* cel);
|
||||
DocRange(const DocRange&) = default;
|
||||
DocRange& operator=(const DocRange&) = default;
|
||||
|
||||
Type type() const { return m_type; }
|
||||
bool enabled() const { return m_type != kNone; }
|
||||
@ -39,6 +42,10 @@ namespace app {
|
||||
const SelectedLayers& selectedLayers() const { return m_selectedLayers; }
|
||||
const SelectedFrames& selectedFrames() const { return m_selectedFrames; }
|
||||
|
||||
void setType(const Type type);
|
||||
void setSelectedLayers(const SelectedLayers& layers);
|
||||
void setSelectedFrames(const SelectedFrames& frames);
|
||||
|
||||
void displace(layer_t layerDelta, frame_t frameDelta);
|
||||
|
||||
bool contains(const Layer* layer) const;
|
||||
@ -48,6 +55,12 @@ namespace app {
|
||||
bool contains(const Layer* layer,
|
||||
const frame_t frame) const;
|
||||
|
||||
// If the range includes the given layer, it will be erased from
|
||||
// the selection and other candidates might be selected (e.g. a
|
||||
// sibling, parent, etc.) using the
|
||||
// candidate_if_layer_is_deleted() function.
|
||||
void eraseAndAdjust(const Layer* layer);
|
||||
|
||||
void clearRange();
|
||||
void startRange(Layer* fromLayer, frame_t fromFrame, Type type);
|
||||
void endRange(Layer* toLayer, frame_t toFrame);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,16 +11,27 @@
|
||||
|
||||
#include "app/extensions.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/console.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_content.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/fstream_path.h"
|
||||
#include "render/dithering_matrix.h"
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#endif
|
||||
|
||||
#include "archive.h"
|
||||
#include "archive_entry.h"
|
||||
#include "json11.hpp"
|
||||
@ -35,6 +47,7 @@ namespace {
|
||||
|
||||
const char* kPackageJson = "package.json";
|
||||
const char* kInfoJson = "__info.json";
|
||||
const char* kPrefLua = "__pref.lua";
|
||||
const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme";
|
||||
|
||||
class ReadArchive {
|
||||
@ -181,19 +194,27 @@ void write_json_file(const std::string& path, const json11::Json& json)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Extension
|
||||
|
||||
Extension::DitheringMatrixInfo::DitheringMatrixInfo()
|
||||
{
|
||||
if (!m_matrix) {
|
||||
m_matrix = new render::DitheringMatrix;
|
||||
load_dithering_matrix_from_sprite(m_path, *m_matrix);
|
||||
}
|
||||
return *m_matrix;
|
||||
}
|
||||
|
||||
void Extension::DitheringMatrixInfo::destroyMatrix()
|
||||
Extension::DitheringMatrixInfo::DitheringMatrixInfo(const std::string& path,
|
||||
const std::string& name)
|
||||
: m_path(path)
|
||||
, m_name(name)
|
||||
{
|
||||
if (m_matrix)
|
||||
delete m_matrix;
|
||||
}
|
||||
|
||||
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
|
||||
{
|
||||
if (!m_loaded) {
|
||||
m_loaded = true;
|
||||
load_dithering_matrix_from_sprite(m_path, m_matrix);
|
||||
}
|
||||
return m_matrix;
|
||||
}
|
||||
|
||||
Extension::Extension(const std::string& path,
|
||||
@ -206,32 +227,52 @@ Extension::Extension(const std::string& path,
|
||||
, m_name(name)
|
||||
, m_version(version)
|
||||
, m_displayName(displayName)
|
||||
, m_category(Category::None)
|
||||
, m_isEnabled(isEnabled)
|
||||
, m_isInstalled(true)
|
||||
, m_isBuiltinExtension(isBuiltinExtension)
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
m_plugin.pluginRef = LUA_REFNIL;
|
||||
#endif
|
||||
}
|
||||
|
||||
Extension::~Extension()
|
||||
{
|
||||
// Delete all matrices
|
||||
for (auto& it : m_ditheringMatrices)
|
||||
it.second.destroyMatrix();
|
||||
}
|
||||
|
||||
void Extension::executeInitActions()
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (isEnabled() && hasScripts())
|
||||
initScripts();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Extension::executeExitActions()
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (isEnabled() && hasScripts())
|
||||
exitScripts();
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
void Extension::addLanguage(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_languages[id] = path;
|
||||
updateCategory(Category::Languages);
|
||||
}
|
||||
|
||||
void Extension::addTheme(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_themes[id] = path;
|
||||
updateCategory(Category::Themes);
|
||||
}
|
||||
|
||||
void Extension::addPalette(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_palettes[id] = path;
|
||||
updateCategory(Category::Palettes);
|
||||
}
|
||||
|
||||
void Extension::addDitheringMatrix(const std::string& id,
|
||||
@ -239,9 +280,35 @@ void Extension::addDitheringMatrix(const std::string& id,
|
||||
const std::string& name)
|
||||
{
|
||||
DitheringMatrixInfo info(path, name);
|
||||
m_ditheringMatrices[id] = info;
|
||||
m_ditheringMatrices[id] = std::move(info);
|
||||
updateCategory(Category::DitheringMatrices);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
void Extension::addCommand(const std::string& id)
|
||||
{
|
||||
PluginItem item;
|
||||
item.type = PluginItem::Command;
|
||||
item.id = id;
|
||||
m_plugin.items.push_back(item);
|
||||
}
|
||||
|
||||
void Extension::removeCommand(const std::string& id)
|
||||
{
|
||||
for (auto it=m_plugin.items.begin(); it != m_plugin.items.end(); ) {
|
||||
if (it->type == PluginItem::Command &&
|
||||
it->id == id) {
|
||||
it = m_plugin.items.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool Extension::canBeDisabled() const
|
||||
{
|
||||
return (m_isEnabled &&
|
||||
@ -267,6 +334,17 @@ void Extension::enable(const bool state)
|
||||
flush_config_file();
|
||||
|
||||
m_isEnabled = state;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (hasScripts()) {
|
||||
if (m_isEnabled) {
|
||||
initScripts();
|
||||
}
|
||||
else {
|
||||
exitScripts();
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
void Extension::uninstall()
|
||||
@ -314,6 +392,13 @@ void Extension::uninstallFiles(const std::string& path)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete __pref.lua file
|
||||
{
|
||||
std::string fn = base::join_path(path, kPrefLua);
|
||||
if (base::is_file(fn))
|
||||
base::delete_file(fn);
|
||||
}
|
||||
|
||||
std::sort(installedDirs.begin(),
|
||||
installedDirs.end(),
|
||||
[](const std::string& a,
|
||||
@ -369,6 +454,244 @@ bool Extension::isDefaultTheme() const
|
||||
return (name() == kAsepriteDefaultThemeExtensionName);
|
||||
}
|
||||
|
||||
void Extension::updateCategory(const Category newCategory)
|
||||
{
|
||||
if (m_category == Category::None)
|
||||
m_category = newCategory;
|
||||
else if (m_category != newCategory)
|
||||
m_category = Category::Multiple;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
// TODO move this to app/script/tableutils.h
|
||||
static void serialize_table(lua_State* L, int idx, std::string& result)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
result.push_back('{');
|
||||
|
||||
idx = lua_absindex(L, idx);
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, idx) != 0) {
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
result.push_back(',');
|
||||
}
|
||||
|
||||
// Save key
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
if (const char* k = lua_tostring(L, -2)) {
|
||||
result += k;
|
||||
result.push_back('=');
|
||||
}
|
||||
}
|
||||
|
||||
// Save value
|
||||
switch (lua_type(L, -1)) {
|
||||
case LUA_TNIL:
|
||||
default:
|
||||
result += "nil";
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
if (lua_toboolean(L, -1))
|
||||
result += "true";
|
||||
else
|
||||
result += "false";
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
result += lua_tostring(L, -1);
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
result.push_back('\"');
|
||||
if (const char* p = lua_tostring(L, -1)) {
|
||||
for (; *p; ++p) {
|
||||
switch (*p) {
|
||||
case '\"':
|
||||
result.push_back('\\');
|
||||
result.push_back('\"');
|
||||
break;
|
||||
case '\\':
|
||||
result.push_back('\\');
|
||||
result.push_back('\\');
|
||||
break;
|
||||
case '\t':
|
||||
result.push_back('\\');
|
||||
result.push_back('t');
|
||||
break;
|
||||
case '\r':
|
||||
result.push_back('\\');
|
||||
result.push_back('n');
|
||||
break;
|
||||
case '\n':
|
||||
result.push_back('\\');
|
||||
result.push_back('n');
|
||||
break;
|
||||
default:
|
||||
result.push_back(*p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back('\"');
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
serialize_table(L, -1, result);
|
||||
break;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
result.push_back('}');
|
||||
}
|
||||
|
||||
Extension::ScriptItem::ScriptItem(const std::string& fn)
|
||||
: fn(fn)
|
||||
, exitFunctionRef(LUA_REFNIL)
|
||||
{
|
||||
}
|
||||
|
||||
void Extension::initScripts()
|
||||
{
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
// Put a new "plugin" object for init()/exit() functions
|
||||
script::push_plugin(L, this);
|
||||
m_plugin.pluginRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Read plugin.preferences value
|
||||
{
|
||||
std::string fn = base::join_path(m_path, kPrefLua);
|
||||
if (base::is_file(fn)) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
if (luaL_loadfile(L, fn.c_str()) == LUA_OK) {
|
||||
if (lua_pcall(L, 0, 1, 0) == LUA_OK) {
|
||||
lua_setfield(L, -2, "preferences");
|
||||
}
|
||||
else {
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s) {
|
||||
Console().printf("%s\n", s);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& script : m_plugin.scripts) {
|
||||
// Reset global init()/exit() functions
|
||||
engine->evalCode("init=nil exit=nil");
|
||||
|
||||
// Eval the code of the script (it should define an init() and an exit() function)
|
||||
engine->evalFile(script.fn);
|
||||
|
||||
if (lua_getglobal(L, "exit") == LUA_TFUNCTION) {
|
||||
// Save a reference to the exit() function of this script
|
||||
script.exitFunctionRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Call the init() function of thi sscript with a Plugin object as first parameter
|
||||
if (lua_getglobal(L, "init") == LUA_TFUNCTION) {
|
||||
// Call init(plugin)
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_pcall(L, 1, 1, 0);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Extension::exitScripts()
|
||||
{
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
// Call the exit() function of each script
|
||||
for (auto& script : m_plugin.scripts) {
|
||||
if (script.exitFunctionRef != LUA_REFNIL) {
|
||||
// Get the exit() function, the "plugin" object, and call exit(plugin)
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_pcall(L, 1, 0, 0);
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
|
||||
script.exitFunctionRef = LUA_REFNIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the plugin preferences object
|
||||
if (m_plugin.pluginRef != LUA_REFNIL) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_getfield(L, -1, "preferences");
|
||||
|
||||
lua_pushnil(L); // Push a nil key, to ask for the first element of the table
|
||||
bool hasPreferences = (lua_next(L, -2) != 0);
|
||||
if (hasPreferences)
|
||||
lua_pop(L, 2); // Remove the value and the key
|
||||
|
||||
if (hasPreferences) {
|
||||
std::string result = "return ";
|
||||
serialize_table(L, -1, result);
|
||||
base::write_file_content(
|
||||
base::join_path(m_path, kPrefLua),
|
||||
(const uint8_t*)result.c_str(), result.size());
|
||||
}
|
||||
lua_pop(L, 2); // Pop preferences table and plugin
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
m_plugin.pluginRef = LUA_REFNIL;
|
||||
}
|
||||
|
||||
// Remove plugin items automatically
|
||||
for (const auto& item : m_plugin.items) {
|
||||
switch (item.type) {
|
||||
case PluginItem::Command: {
|
||||
auto cmds = Commands::instance();
|
||||
auto cmd = cmds->byId(item.id.c_str());
|
||||
ASSERT(cmd);
|
||||
|
||||
if (cmd) {
|
||||
#ifdef ENABLE_UI
|
||||
// TODO use a signal
|
||||
AppMenus::instance()->removeMenuItemFromGroup(cmd);
|
||||
#endif // ENABLE_UI
|
||||
|
||||
cmds->remove(cmd);
|
||||
|
||||
// This will call ~PluginCommand() and unref the command
|
||||
// onclick callback.
|
||||
delete cmd;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_plugin.items.clear();
|
||||
}
|
||||
|
||||
void Extension::addScript(const std::string& fn)
|
||||
{
|
||||
m_plugin.scripts.push_back(ScriptItem(fn));
|
||||
updateCategory(Category::Scripts);
|
||||
}
|
||||
|
||||
#endif // ENABLE_SCRIPTING
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Extensions
|
||||
|
||||
Extensions::Extensions()
|
||||
{
|
||||
// Create and get the user extensions directory
|
||||
@ -429,6 +752,22 @@ Extensions::~Extensions()
|
||||
delete ext;
|
||||
}
|
||||
|
||||
void Extensions::executeInitActions()
|
||||
{
|
||||
for (auto& ext : m_extensions)
|
||||
ext->executeInitActions();
|
||||
|
||||
ScriptsChange(nullptr);
|
||||
}
|
||||
|
||||
void Extensions::executeExitActions()
|
||||
{
|
||||
for (auto& ext : m_extensions)
|
||||
ext->executeExitActions();
|
||||
|
||||
ScriptsChange(nullptr);
|
||||
}
|
||||
|
||||
std::string Extensions::languagePath(const std::string& langId)
|
||||
{
|
||||
for (auto ext : m_extensions) {
|
||||
@ -540,7 +879,15 @@ ExtensionInfo Extensions::getCompressedExtensionInfo(const std::string& zipFn)
|
||||
ReadArchive in(zipFn);
|
||||
archive_entry* entry;
|
||||
while ((entry = in.readEntry()) != nullptr) {
|
||||
const std::string entryFn = archive_entry_pathname(entry);
|
||||
const char* entryFnPtr = archive_entry_pathname(entry);
|
||||
|
||||
// This can happen, e.g. if the file contains "!" + unicode chars
|
||||
// (maybe a bug in archive library?)
|
||||
// TODO try to find the real cause of this on libarchive
|
||||
if (!entryFnPtr)
|
||||
continue;
|
||||
|
||||
const std::string entryFn = entryFnPtr;
|
||||
if (base::get_file_name(entryFn) != kPackageJson)
|
||||
continue;
|
||||
|
||||
@ -577,8 +924,12 @@ Extension* Extensions::installCompressedExtension(const std::string& zipFn,
|
||||
|
||||
archive_entry* entry;
|
||||
while ((entry = in.readEntry()) != nullptr) {
|
||||
const char* entryFnPtr = archive_entry_pathname(entry);
|
||||
if (!entryFnPtr)
|
||||
continue;
|
||||
|
||||
// Fix the entry filename to write the file in the disk
|
||||
std::string fn = archive_entry_pathname(entry);
|
||||
std::string fn = entryFnPtr;
|
||||
|
||||
LOG("EXT: Original filename in zip <%s>...\n", fn.c_str());
|
||||
|
||||
@ -739,6 +1090,37 @@ Extension* Extensions::loadExtension(const std::string& path,
|
||||
extension->addDitheringMatrix(matId, matPath, matName);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Scripts
|
||||
auto scripts = contributes["scripts"];
|
||||
if (scripts.is_array()) {
|
||||
for (const auto& script : scripts.array_items()) {
|
||||
std::string scriptPath = script["path"].string_value();
|
||||
if (scriptPath.empty())
|
||||
continue;
|
||||
|
||||
// The path must be always relative to the extension
|
||||
scriptPath = base::join_path(path, scriptPath);
|
||||
|
||||
LOG("EXT: New script '%s'\n", scriptPath.c_str());
|
||||
|
||||
extension->addScript(scriptPath);
|
||||
}
|
||||
}
|
||||
// Simple version of packages.json with {... "scripts": "file.lua" ...}
|
||||
else if (scripts.is_string() &&
|
||||
!scripts.string_value().empty()) {
|
||||
std::string scriptPath = scripts.string_value();
|
||||
|
||||
// The path must be always relative to the extension
|
||||
scriptPath = base::join_path(path, scriptPath);
|
||||
|
||||
LOG("EXT: New script '%s'\n", scriptPath.c_str());
|
||||
|
||||
extension->addScript(scriptPath);
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
if (extension)
|
||||
@ -752,6 +1134,9 @@ void Extensions::generateExtensionSignals(Extension* extension)
|
||||
if (extension->hasThemes()) ThemesChange(extension);
|
||||
if (extension->hasPalettes()) PalettesChange(extension);
|
||||
if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (extension->hasScripts()) ScriptsChange(extension);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,15 +10,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "obs/signal.h"
|
||||
#include "render/dithering_matrix.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
class DitheringMatrix;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
// Key=theme/palette/etc. id
|
||||
@ -36,21 +34,31 @@ namespace app {
|
||||
class Extension {
|
||||
friend class Extensions;
|
||||
public:
|
||||
enum class Category {
|
||||
None,
|
||||
Languages,
|
||||
Themes,
|
||||
Scripts,
|
||||
Palettes,
|
||||
DitheringMatrices,
|
||||
Multiple,
|
||||
Max
|
||||
};
|
||||
|
||||
class DitheringMatrixInfo {
|
||||
public:
|
||||
DitheringMatrixInfo() : m_matrix(nullptr) { }
|
||||
DitheringMatrixInfo();
|
||||
DitheringMatrixInfo(const std::string& path,
|
||||
const std::string& name)
|
||||
: m_path(path), m_name(name), m_matrix(nullptr) { }
|
||||
const std::string& name);
|
||||
|
||||
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;
|
||||
mutable render::DitheringMatrix m_matrix;
|
||||
mutable bool m_loaded = false;
|
||||
};
|
||||
|
||||
Extension(const std::string& path,
|
||||
@ -61,10 +69,14 @@ namespace app {
|
||||
const bool isBuiltinExtension);
|
||||
~Extension();
|
||||
|
||||
void executeInitActions();
|
||||
void executeExitActions();
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::string& version() const { return m_version; }
|
||||
const std::string& displayName() const { return m_displayName; }
|
||||
const Category category() const { return m_category; }
|
||||
|
||||
const ExtensionItems& languages() const { return m_languages; }
|
||||
const ExtensionItems& themes() const { return m_themes; }
|
||||
@ -76,6 +88,10 @@ namespace app {
|
||||
void addDitheringMatrix(const std::string& id,
|
||||
const std::string& path,
|
||||
const std::string& name);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void addCommand(const std::string& id);
|
||||
void removeCommand(const std::string& id);
|
||||
#endif
|
||||
|
||||
bool isEnabled() const { return m_isEnabled; }
|
||||
bool isInstalled() const { return m_isInstalled; }
|
||||
@ -86,6 +102,10 @@ namespace app {
|
||||
bool hasThemes() const { return !m_themes.empty(); }
|
||||
bool hasPalettes() const { return !m_palettes.empty(); }
|
||||
bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); }
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
bool hasScripts() const { return !m_plugin.scripts.empty(); }
|
||||
void addScript(const std::string& fn);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void enable(const bool state);
|
||||
@ -93,15 +113,40 @@ namespace app {
|
||||
void uninstallFiles(const std::string& path);
|
||||
bool isCurrentTheme() const;
|
||||
bool isDefaultTheme() const;
|
||||
void updateCategory(const Category newCategory);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void initScripts();
|
||||
void exitScripts();
|
||||
#endif
|
||||
|
||||
ExtensionItems m_languages;
|
||||
ExtensionItems m_themes;
|
||||
ExtensionItems m_palettes;
|
||||
std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
struct ScriptItem {
|
||||
std::string fn;
|
||||
int exitFunctionRef;
|
||||
ScriptItem(const std::string& fn);
|
||||
};
|
||||
struct PluginItem {
|
||||
enum Type { Command };
|
||||
Type type;
|
||||
std::string id;
|
||||
};
|
||||
struct Plugin {
|
||||
int pluginRef;
|
||||
std::vector<ScriptItem> scripts;
|
||||
std::vector<PluginItem> items;
|
||||
} m_plugin;
|
||||
#endif
|
||||
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::string m_version;
|
||||
std::string m_displayName;
|
||||
Category m_category;
|
||||
bool m_isEnabled;
|
||||
bool m_isInstalled;
|
||||
bool m_isBuiltinExtension;
|
||||
@ -115,6 +160,9 @@ namespace app {
|
||||
Extensions();
|
||||
~Extensions();
|
||||
|
||||
void executeInitActions();
|
||||
void executeExitActions();
|
||||
|
||||
iterator begin() { return m_extensions.begin(); }
|
||||
iterator end() { return m_extensions.end(); }
|
||||
|
||||
@ -136,6 +184,7 @@ namespace app {
|
||||
obs::signal<void(Extension*)> ThemesChange;
|
||||
obs::signal<void(Extension*)> PalettesChange;
|
||||
obs::signal<void(Extension*)> DitheringMatricesChange;
|
||||
obs::signal<void(Extension*)> ScriptsChange;
|
||||
|
||||
private:
|
||||
Extension* loadExtension(const std::string& path,
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/cfile.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/exception.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
@ -1089,8 +1090,8 @@ static void ase_file_write_tags_chunk(FILE* f,
|
||||
tag->toFrame() < fromFrame)
|
||||
continue;
|
||||
|
||||
frame_t from = MID(0, tag->fromFrame()-fromFrame, toFrame-fromFrame);
|
||||
frame_t to = MID(from, tag->toFrame()-fromFrame, toFrame-fromFrame);
|
||||
frame_t from = base::clamp(tag->fromFrame()-fromFrame, 0, toFrame-fromFrame);
|
||||
frame_t to = base::clamp(tag->toFrame()-fromFrame, from, toFrame-fromFrame);
|
||||
|
||||
fputw(from, f);
|
||||
fputw(to, f);
|
||||
|
@ -95,8 +95,6 @@ Doc* load_document(Context* context, const std::string& filename)
|
||||
}
|
||||
|
||||
Doc* document = fop->releaseDocument();
|
||||
fop.release();
|
||||
|
||||
if (document && context)
|
||||
document->setContext(context);
|
||||
|
||||
@ -168,8 +166,8 @@ FileOpROI::FileOpROI(const Doc* doc,
|
||||
m_selFrames.displace(m_tag->fromFrame());
|
||||
|
||||
m_selFrames =
|
||||
m_selFrames.filter(MAX(0, m_tag->fromFrame()),
|
||||
MIN(m_tag->toFrame(), doc->sprite()->lastFrame()));
|
||||
m_selFrames.filter(std::max(0, m_tag->fromFrame()),
|
||||
std::min(m_tag->toFrame(), doc->sprite()->lastFrame()));
|
||||
}
|
||||
// All frames if selected frames is empty
|
||||
else if (m_selFrames.empty())
|
||||
@ -838,7 +836,7 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||
setError(
|
||||
fmt::format("Save operation is not supported in trial version.\n"
|
||||
"Go to {} and get the full-version.",
|
||||
get_app_download_url()));
|
||||
get_app_download_url()).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1007,7 +1005,9 @@ void FileOp::postLoad()
|
||||
|
||||
void FileOp::setLoadedFormatOptions(const FormatOptionsPtr& opts)
|
||||
{
|
||||
ASSERT(!m_formatOptions);
|
||||
// This assert can fail when we load a sequence of files.
|
||||
// TODO what we should do, keep the first or the latest format options?
|
||||
//ASSERT(!m_formatOptions);
|
||||
m_formatOptions = opts;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -158,6 +158,10 @@ namespace app {
|
||||
return opts;
|
||||
}
|
||||
|
||||
bool hasFormatOptionsOfDocument() const {
|
||||
return (m_document->formatOptions() != nullptr);
|
||||
}
|
||||
|
||||
// Options from the document when it was loaded. This function
|
||||
// doesn't return nullptr.
|
||||
template<typename T>
|
||||
@ -187,6 +191,7 @@ namespace app {
|
||||
void sequenceGetAlpha(int index, int* a) const;
|
||||
Image* sequenceImage(PixelFormat pixelFormat, int w, int h);
|
||||
const Image* sequenceImage() const { return m_seq.image.get(); }
|
||||
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
||||
bool sequenceGetHasAlpha() const {
|
||||
return m_seq.has_alpha;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -20,6 +20,7 @@
|
||||
#include "flic/flic.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace app {
|
||||
@ -76,8 +77,13 @@ bool FliFormat::onLoad(FileOp* fop)
|
||||
}
|
||||
|
||||
// Size by frame
|
||||
int w = header.width;
|
||||
int h = header.height;
|
||||
const int w = header.width;
|
||||
const int h = header.height;
|
||||
ASSERT(w > 0 && h > 0); // The decoder cannot return invalid widht/height values
|
||||
if (w > 10000 || h > 10000) {
|
||||
fop->setError("Image size too big: %dx%d not suported\n", w, h);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a temporal bitmap
|
||||
ImageRef bmp(Image::create(IMAGE_INDEXED, w, h));
|
||||
@ -234,7 +240,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
|
||||
frame_t frame = *frame_it;
|
||||
const Palette* pal = sprite->palette(frame);
|
||||
int size = MIN(256, pal->size());
|
||||
int size = std::min(256, pal->size());
|
||||
|
||||
for (int c=0; c<size; c++) {
|
||||
color_t color = pal->getEntry(c);
|
||||
@ -250,7 +256,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
// time that it has in the sprite
|
||||
if (f < nframes) {
|
||||
int times = sprite->frameDuration(frame) / header.speed;
|
||||
times = MAX(1, times);
|
||||
times = std::max(1, times);
|
||||
for (int c=0; c<times; c++)
|
||||
encoder.writeFrame(fliFrame);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -33,6 +33,8 @@
|
||||
|
||||
#include "gif_options.xml.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <gif_lib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -52,7 +54,7 @@
|
||||
#define GIF_TRACE(...)
|
||||
|
||||
// GifBitSize can return 9 (it's a bug in giflib)
|
||||
#define GifBitSizeLimited(v) (MIN(GifBitSize(v), 8))
|
||||
#define GifBitSizeLimited(v) (std::min(GifBitSize(v), 8))
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -539,7 +541,7 @@ private:
|
||||
palette.reset(new Palette(*m_sprite->palette(m_frameNum-1)));
|
||||
palette->setFrame(m_frameNum);
|
||||
}
|
||||
resetRemap(MAX(ncolors, palette->size()));
|
||||
resetRemap(std::max(ncolors, palette->size()));
|
||||
|
||||
// Number of colors in the colormap that are part of the current
|
||||
// sprite palette.
|
||||
@ -588,7 +590,7 @@ private:
|
||||
|
||||
Palette oldPalette(*palette);
|
||||
palette->resize(base + missing + (needsExtraBgColor ? 1: 0));
|
||||
resetRemap(MAX(ncolors, palette->size()));
|
||||
resetRemap(std::max(ncolors, palette->size()));
|
||||
|
||||
for (int i=0; i<ncolors; ++i) {
|
||||
if (!usedEntries[i])
|
||||
@ -768,12 +770,13 @@ private:
|
||||
Image* oldImage = cel->image();
|
||||
ImageRef newImage(
|
||||
render::convert_pixel_format
|
||||
(oldImage, NULL, IMAGE_RGB,
|
||||
(oldImage, nullptr, IMAGE_RGB,
|
||||
render::Dithering(),
|
||||
nullptr,
|
||||
m_sprite->palette(cel->frame()),
|
||||
m_opaque,
|
||||
m_bgIndex));
|
||||
m_bgIndex,
|
||||
nullptr));
|
||||
|
||||
m_sprite->replaceImage(oldImage->id(), newImage);
|
||||
}
|
||||
@ -792,7 +795,7 @@ private:
|
||||
(m_previousImage.get(), NULL, IMAGE_RGB,
|
||||
render::Dithering(),
|
||||
nullptr,
|
||||
m_sprite->palette(MAX(0, m_frameNum-1)),
|
||||
m_sprite->palette(std::max(0, m_frameNum-1)),
|
||||
m_opaque,
|
||||
m_bgIndex));
|
||||
|
||||
@ -906,7 +909,7 @@ public:
|
||||
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
||||
for (Palette* palette : m_sprite->getPalettes()) {
|
||||
int bpp = GifBitSizeLimited(palette->size());
|
||||
m_bitsPerPixel = MAX(m_bitsPerPixel, bpp);
|
||||
m_bitsPerPixel = std::max(m_bitsPerPixel, bpp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1092,9 +1095,9 @@ private:
|
||||
// 1/4 of its duration for some strange reason in the Twitter
|
||||
// conversion from GIF to video.
|
||||
if (fixDuration)
|
||||
frameDelay = MAX(2, frameDelay/4);
|
||||
frameDelay = std::max(2, frameDelay/4);
|
||||
if (fix_last_frame_duration)
|
||||
frameDelay = MAX(2, frameDelay);
|
||||
frameDelay = std::max(2, frameDelay);
|
||||
|
||||
extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
|
||||
(transparentIndex >= 0 ? 1: 0));
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "app/find_widget.h"
|
||||
#include "app/load_widget.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/memory.h"
|
||||
#include "doc/doc.h"
|
||||
@ -356,7 +357,7 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||
JSAMPARRAY buffer;
|
||||
JDIMENSION buffer_height;
|
||||
const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
|
||||
const int qualityValue = (int)MID(0, 100.0f * jpeg_options->quality, 100);
|
||||
const int qualityValue = (int)base::clamp(100.0f * jpeg_options->quality, 0.f, 100.f);
|
||||
|
||||
int c;
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/file_formats_manager.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
#include "dio/detect_format.h"
|
||||
@ -149,7 +150,7 @@ bool save_palette(const char* filename, const Palette* pal, int columns)
|
||||
if (!ff || !ff->support(FILE_SUPPORT_SAVE))
|
||||
break;
|
||||
|
||||
int w = (columns > 0 ? MID(0, columns, pal->size()): pal->size());
|
||||
int w = (columns > 0 ? base::clamp(columns, 0, pal->size()): pal->size());
|
||||
int h = (pal->size() / w) + (pal->size() % w > 0 ? 1: 0);
|
||||
|
||||
Context tmpContext;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -16,6 +16,7 @@
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/file/png_format.h"
|
||||
#include "app/file/png_options.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "doc/doc.h"
|
||||
#include "gfx/color_space.h"
|
||||
@ -613,7 +614,7 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
int c, r, g, b;
|
||||
int pal_size = fop->sequenceGetNColors();
|
||||
pal_size = MID(1, pal_size, PNG_MAX_PALETTE_LENGTH);
|
||||
pal_size = base::clamp(pal_size, 1, PNG_MAX_PALETTE_LENGTH);
|
||||
|
||||
#if PNG_MAX_PALETTE_LENGTH != 256
|
||||
#error PNG_MAX_PALETTE_LENGTH should be 256
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -17,6 +17,7 @@
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/cfile.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "doc/doc.h"
|
||||
#include "ui/window.h"
|
||||
@ -81,7 +82,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
||||
const Image* image = fop->sequenceImage();
|
||||
int x, y, c, r, g, b, a, alpha;
|
||||
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
||||
const int pixelScaleValue = MID(0, svg_options->pixelScale, 10000);
|
||||
const int pixelScaleValue = base::clamp(svg_options->pixelScale, 0, 10000);
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
FILE* f = handle.get();
|
||||
auto printcol = [f](int x, int y,int r, int g, int b, int a, int pxScale) {
|
||||
|
@ -1,24 +1,30 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
//
|
||||
// tga.c - Based on the code of Tim Gunn, Michal Mertl, Salvador
|
||||
// Eduardo Tropea and Peter Wang.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/file/tga_options.h"
|
||||
#include "base/cfile.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "doc/doc.h"
|
||||
#include "doc/image_bits.h"
|
||||
#include "tga/tga.h"
|
||||
#include "ui/combobox.h"
|
||||
#include "ui/listitem.h"
|
||||
|
||||
#include "tga_options.xml.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -45,13 +51,16 @@ class TgaFormat : public FileFormat {
|
||||
FILE_SUPPORT_RGBA |
|
||||
FILE_SUPPORT_GRAY |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES;
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
#ifdef ENABLE_SAVE
|
||||
bool onSave(FileOp* fop) override;
|
||||
#endif
|
||||
|
||||
FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
|
||||
};
|
||||
|
||||
FileFormat* CreateTgaFormat()
|
||||
@ -59,344 +68,139 @@ FileFormat* CreateTgaFormat()
|
||||
return new TgaFormat;
|
||||
}
|
||||
|
||||
static void rle_tga_read8(FILE* f, uint8_t* address, int w, int type)
|
||||
{
|
||||
uint8_t* end = address + (w * (type == 1 ? 1: 2));
|
||||
uint8_t value;
|
||||
int count, g;
|
||||
int c = 0;
|
||||
namespace {
|
||||
|
||||
do {
|
||||
count = fgetc(f);
|
||||
if (count & 0x80) {
|
||||
count = (count & 0x7F) + 1;
|
||||
c += count;
|
||||
value = fgetc(f);
|
||||
while (count--) {
|
||||
if (type == 1) {
|
||||
if (address+1 < end)
|
||||
*(address++) = value;
|
||||
}
|
||||
else {
|
||||
if (address+2 < end)
|
||||
*((uint16_t*)address) = graya(value, 255);
|
||||
address += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
count++;
|
||||
c += count;
|
||||
if (type == 1) {
|
||||
if (address+count < end)
|
||||
fread(address, 1, count, f);
|
||||
address += count;
|
||||
}
|
||||
else {
|
||||
for (g=0; g<count; g++) {
|
||||
int c = fgetc(f);
|
||||
if (address+2 < end)
|
||||
*((uint16_t*)address) = graya(c, 255);
|
||||
address += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (c < w);
|
||||
class TgaDelegate : public tga::Delegate {
|
||||
public:
|
||||
TgaDelegate(FileOp* fop) : m_fop(fop) { }
|
||||
bool notifyProgress(double progress) override {
|
||||
m_fop->setProgress(progress);
|
||||
return !m_fop->isStop();
|
||||
}
|
||||
private:
|
||||
FileOp* m_fop;
|
||||
};
|
||||
|
||||
bool get_image_spec(const tga::Header& header, ImageSpec& spec)
|
||||
{
|
||||
switch (header.imageType) {
|
||||
|
||||
case tga::UncompressedIndexed:
|
||||
case tga::RleIndexed:
|
||||
if (header.bitsPerPixel != 8)
|
||||
return false;
|
||||
spec = ImageSpec(ColorMode::INDEXED,
|
||||
header.width,
|
||||
header.height);
|
||||
return true;
|
||||
|
||||
case tga::UncompressedRgb:
|
||||
case tga::RleRgb:
|
||||
if (header.bitsPerPixel != 15 &&
|
||||
header.bitsPerPixel != 16 &&
|
||||
header.bitsPerPixel != 24 &&
|
||||
header.bitsPerPixel != 32)
|
||||
return false;
|
||||
spec = ImageSpec(ColorMode::RGB,
|
||||
header.width,
|
||||
header.height);
|
||||
return true;
|
||||
|
||||
case tga::UncompressedGray:
|
||||
case tga::RleGray:
|
||||
if (header.bitsPerPixel != 8)
|
||||
return false;
|
||||
spec = ImageSpec(ColorMode::GRAYSCALE,
|
||||
header.width,
|
||||
header.height);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void rle_tga_read32(FILE* f, uint32_t* address, int w, bool withAlpha)
|
||||
{
|
||||
uint32_t* end = address + w;
|
||||
unsigned char value[4];
|
||||
int count;
|
||||
int c = 0;
|
||||
} // anonymous namespace
|
||||
|
||||
do {
|
||||
count = fgetc(f);
|
||||
if (count & 0x80) {
|
||||
count = (count & 0x7F) + 1;
|
||||
c += count;
|
||||
fread(value, 1, 4, f);
|
||||
while (count--) {
|
||||
if (address+1 < end)
|
||||
*(address++) = rgba(value[2], value[1], value[0],
|
||||
withAlpha ? value[3]: 255);
|
||||
}
|
||||
}
|
||||
else {
|
||||
count++;
|
||||
c += count;
|
||||
while (count--) {
|
||||
fread(value, 1, 4, f);
|
||||
if (address+1 < end)
|
||||
*(address++) = rgba(value[2], value[1], value[0],
|
||||
withAlpha ? value[3]: 255);
|
||||
}
|
||||
}
|
||||
} while (c < w);
|
||||
}
|
||||
|
||||
static void rle_tga_read24(FILE* f, uint32_t* address, int w)
|
||||
{
|
||||
uint32_t* end = address + w;
|
||||
unsigned char value[4];
|
||||
int count;
|
||||
int c = 0;
|
||||
|
||||
do {
|
||||
count = fgetc(f);
|
||||
if (count & 0x80) {
|
||||
count = (count & 0x7F) + 1;
|
||||
c += count;
|
||||
fread(value, 1, 3, f);
|
||||
while (count--) {
|
||||
if (address+1 < end)
|
||||
*(address++) = rgba(value[2], value[1], value[0], 255);
|
||||
}
|
||||
}
|
||||
else {
|
||||
count++;
|
||||
c += count;
|
||||
while (count--) {
|
||||
fread(value, 1, 3, f);
|
||||
if (address+1 < end)
|
||||
*(address++) = rgba(value[2], value[1], value[0], 255);
|
||||
}
|
||||
}
|
||||
} while (c < w);
|
||||
}
|
||||
|
||||
static void rle_tga_read16(uint32_t* address, int w, FILE *f)
|
||||
{
|
||||
uint32_t* end = address + w;
|
||||
unsigned int value;
|
||||
uint32_t color;
|
||||
int count;
|
||||
int c = 0;
|
||||
|
||||
do {
|
||||
count = fgetc(f);
|
||||
if (count & 0x80) {
|
||||
count = (count & 0x7F) + 1;
|
||||
c += count;
|
||||
value = fgetw(f);
|
||||
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
|
||||
scale_5bits_to_8bits((value >> 5) & 0x1F),
|
||||
scale_5bits_to_8bits(value & 0x1F), 255);
|
||||
|
||||
while (count--) {
|
||||
if (address+1 < end)
|
||||
*(address++) = color;
|
||||
}
|
||||
}
|
||||
else {
|
||||
count++;
|
||||
c += count;
|
||||
while (count--) {
|
||||
value = fgetw(f);
|
||||
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
|
||||
scale_5bits_to_8bits((value >> 5) & 0x1F),
|
||||
scale_5bits_to_8bits(value & 0x1F), 255);
|
||||
|
||||
if (address+1 < end)
|
||||
*(address++) = color;
|
||||
}
|
||||
}
|
||||
} while (c < w);
|
||||
}
|
||||
|
||||
// Loads a 256 color or 24 bit uncompressed TGA file, returning a bitmap
|
||||
// structure and storing the palette data in the specified palette (this
|
||||
// should be an array of at least 256 RGB structures).
|
||||
bool TgaFormat::onLoad(FileOp* fop)
|
||||
{
|
||||
unsigned char image_id[256], image_palette[256][3], rgb[4];
|
||||
unsigned char id_length, palette_type, image_type, palette_entry_size;
|
||||
unsigned char bpp, descriptor_bits;
|
||||
short unsigned int palette_colors;
|
||||
short unsigned int image_width, image_height;
|
||||
unsigned int c, i, x, y, yc;
|
||||
int compressed;
|
||||
|
||||
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
|
||||
FILE* f = handle.get();
|
||||
|
||||
id_length = fgetc(f);
|
||||
palette_type = fgetc(f);
|
||||
image_type = fgetc(f);
|
||||
fgetw(f); // first_color
|
||||
palette_colors = fgetw(f);
|
||||
palette_entry_size = fgetc(f);
|
||||
fgetw(f); // "left" field
|
||||
fgetw(f); // "top" field
|
||||
image_width = fgetw(f);
|
||||
image_height = fgetw(f);
|
||||
bpp = fgetc(f);
|
||||
descriptor_bits = fgetc(f);
|
||||
|
||||
fread(image_id, 1, id_length, f);
|
||||
|
||||
if (palette_type == 1) {
|
||||
for (i=0; i<palette_colors; i++) {
|
||||
switch (palette_entry_size) {
|
||||
|
||||
case 16:
|
||||
c = fgetw(f);
|
||||
image_palette[i][0] = scale_5bits_to_8bits(c & 0x1F);
|
||||
image_palette[i][1] = scale_5bits_to_8bits((c >> 5) & 0x1F);
|
||||
image_palette[i][2] = scale_5bits_to_8bits((c >> 10) & 0x1F);
|
||||
break;
|
||||
|
||||
case 24:
|
||||
case 32:
|
||||
image_palette[i][0] = fgetc(f);
|
||||
image_palette[i][1] = fgetc(f);
|
||||
image_palette[i][2] = fgetc(f);
|
||||
if (palette_entry_size == 32)
|
||||
fgetc(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (palette_type != 0) {
|
||||
tga::StdioFileInterface finterface(handle.get());
|
||||
tga::Decoder decoder(&finterface);
|
||||
tga::Header header;
|
||||
if (!decoder.readHeader(header)) {
|
||||
fop->setError("Invalid TGA header\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Image type:
|
||||
* 0 = no image data
|
||||
* 1 = uncompressed color mapped
|
||||
* 2 = uncompressed true color
|
||||
* 3 = grayscale
|
||||
* 9 = RLE color mapped
|
||||
* 10 = RLE true color
|
||||
* 11 = RLE grayscale
|
||||
*/
|
||||
compressed = (image_type & 8);
|
||||
image_type &= 7;
|
||||
|
||||
PixelFormat pixelFormat;
|
||||
bool withAlpha = false;
|
||||
|
||||
switch (image_type) {
|
||||
|
||||
/* paletted image */
|
||||
case 1:
|
||||
if ((palette_type != 1) || (bpp != 8)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i=0; i<palette_colors; i++) {
|
||||
fop->sequenceSetColor(i,
|
||||
image_palette[i][2],
|
||||
image_palette[i][1],
|
||||
image_palette[i][0]);
|
||||
}
|
||||
|
||||
pixelFormat = IMAGE_INDEXED;
|
||||
break;
|
||||
|
||||
/* truecolor image */
|
||||
case 2:
|
||||
if ((palette_type != 0) ||
|
||||
((bpp != 15) && (bpp != 16) &&
|
||||
(bpp != 24) && (bpp != 32))) {
|
||||
return false;
|
||||
}
|
||||
withAlpha = ((descriptor_bits & 0xf) == 8);
|
||||
if (withAlpha)
|
||||
fop->sequenceSetHasAlpha(true);
|
||||
pixelFormat = IMAGE_RGB;
|
||||
break;
|
||||
|
||||
/* grayscale image */
|
||||
case 3:
|
||||
if ((palette_type != 0) || (bpp != 8)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i=0; i<256; i++)
|
||||
fop->sequenceSetColor(i, i, i, i);
|
||||
|
||||
pixelFormat = IMAGE_GRAYSCALE;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* TODO add support for more TGA types? */
|
||||
return false;
|
||||
ImageSpec spec(ColorMode::RGB, 1, 1);
|
||||
if (!get_image_spec(header, spec)) {
|
||||
fop->setError("Unsupported color depth in TGA file: %d bpp, image type=%d.\n",
|
||||
header.bitsPerPixel,
|
||||
header.imageType);
|
||||
return false;
|
||||
}
|
||||
|
||||
Image* image = fop->sequenceImage(pixelFormat, image_width, image_height);
|
||||
// Palette from TGA file
|
||||
if (header.hasColormap()) {
|
||||
const tga::Colormap& pal = header.colormap;
|
||||
for (int i=0; i<pal.size(); ++i) {
|
||||
tga::color_t c = pal[i];
|
||||
fop->sequenceSetColor(i,
|
||||
tga::getr(c),
|
||||
tga::getg(c),
|
||||
tga::getb(c));
|
||||
if (tga::geta(c) < 255)
|
||||
fop->sequenceSetAlpha(i, tga::geta(c));
|
||||
}
|
||||
}
|
||||
// Generate grayscale palette
|
||||
else if (header.isGray()) {
|
||||
for (int i=0; i<256; ++i)
|
||||
fop->sequenceSetColor(i, i, i, i);
|
||||
}
|
||||
|
||||
if (decoder.hasAlpha())
|
||||
fop->sequenceSetHasAlpha(true);
|
||||
|
||||
Image* image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
|
||||
spec.width(),
|
||||
spec.height());
|
||||
if (!image)
|
||||
return false;
|
||||
|
||||
for (y=image_height; y; y--) {
|
||||
yc = (descriptor_bits & 0x20) ? image_height-y : y-1;
|
||||
tga::Image tgaImage;
|
||||
tgaImage.pixels = image->getPixelAddress(0, 0);
|
||||
tgaImage.rowstride = image->getRowStrideSize();
|
||||
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
||||
|
||||
switch (image_type) {
|
||||
// Read image
|
||||
TgaDelegate delegate(fop);
|
||||
if (!decoder.readImage(header, tgaImage, &delegate)) {
|
||||
fop->setError("Error loading image data from TGA file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
case 1:
|
||||
case 3:
|
||||
if (compressed)
|
||||
rle_tga_read8(f, image->getPixelAddress(0, yc), image_width, image_type);
|
||||
else if (image_type == 1)
|
||||
fread(image->getPixelAddress(0, yc), 1, image_width, f);
|
||||
else {
|
||||
for (x=0; x<image_width; x++)
|
||||
put_pixel_fast<GrayscaleTraits>(image, x, yc, graya(fgetc(f), 255));
|
||||
}
|
||||
break;
|
||||
// Fix alpha values for RGB images
|
||||
decoder.postProcessImage(header, tgaImage);
|
||||
|
||||
case 2:
|
||||
if (bpp == 32) {
|
||||
if (compressed) {
|
||||
rle_tga_read32(f, (uint32_t*)image->getPixelAddress(0, yc), image_width,
|
||||
withAlpha);
|
||||
}
|
||||
else {
|
||||
for (x=0; x<image_width; x++) {
|
||||
fread(rgb, 1, 4, f);
|
||||
put_pixel_fast<RgbTraits>(image, x, yc,
|
||||
rgba(rgb[2], rgb[1], rgb[0],
|
||||
withAlpha ? rgb[3]: 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (bpp == 24) {
|
||||
if (compressed) {
|
||||
rle_tga_read24(f, (uint32_t*)image->getPixelAddress(0, yc), image_width);
|
||||
}
|
||||
else {
|
||||
for (x=0; x<image_width; x++) {
|
||||
fread(rgb, 1, 3, f);
|
||||
put_pixel_fast<RgbTraits>(image, x, yc, rgba(rgb[2], rgb[1], rgb[0], 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (compressed) {
|
||||
rle_tga_read16((uint32_t*)image->getPixelAddress(0, yc), image_width, f);
|
||||
}
|
||||
else {
|
||||
for (x=0; x<image_width; x++) {
|
||||
c = fgetw(f);
|
||||
put_pixel_fast<RgbTraits>(
|
||||
image, x, yc, rgba(scale_5bits_to_8bits((c >> 10) & 0x1F),
|
||||
scale_5bits_to_8bits((c >> 5) & 0x1F),
|
||||
scale_5bits_to_8bits(c & 0x1F), 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (image_height > 1) {
|
||||
fop->setProgress((float)(image_height-y) / (float)(image_height));
|
||||
if (fop->isStop())
|
||||
break;
|
||||
// Post process gray image pixels (because we use grayscale images
|
||||
// with alpha).
|
||||
if (header.isGray()) {
|
||||
doc::LockImageBits<GrayscaleTraits> bits(image);
|
||||
for (auto it=bits.begin(), end=bits.end(); it != end; ++it) {
|
||||
*it = doc::graya(*it, 255);
|
||||
}
|
||||
}
|
||||
|
||||
if (ferror(f)) {
|
||||
if (decoder.hasAlpha())
|
||||
fop->sequenceSetHasAlpha(true);
|
||||
|
||||
// Set default options for this TGA
|
||||
auto opts = std::make_shared<TgaOptions>();
|
||||
opts->bitsPerPixel(header.bitsPerPixel);
|
||||
opts->compress(header.isRle());
|
||||
fop->setLoadedFormatOptions(opts);
|
||||
|
||||
if (ferror(handle.get())) {
|
||||
fop->setError("Error reading file.\n");
|
||||
return false;
|
||||
}
|
||||
@ -406,87 +210,110 @@ bool TgaFormat::onLoad(FileOp* fop)
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SAVE
|
||||
// Writes a bitmap into a TGA file, using the specified palette (this
|
||||
// should be an array of at least 256 RGB structures).
|
||||
|
||||
namespace {
|
||||
|
||||
void prepare_header(tga::Header& header,
|
||||
const doc::Image* image,
|
||||
const doc::Palette* palette,
|
||||
const bool isOpaque,
|
||||
const bool compressed,
|
||||
int bitsPerPixel)
|
||||
{
|
||||
header.idLength = 0;
|
||||
header.colormapType = 0;
|
||||
header.imageType = tga::NoImage;
|
||||
header.colormapOrigin = 0;
|
||||
header.colormapLength = 0;
|
||||
header.colormapDepth = 0;
|
||||
header.xOrigin = 0;
|
||||
header.yOrigin = 0;
|
||||
header.width = image->width();
|
||||
header.height = image->height();
|
||||
header.bitsPerPixel = 0;
|
||||
// TODO make this option configurable
|
||||
header.imageDescriptor = 0x20; // Top-to-bottom
|
||||
|
||||
switch (image->colorMode()) {
|
||||
case ColorMode::RGB:
|
||||
header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb);
|
||||
header.bitsPerPixel = (bitsPerPixel > 8 ?
|
||||
bitsPerPixel:
|
||||
(isOpaque ? 24: 32));
|
||||
if (!isOpaque) {
|
||||
switch (header.bitsPerPixel) {
|
||||
case 16: header.imageDescriptor |= 1; break;
|
||||
case 32: header.imageDescriptor |= 8; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ColorMode::GRAYSCALE:
|
||||
// TODO if the grayscale is not opaque, we should use RGB,
|
||||
// this could be done automatically in FileOp in a
|
||||
// generic way for all formats when FILE_SUPPORT_RGBA is
|
||||
// available and FILE_SUPPORT_GRAYA isn't.
|
||||
header.imageType = (compressed ? tga::RleGray: tga::UncompressedGray);
|
||||
header.bitsPerPixel = 8;
|
||||
break;
|
||||
case ColorMode::INDEXED:
|
||||
ASSERT(palette);
|
||||
|
||||
header.imageType = (compressed ? tga::RleIndexed: tga::UncompressedIndexed);
|
||||
header.bitsPerPixel = 8;
|
||||
header.colormapType = 1;
|
||||
header.colormapLength = palette->size();
|
||||
if (palette->hasAlpha())
|
||||
header.colormapDepth = 32;
|
||||
else
|
||||
header.colormapDepth = 24;
|
||||
|
||||
header.colormap = tga::Colormap(palette->size());
|
||||
for (int i=0; i<palette->size(); ++i) {
|
||||
doc::color_t c = palette->getEntry(i);
|
||||
header.colormap[i] =
|
||||
tga::rgba(doc::rgba_getr(c),
|
||||
doc::rgba_getg(c),
|
||||
doc::rgba_getb(c),
|
||||
doc::rgba_geta(c));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool TgaFormat::onSave(FileOp* fop)
|
||||
{
|
||||
const Image* image = fop->sequenceImage();
|
||||
unsigned char image_palette[256][3];
|
||||
int x, y, c, r, g, b;
|
||||
int depth = (image->pixelFormat() == IMAGE_RGB) ? 32 : 8;
|
||||
bool need_pal = (image->pixelFormat() == IMAGE_INDEXED)? true: false;
|
||||
const Palette* palette = fop->sequenceGetPalette();
|
||||
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
FILE* f = handle.get();
|
||||
tga::StdioFileInterface finterface(handle.get());
|
||||
tga::Encoder encoder(&finterface);
|
||||
tga::Header header;
|
||||
|
||||
fputc(0, f); /* id length (no id saved) */
|
||||
fputc((need_pal) ? 1 : 0, f); /* palette type */
|
||||
/* image type */
|
||||
fputc((image->pixelFormat() == IMAGE_RGB ) ? 2 :
|
||||
(image->pixelFormat() == IMAGE_GRAYSCALE) ? 3 :
|
||||
(image->pixelFormat() == IMAGE_INDEXED ) ? 1 : 0, f);
|
||||
fputw(0, f); /* first colour */
|
||||
fputw((need_pal) ? 256 : 0, f); /* number of colours */
|
||||
fputc((need_pal) ? 24 : 0, f); /* palette entry size */
|
||||
fputw(0, f); /* left */
|
||||
fputw(0, f); /* top */
|
||||
fputw(image->width(), f); /* width */
|
||||
fputw(image->height(), f); /* height */
|
||||
fputc(depth, f); /* bits per pixel */
|
||||
const auto tgaOptions = std::static_pointer_cast<TgaOptions>(fop->formatOptions());
|
||||
prepare_header(
|
||||
header, image, palette,
|
||||
// Is alpha channel required?
|
||||
fop->document()->sprite()->isOpaque(),
|
||||
// Compressed by default
|
||||
(tgaOptions ? tgaOptions->compress(): true),
|
||||
// Bits per pixel (0 means "calculate what is best")
|
||||
(tgaOptions ? tgaOptions->bitsPerPixel(): 0));
|
||||
|
||||
/* descriptor (bottom to top, 8-bit alpha) */
|
||||
fputc(image->pixelFormat() == IMAGE_RGB &&
|
||||
!fop->document()->sprite()->isOpaque() ? 8: 0, f);
|
||||
encoder.writeHeader(header);
|
||||
|
||||
if (need_pal) {
|
||||
for (y=0; y<256; y++) {
|
||||
fop->sequenceGetColor(y, &r, &g, &b);
|
||||
image_palette[y][2] = r;
|
||||
image_palette[y][1] = g;
|
||||
image_palette[y][0] = b;
|
||||
}
|
||||
fwrite(image_palette, 1, 768, f);
|
||||
}
|
||||
tga::Image tgaImage;
|
||||
tgaImage.pixels = image->getPixelAddress(0, 0);
|
||||
tgaImage.rowstride = image->getRowStrideSize();
|
||||
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
||||
|
||||
switch (image->pixelFormat()) {
|
||||
TgaDelegate delegate(fop);
|
||||
encoder.writeImage(header, tgaImage);
|
||||
encoder.writeFooter();
|
||||
|
||||
case IMAGE_RGB:
|
||||
for (y=image->height()-1; y>=0; y--) {
|
||||
for (x=0; x<image->width(); x++) {
|
||||
c = get_pixel(image, x, y);
|
||||
fputc(rgba_getb(c), f);
|
||||
fputc(rgba_getg(c), f);
|
||||
fputc(rgba_getr(c), f);
|
||||
fputc(rgba_geta(c), f);
|
||||
}
|
||||
|
||||
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
|
||||
case IMAGE_GRAYSCALE:
|
||||
for (y=image->height()-1; y>=0; y--) {
|
||||
for (x=0; x<image->width(); x++)
|
||||
fputc(graya_getv(get_pixel(image, x, y)), f);
|
||||
|
||||
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
|
||||
case IMAGE_INDEXED:
|
||||
for (y=image->height()-1; y>=0; y--) {
|
||||
for (x=0; x<image->width(); x++)
|
||||
fputc(get_pixel(image, x, y), f);
|
||||
|
||||
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const char* tga2_footer = "\0\0\0\0\0\0\0\0TRUEVISION-XFILE.\0";
|
||||
fwrite(tga2_footer, 1, 26, f);
|
||||
|
||||
if (ferror(f)) {
|
||||
if (ferror(handle.get())) {
|
||||
fop->setError("Error writing file.\n");
|
||||
return false;
|
||||
}
|
||||
@ -494,6 +321,79 @@ bool TgaFormat::onSave(FileOp* fop)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ENABLE_SAVE
|
||||
|
||||
FormatOptionsPtr TgaFormat::onAskUserForFormatOptions(FileOp* fop)
|
||||
{
|
||||
const bool origOpts = fop->hasFormatOptionsOfDocument();
|
||||
auto opts = fop->formatOptionsOfDocument<TgaOptions>();
|
||||
#ifdef ENABLE_UI
|
||||
if (fop->context() && fop->context()->isUIAvailable()) {
|
||||
try {
|
||||
auto& pref = Preferences::instance();
|
||||
|
||||
// If the TGA options are not original from a TGA file, we can
|
||||
// use the default options from the preferences.
|
||||
if (!origOpts) {
|
||||
if (pref.isSet(pref.tga.bitsPerPixel))
|
||||
opts->bitsPerPixel(pref.tga.bitsPerPixel());
|
||||
if (pref.isSet(pref.tga.compress))
|
||||
opts->compress(pref.tga.compress());
|
||||
}
|
||||
|
||||
if (pref.tga.showAlert()) {
|
||||
const bool isOpaque = fop->document()->sprite()->isOpaque();
|
||||
const std::string defBitsPerPixel = (isOpaque ? "24": "32");
|
||||
app::gen::TgaOptions win;
|
||||
|
||||
if (fop->document()->colorMode() == doc::ColorMode::RGB) {
|
||||
// TODO implement a better way to create ListItems with values
|
||||
auto newItem = [](const char* s) -> ui::ListItem* {
|
||||
auto item = new ui::ListItem(s);
|
||||
item->setValue(s);
|
||||
return item;
|
||||
};
|
||||
|
||||
win.bitsPerPixel()->addItem(newItem("16"));
|
||||
win.bitsPerPixel()->addItem(newItem("24"));
|
||||
win.bitsPerPixel()->addItem(newItem("32"));
|
||||
|
||||
std::string v = defBitsPerPixel;
|
||||
if (opts->bitsPerPixel() > 0)
|
||||
v = base::convert_to<std::string>(opts->bitsPerPixel());
|
||||
win.bitsPerPixel()->setValue(v);
|
||||
}
|
||||
else {
|
||||
win.bitsPerPixelLabel()->setVisible(false);
|
||||
win.bitsPerPixel()->setVisible(false);
|
||||
}
|
||||
win.compress()->setSelected(opts->compress());
|
||||
|
||||
win.openWindowInForeground();
|
||||
|
||||
if (win.closer() == win.ok()) {
|
||||
int bpp = base::convert_to<int>(win.bitsPerPixel()->getValue());
|
||||
|
||||
pref.tga.bitsPerPixel(bpp);
|
||||
pref.tga.compress(win.compress()->isSelected());
|
||||
pref.tga.showAlert(!win.dontShow()->isSelected());
|
||||
|
||||
opts->bitsPerPixel(pref.tga.bitsPerPixel());
|
||||
opts->compress(pref.tga.compress());
|
||||
}
|
||||
else {
|
||||
opts.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Console::showException(e);
|
||||
return std::shared_ptr<TgaOptions>(nullptr);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
return opts;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
30
src/app/file/tga_options.h
Normal file
30
src/app/file/tga_options.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_FILE_TGA_OPTIONS_H_INCLUDED
|
||||
#define APP_FILE_TGA_OPTIONS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/file/format_options.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class TgaOptions : public FormatOptions {
|
||||
public:
|
||||
int bitsPerPixel() const { return m_bitsPerPixel; }
|
||||
bool compress() const { return m_compress; }
|
||||
|
||||
void bitsPerPixel(int bpp) { m_bitsPerPixel = bpp; }
|
||||
void compress(bool state) { m_compress = state; }
|
||||
|
||||
private:
|
||||
int m_bitsPerPixel = 0;
|
||||
bool m_compress = true;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2015-2018 David Capello
|
||||
// Copyright (C) 2015 Gabriel Rauter
|
||||
//
|
||||
@ -21,6 +21,7 @@
|
||||
#include "app/ini_file.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "doc/doc.h"
|
||||
@ -242,8 +243,8 @@ static int progress_report(int percent, const WebPPicture* pic)
|
||||
FileOp* fop = wd->fop;
|
||||
|
||||
double newProgress = (double(wd->f) + double(percent)/100.0) / double(wd->n);
|
||||
wd->progress = MAX(wd->progress, newProgress);
|
||||
wd->progress = MID(0.0, wd->progress, 1.0);
|
||||
wd->progress = std::max(wd->progress, newProgress);
|
||||
wd->progress = base::clamp(wd->progress, 0.0, 1.0);
|
||||
|
||||
fop->setProgress(wd->progress);
|
||||
if (fop->isStop())
|
||||
|
@ -21,12 +21,14 @@ std::string XmlTranslator::operator()(const TiXmlElement* elem,
|
||||
const char* value = elem->Attribute(attrName);
|
||||
if (!value)
|
||||
return std::string();
|
||||
else if (value[0] == '@') {
|
||||
else if (value[0] == '@') { // Translate string
|
||||
if (value[1] == '.')
|
||||
return Strings::instance()->translate((m_stringIdPrefix + (value+1)).c_str());
|
||||
else
|
||||
return Strings::instance()->translate(value+1);
|
||||
}
|
||||
else if (value[0] == '!') // Raw string
|
||||
return std::string(value+1);
|
||||
else
|
||||
return std::string(value);
|
||||
}
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include "ui/system.h"
|
||||
#include "ui/theme.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace app::skin;
|
||||
@ -183,7 +185,7 @@ void draw_alpha_slider(ui::Graphics* g,
|
||||
const gfx::Rect& rc,
|
||||
const app::Color& color)
|
||||
{
|
||||
const int xmax = MAX(1, rc.w-1);
|
||||
const int xmax = std::max(1, rc.w-1);
|
||||
const doc::color_t c =
|
||||
(color.getType() != app::Color::MaskType ?
|
||||
doc::rgba(color.getRed(),
|
||||
@ -210,7 +212,7 @@ void draw_alpha_slider(os::Surface* s,
|
||||
const gfx::Rect& rc,
|
||||
const app::Color& color)
|
||||
{
|
||||
const int xmax = MAX(1, rc.w-1);
|
||||
const int xmax = std::max(1, rc.w-1);
|
||||
const doc::color_t c =
|
||||
(color.getType() != app::Color::MaskType ?
|
||||
doc::rgba(color.getRed(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -121,7 +121,7 @@ static bool create_main_display(bool gpuAccel,
|
||||
try {
|
||||
if (w > 0 && h > 0) {
|
||||
main_display = os::instance()->createDisplay(
|
||||
w, h, (scale == 0 ? 2: MID(1, scale, 4)));
|
||||
w, h, (scale == 0 ? 2: base::clamp(scale, 1, 4)));
|
||||
}
|
||||
}
|
||||
catch (const os::DisplayCreationException& e) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -64,6 +64,13 @@ Preferences::Preferences()
|
||||
});
|
||||
doc::Sprite::SetDefaultGridBounds(defPref.grid.bounds());
|
||||
|
||||
// Reset confusing defaults for a new instance of the program.
|
||||
defPref.grid.snap(false);
|
||||
if (selection.mode() != gen::SelectionMode::DEFAULT &&
|
||||
selection.mode() != gen::SelectionMode::ADD) {
|
||||
selection.mode(gen::SelectionMode::DEFAULT);
|
||||
}
|
||||
|
||||
// Hide the menu bar depending on:
|
||||
// 1. this is the first run of the program
|
||||
// 2. the native menu bar is available
|
||||
@ -166,6 +173,10 @@ DocumentPreferences& Preferences::document(const Doc* doc)
|
||||
// Load specific settings of this document
|
||||
serializeDocPref(doc, docPref, false);
|
||||
|
||||
// Turn off snap to grid setting (it's confusing for most of the
|
||||
// users to load this setting).
|
||||
docPref->grid.snap.setValueAndDefault(false);
|
||||
|
||||
return *docPref;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -26,6 +26,7 @@
|
||||
#include "doc/frame.h"
|
||||
#include "doc/layer_list.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "filters/hue_saturation_filter.h"
|
||||
#include "filters/tiled_mode.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "render/onionskin_position.h"
|
||||
@ -55,7 +56,6 @@ namespace app {
|
||||
Preferences();
|
||||
~Preferences();
|
||||
|
||||
void load();
|
||||
void save();
|
||||
|
||||
// Returns true if the given option was set by the user or false
|
||||
@ -80,6 +80,7 @@ namespace app {
|
||||
void onRemoveDocument(Doc* doc) override;
|
||||
|
||||
private:
|
||||
void load();
|
||||
std::string docConfigFileName(const Doc* doc);
|
||||
|
||||
void serializeDocPref(const Doc* doc, app::DocumentPreferences* docPref, bool save);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -10,6 +10,6 @@
|
||||
|
||||
// Increment this value if the scripting API is modified between two
|
||||
// released Aseprite versions.
|
||||
#define API_VERSION 9
|
||||
#define API_VERSION 11
|
||||
|
||||
#endif
|
||||
|
@ -335,7 +335,13 @@ int App_useTool(lua_State* L)
|
||||
while (lua_next(L, -2) != 0) {
|
||||
gfx::Point pt = convert_args_into_point(L, -1);
|
||||
|
||||
tools::Pointer pointer(pt, tools::Pointer::Button::Left);
|
||||
tools::Pointer pointer(
|
||||
pt,
|
||||
// TODO configurable params
|
||||
tools::Vec2(0.0f, 0.0f),
|
||||
tools::Pointer::Button::Left,
|
||||
tools::Pointer::Type::Unknown,
|
||||
0.0f);
|
||||
if (first) {
|
||||
first = false;
|
||||
manager.prepareLoop(pointer);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -27,6 +27,7 @@
|
||||
#include "ui/entry.h"
|
||||
#include "ui/grid.h"
|
||||
#include "ui/label.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/separator.h"
|
||||
#include "ui/slider.h"
|
||||
#include "ui/window.h"
|
||||
@ -50,7 +51,9 @@ struct Dialog {
|
||||
ui::VBox vbox;
|
||||
ui::Grid grid;
|
||||
ui::HBox* hbox = nullptr;
|
||||
bool autoNewRow = false;
|
||||
std::map<std::string, ui::Widget*> dataWidgets;
|
||||
std::map<std::string, ui::Widget*> labelWidgets;
|
||||
int currentRadioGroup = 0;
|
||||
|
||||
// Used to create a new row when a different kind of widget is added
|
||||
@ -101,13 +104,33 @@ struct Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
Widget* findDataWidgetById(const char* id) {
|
||||
auto it = dataWidgets.find(id);
|
||||
if (it != dataWidgets.end())
|
||||
return it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void setLabelVisibility(const char* id, bool visible) {
|
||||
auto it = labelWidgets.find(id);
|
||||
if (it != labelWidgets.end())
|
||||
it->second->setVisible(visible);
|
||||
}
|
||||
|
||||
void setLabelText(const char* id, const char* text) {
|
||||
auto it = labelWidgets.find(id);
|
||||
if (it != labelWidgets.end())
|
||||
it->second->setText(text);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename Signal,
|
||||
template<typename...Args,
|
||||
typename Callback>
|
||||
void Dialog_connect_signal(lua_State* L,
|
||||
int dlgIdx,
|
||||
Signal& signal,
|
||||
obs::signal<void(Args...)>& signal,
|
||||
Callback callback)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, dlgIdx);
|
||||
@ -124,7 +147,7 @@ void Dialog_connect_signal(lua_State* L,
|
||||
lua_pop(L, 1); // Pop the uservalue
|
||||
|
||||
signal.connect(
|
||||
base::Bind<void>([=]() {
|
||||
[=](Args...args) {
|
||||
// In case that the dialog is hidden, we cannot access to the
|
||||
// global LUA_REGISTRYINDEX to get its reference.
|
||||
if (dlg->showRef == LUA_REFNIL)
|
||||
@ -141,7 +164,7 @@ void Dialog_connect_signal(lua_State* L,
|
||||
// lua_pcall() (that table is like an "event data" parameter
|
||||
// for the function).
|
||||
lua_newtable(L);
|
||||
callback(L);
|
||||
callback(L, std::forward<Args>(args)...);
|
||||
|
||||
if (lua_isfunction(L, -2)) {
|
||||
if (lua_pcall(L, 1, 0, 0)) {
|
||||
@ -164,7 +187,7 @@ void Dialog_connect_signal(lua_State* L,
|
||||
->scriptEngine()
|
||||
->consolePrint(ex.what());
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
int Dialog_new(lua_State* L)
|
||||
@ -193,7 +216,7 @@ int Dialog_new(lua_State* L)
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, -2, dlg->window.Close,
|
||||
[](lua_State* L){
|
||||
[](lua_State*, CloseEvent&){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -261,10 +284,13 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
const char* label = nullptr;
|
||||
std::string id;
|
||||
bool visible = true;
|
||||
|
||||
// This is to separate different kind of widgets without label in
|
||||
// different rows.
|
||||
if (dlg->lastWidgetType != widget->type()) {
|
||||
if (dlg->lastWidgetType != widget->type() ||
|
||||
dlg->autoNewRow) {
|
||||
dlg->lastWidgetType = widget->type();
|
||||
dlg->hbox = nullptr;
|
||||
}
|
||||
@ -273,8 +299,9 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
|
||||
// Widget ID (used to fill the Dialog_get_data table then)
|
||||
int type = lua_getfield(L, 2, "id");
|
||||
if (type == LUA_TSTRING) {
|
||||
if (auto id = lua_tostring(L, -1)) {
|
||||
widget->setId(id);
|
||||
if (auto s = lua_tostring(L, -1)) {
|
||||
id = s;
|
||||
widget->setId(s);
|
||||
dlg->dataWidgets[id] = widget;
|
||||
}
|
||||
}
|
||||
@ -282,20 +309,41 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
|
||||
|
||||
// Label
|
||||
type = lua_getfield(L, 2, "label");
|
||||
if (type == LUA_TSTRING)
|
||||
if (type != LUA_TNIL)
|
||||
label = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Focus magnet
|
||||
type = lua_getfield(L, 2, "focus");
|
||||
if (type != LUA_TNONE && lua_toboolean(L, -1))
|
||||
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
||||
widget->setFocusMagnet(true);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Enabled
|
||||
type = lua_getfield(L, 2, "enabled");
|
||||
if (type != LUA_TNIL)
|
||||
widget->setEnabled(lua_toboolean(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Visible
|
||||
type = lua_getfield(L, 2, "visible");
|
||||
if (type != LUA_TNIL) {
|
||||
visible = lua_toboolean(L, -1);
|
||||
widget->setVisible(visible);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
if (label || !dlg->hbox) {
|
||||
if (label)
|
||||
dlg->grid.addChildInCell(new ui::Label(label), 1, 1, ui::LEFT | ui::TOP);
|
||||
if (label) {
|
||||
auto labelWidget = new ui::Label(label);
|
||||
if (!visible)
|
||||
labelWidget->setVisible(false);
|
||||
|
||||
dlg->grid.addChildInCell(labelWidget, 1, 1, ui::LEFT | ui::TOP);
|
||||
if (!id.empty())
|
||||
dlg->labelWidgets[id] = labelWidget;
|
||||
}
|
||||
else
|
||||
dlg->grid.addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP);
|
||||
|
||||
@ -317,6 +365,20 @@ int Dialog_newrow(lua_State* L)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
dlg->hbox = nullptr;
|
||||
|
||||
dlg->autoNewRow = false;
|
||||
if (lua_istable(L, 2)) {
|
||||
// Dialog:newrow{ always }
|
||||
const int type = lua_getfield(L, 2, "always");
|
||||
if (type != LUA_TNONE) {
|
||||
if (type == LUA_TNIL ||
|
||||
lua_toboolean(L, -1)) {
|
||||
dlg->autoNewRow = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushvalue(L, 1);
|
||||
return 1;
|
||||
}
|
||||
@ -325,7 +387,8 @@ int Dialog_separator(lua_State* L)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
|
||||
std::string text;
|
||||
std::string id, text;
|
||||
|
||||
if (lua_isstring(L, 2)) {
|
||||
if (auto p = lua_tostring(L, 2))
|
||||
text = p;
|
||||
@ -337,9 +400,21 @@ int Dialog_separator(lua_State* L)
|
||||
text = p;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "id");
|
||||
if (type == LUA_TSTRING) {
|
||||
if (auto s = lua_tostring(L, -1))
|
||||
id = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
auto widget = new ui::Separator(text, ui::HORIZONTAL);
|
||||
if (!id.empty()) {
|
||||
widget->setId(id.c_str());
|
||||
dlg->dataWidgets[id] = widget;
|
||||
}
|
||||
|
||||
dlg->grid.addChildInCell(widget, 2, 1, ui::HORIZONTAL | ui::TOP);
|
||||
dlg->hbox = nullptr;
|
||||
|
||||
@ -352,7 +427,7 @@ int Dialog_label(lua_State* L)
|
||||
std::string text;
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "text");
|
||||
if (type == LUA_TSTRING) {
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto p = lua_tostring(L, -1))
|
||||
text = p;
|
||||
}
|
||||
@ -369,7 +444,7 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
|
||||
std::string text;
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "text");
|
||||
if (type == LUA_TSTRING) {
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto p = lua_tostring(L, -1))
|
||||
text = p;
|
||||
}
|
||||
@ -386,7 +461,7 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "selected");
|
||||
if (type != LUA_TNONE)
|
||||
if (type != LUA_TNIL)
|
||||
widget->setSelected(lua_toboolean(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
@ -395,8 +470,9 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Click,
|
||||
[dlg, widget](lua_State* L){
|
||||
dlg->lastButton = widget;
|
||||
[dlg, widget](lua_State* L, Event&){
|
||||
if (widget->type() == ui::kButtonWidget)
|
||||
dlg->lastButton = widget;
|
||||
});
|
||||
closeWindowByDefault = false;
|
||||
}
|
||||
@ -457,6 +533,19 @@ int Dialog_entry(lua_State* L)
|
||||
}
|
||||
|
||||
auto widget = new ui::Entry(4096, text.c_str());
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Change,
|
||||
[](lua_State* L){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return Dialog_add_widget(L, widget);
|
||||
}
|
||||
|
||||
@ -473,11 +562,20 @@ int Dialog_number(lua_State* L)
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "decimals");
|
||||
if (type != LUA_TNONE &&
|
||||
type != LUA_TNIL) {
|
||||
if (type != LUA_TNIL) {
|
||||
widget->setDecimals(lua_tointegerx(L, -1, nullptr));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Change,
|
||||
[](lua_State* L){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return Dialog_add_widget(L, widget);
|
||||
@ -491,25 +589,48 @@ int Dialog_slider(lua_State* L)
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "min");
|
||||
if (type != LUA_TNONE) {
|
||||
if (type != LUA_TNIL) {
|
||||
min = lua_tointegerx(L, -1, nullptr);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "max");
|
||||
if (type != LUA_TNONE) {
|
||||
if (type != LUA_TNIL) {
|
||||
max = lua_tointegerx(L, -1, nullptr);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "value");
|
||||
if (type != LUA_TNONE) {
|
||||
if (type != LUA_TNIL) {
|
||||
value = lua_tointegerx(L, -1, nullptr);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
auto widget = new ui::Slider(min, max, value);
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Change,
|
||||
[](lua_State* L){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onrelease");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->SliderReleased,
|
||||
[](lua_State* L){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return Dialog_add_widget(L, widget);
|
||||
}
|
||||
|
||||
@ -538,6 +659,16 @@ int Dialog_combobox(lua_State* L)
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Change,
|
||||
[](lua_State* L){
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return Dialog_add_widget(L, widget);
|
||||
@ -555,6 +686,19 @@ int Dialog_color(lua_State* L)
|
||||
auto widget = new ColorButton(color,
|
||||
app_get_current_pixel_format(),
|
||||
ColorButtonOptions());
|
||||
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Change,
|
||||
[](lua_State* L, const app::Color& color){
|
||||
push_obj<app::Color>(L, color);
|
||||
lua_setfield(L, -2, "color");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Dialog_add_widget(L, widget);
|
||||
}
|
||||
|
||||
@ -595,7 +739,10 @@ int Dialog_shades(lua_State* L)
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(
|
||||
L, 1, widget->Click,
|
||||
[widget](lua_State* L){
|
||||
[widget](lua_State* L, ColorShades::ClickEvent& ev){
|
||||
lua_pushinteger(L, (int)ev.button());
|
||||
lua_setfield(L, -2, "button");
|
||||
|
||||
const int i = widget->getHotEntry();
|
||||
const Shade shade = widget->getShade();
|
||||
if (i >= 0 && i < int(shade.size())) {
|
||||
@ -682,6 +829,173 @@ int Dialog_file(lua_State* L)
|
||||
return Dialog_add_widget(L, widget);
|
||||
}
|
||||
|
||||
int Dialog_modify(lua_State* L)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
const char* id = nullptr;
|
||||
bool relayout = false;
|
||||
|
||||
int type = lua_getfield(L, 2, "id");
|
||||
if (type != LUA_TNIL)
|
||||
id = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Modify window itself when no ID is specified
|
||||
if (id == nullptr) {
|
||||
// "title" or "text" is the same for dialogs
|
||||
type = lua_getfield(L, 2, "title");
|
||||
if (type == LUA_TNIL) {
|
||||
lua_pop(L, 1);
|
||||
type = lua_getfield(L, 2, "text");
|
||||
}
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
dlg->window.setText(s);
|
||||
relayout = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Here we could use dlg->window.findChild(id) but why not use the
|
||||
// map directly (it should be faster than iterating over all
|
||||
// children).
|
||||
Widget* widget = dlg->findDataWidgetById(id);
|
||||
if (!widget)
|
||||
return luaL_error(L, "Given id=\"%s\" in Dialog:modify{} not found in dialog", id);
|
||||
|
||||
type = lua_getfield(L, 2, "enabled");
|
||||
if (type != LUA_TNIL)
|
||||
widget->setEnabled(lua_toboolean(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "selected");
|
||||
if (type != LUA_TNIL)
|
||||
widget->setSelected(lua_toboolean(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "visible");
|
||||
if (type != LUA_TNIL) {
|
||||
bool state = lua_toboolean(L, -1);
|
||||
widget->setVisible(state);
|
||||
dlg->setLabelVisibility(id, state);
|
||||
relayout = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "text");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
widget->setText(s);
|
||||
relayout = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "label");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
dlg->setLabelText(id, s);
|
||||
relayout = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "focus");
|
||||
if (type != LUA_TNIL && lua_toboolean(L, -1)) {
|
||||
widget->requestFocus();
|
||||
relayout = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "decimals");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto expr = dynamic_cast<ExprEntry*>(widget)) {
|
||||
expr->setDecimals(lua_tointegerx(L, -1, nullptr));
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "min");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
||||
slider->setRange(lua_tointegerx(L, -1, nullptr), slider->getMaxValue());
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "max");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
||||
slider->setRange(slider->getMinValue(), lua_tointegerx(L, -1, nullptr));
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "value");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
||||
slider->setValue(lua_tointegerx(L, -1, nullptr));
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "option");
|
||||
if (auto p = lua_tostring(L, -1)) {
|
||||
if (auto combobox = dynamic_cast<ui::ComboBox*>(widget)) {
|
||||
int index = combobox->findItemIndex(p);
|
||||
if (index >= 0)
|
||||
combobox->setSelectedItemIndex(index);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "color");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto colorButton = dynamic_cast<ColorButton*>(widget)) {
|
||||
colorButton->setColor(convert_args_into_color(L, -1));
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "colors");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto colorShade = dynamic_cast<ColorShades*>(widget)) {
|
||||
Shade shade;
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
app::Color color = convert_args_into_color(L, -1);
|
||||
shade.push_back(color);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
colorShade->setShade(shade);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "filename");
|
||||
if (auto p = lua_tostring(L, -1)) {
|
||||
if (auto filenameField = dynamic_cast<FilenameField*>(widget)) {
|
||||
filenameField->setFilename(p);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// TODO combobox options? shades mode? file title / open / save / filetypes? on* events?
|
||||
|
||||
if (relayout) {
|
||||
dlg->window.layout();
|
||||
|
||||
gfx::Rect origBounds = dlg->window.bounds();
|
||||
gfx::Rect bounds = origBounds;
|
||||
bounds.h = dlg->window.sizeHint().h;
|
||||
dlg->window.setBounds(bounds);
|
||||
|
||||
dlg->window.manager()->invalidateRect(origBounds);
|
||||
}
|
||||
}
|
||||
lua_pushvalue(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Dialog_get_data(lua_State* L)
|
||||
{
|
||||
auto dlg = get_obj<Dialog>(L, 1);
|
||||
@ -689,6 +1003,9 @@ int Dialog_get_data(lua_State* L)
|
||||
for (const auto& kv : dlg->dataWidgets) {
|
||||
const ui::Widget* widget = kv.second;
|
||||
switch (widget->type()) {
|
||||
case ui::kSeparatorWidget:
|
||||
// Do nothing
|
||||
continue;
|
||||
case ui::kButtonWidget:
|
||||
case ui::kCheckWidget:
|
||||
case ui::kRadioWidget:
|
||||
@ -779,6 +1096,9 @@ int Dialog_set_data(lua_State* L)
|
||||
|
||||
ui::Widget* widget = kv.second;
|
||||
switch (widget->type()) {
|
||||
case ui::kSeparatorWidget:
|
||||
// Do nothing
|
||||
break;
|
||||
case ui::kButtonWidget:
|
||||
case ui::kCheckWidget:
|
||||
case ui::kRadioWidget:
|
||||
@ -891,6 +1211,7 @@ const luaL_Reg Dialog_methods[] = {
|
||||
{ "color", Dialog_color },
|
||||
{ "shades", Dialog_shades },
|
||||
{ "file", Dialog_file },
|
||||
{ "modify", Dialog_modify },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -27,6 +27,7 @@
|
||||
#include "doc/blend_mode.h"
|
||||
#include "doc/color_mode.h"
|
||||
#include "filters/target.h"
|
||||
#include "ui/mouse_button.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@ -162,6 +163,7 @@ void register_layer_class(lua_State* L);
|
||||
void register_layers_class(lua_State* L);
|
||||
void register_palette_class(lua_State* L);
|
||||
void register_palettes_class(lua_State* L);
|
||||
void register_plugin_class(lua_State* L);
|
||||
void register_point_class(lua_State* L);
|
||||
void register_range_class(lua_State* L);
|
||||
void register_rect_class(lua_State* L);
|
||||
@ -346,6 +348,17 @@ Engine::Engine()
|
||||
setfield_integer(L, "GRAYA", TARGET_GRAY_CHANNEL | TARGET_ALPHA_CHANNEL);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, "MouseButton");
|
||||
setfield_integer(L, "NONE", (int)ui::kButtonNone);
|
||||
setfield_integer(L, "LEFT", (int)ui::kButtonLeft);
|
||||
setfield_integer(L, "RIGHT", (int)ui::kButtonRight);
|
||||
setfield_integer(L, "MIDDLE", (int)ui::kButtonMiddle);
|
||||
setfield_integer(L, "X1", (int)ui::kButtonX1);
|
||||
setfield_integer(L, "X2", (int)ui::kButtonX2);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, "TilesetMode");
|
||||
@ -372,6 +385,7 @@ Engine::Engine()
|
||||
register_layers_class(L);
|
||||
register_palette_class(L);
|
||||
register_palettes_class(L);
|
||||
register_plugin_class(L);
|
||||
register_point_class(L);
|
||||
register_range_class(L);
|
||||
register_rect_class(L);
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/extensions.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/object_ids.h"
|
||||
@ -96,6 +97,8 @@ namespace app {
|
||||
return m_returnCode;
|
||||
}
|
||||
|
||||
lua_State* luaState() { return L; }
|
||||
|
||||
private:
|
||||
void onConsolePrint(const char* text);
|
||||
|
||||
@ -133,6 +136,7 @@ namespace app {
|
||||
void push_images(lua_State* L, const doc::ObjectIds& images);
|
||||
void push_layers(lua_State* L, const doc::ObjectIds& layers);
|
||||
void push_palette(lua_State* L, doc::Palette* palette);
|
||||
void push_plugin(lua_State* L, Extension* ext);
|
||||
void push_sprite_cel(lua_State* L, doc::Cel* cel);
|
||||
void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame);
|
||||
void push_sprite_frames(lua_State* L, doc::Sprite* sprite);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -146,6 +146,8 @@ doc::frame_t get_frame_number_from_arg(lua_State* L, int index)
|
||||
auto obj = may_get_obj<FrameObj>(L, index);
|
||||
if (obj)
|
||||
return obj->frame;
|
||||
else if (lua_isnil(L, index) || lua_isnone(L, index))
|
||||
return 0;
|
||||
else
|
||||
return lua_tointeger(L, index)-1;
|
||||
}
|
||||
|
@ -80,10 +80,26 @@ struct ImageObj {
|
||||
}
|
||||
};
|
||||
|
||||
void render_sprite(Image* dst,
|
||||
const Sprite* sprite,
|
||||
const frame_t frame,
|
||||
const int x, const int y)
|
||||
{
|
||||
render::Render render;
|
||||
render.setNewBlend(true);
|
||||
render.renderSprite(
|
||||
dst, sprite, frame,
|
||||
gfx::Clip(x, y,
|
||||
0, 0,
|
||||
sprite->width(),
|
||||
sprite->height()));
|
||||
}
|
||||
|
||||
int Image_clone(lua_State* L);
|
||||
|
||||
int Image_new(lua_State* L)
|
||||
{
|
||||
doc::Image* image = nullptr;
|
||||
doc::ImageSpec spec(doc::ColorMode::RGB, 1, 1, 0);
|
||||
if (auto spec2 = may_get_obj<doc::ImageSpec>(L, 1)) {
|
||||
spec = *spec2;
|
||||
@ -91,6 +107,13 @@ int Image_new(lua_State* L)
|
||||
else if (may_get_obj<ImageObj>(L, 1)) {
|
||||
return Image_clone(L);
|
||||
}
|
||||
else if (auto spr = may_get_docobj<doc::Sprite>(L, 1)) {
|
||||
image = doc::Image::create(spr->spec());
|
||||
if (!image)
|
||||
return 0;
|
||||
|
||||
render_sprite(image, spr, 0, 0, 0);
|
||||
}
|
||||
else if (lua_istable(L, 1)) {
|
||||
// Image{ fromFile }
|
||||
int type = lua_getfield(L, 1, "fromFile");
|
||||
@ -129,8 +152,16 @@ int Image_new(lua_State* L)
|
||||
spec.setHeight(h);
|
||||
spec.setColorMode((doc::ColorMode)colorMode);
|
||||
}
|
||||
doc::Image* image = doc::Image::create(spec);
|
||||
doc::clear_image(image, spec.maskColor());
|
||||
if (!image) {
|
||||
if (spec.width() < 1) spec.setWidth(1);
|
||||
if (spec.height() < 1) spec.setHeight(1);
|
||||
image = doc::Image::create(spec);
|
||||
if (!image) {
|
||||
// Invalid spec (e.g. width=0, height=0, etc.)
|
||||
return 0;
|
||||
}
|
||||
doc::clear_image(image, spec.maskColor());
|
||||
}
|
||||
push_new<ImageObj>(L, image);
|
||||
return 1;
|
||||
}
|
||||
@ -230,27 +261,13 @@ int Image_drawSprite(lua_State* L)
|
||||
// If the destination image is not related to a sprite, we just draw
|
||||
// the source image without undo information.
|
||||
if (obj->cel(L) == nullptr) {
|
||||
render::Render render;
|
||||
render.setNewBlend(true);
|
||||
render.renderSprite(
|
||||
dst, sprite, frame,
|
||||
gfx::Clip(pos.x, pos.y,
|
||||
0, 0,
|
||||
sprite->width(),
|
||||
sprite->height()));
|
||||
render_sprite(dst, sprite, frame, pos.x, pos.y);
|
||||
}
|
||||
else {
|
||||
Tx tx;
|
||||
|
||||
ImageRef tmp(Image::createCopy(dst));
|
||||
render::Render render;
|
||||
render.setNewBlend(true);
|
||||
render.renderSprite(
|
||||
tmp.get(), sprite, frame,
|
||||
gfx::Clip(pos.x, pos.y,
|
||||
0, 0,
|
||||
sprite->width(),
|
||||
sprite->height()));
|
||||
render_sprite(tmp.get(), sprite, frame, pos.x, pos.y);
|
||||
|
||||
int x1, y1, x2, y2;
|
||||
if (get_shrink_rect2(&x1, &y1, &x2, &y2, dst, tmp.get())) {
|
||||
|
218
src/app/script/plugin_class.cpp
Normal file
218
src/app/script/plugin_class.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
//
|
||||
// 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/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/console.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/ui/app_menuitem.h"
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Plugin {
|
||||
Extension* ext;
|
||||
Plugin(Extension* ext) : ext(ext) { }
|
||||
};
|
||||
|
||||
class PluginCommand : public Command {
|
||||
public:
|
||||
PluginCommand(const std::string& id,
|
||||
const std::string& title,
|
||||
int onclickRef)
|
||||
: Command(id.c_str(), CmdUIOnlyFlag)
|
||||
, m_title(title)
|
||||
, m_onclickRef(onclickRef) {
|
||||
}
|
||||
|
||||
~PluginCommand() {
|
||||
auto app = App::instance();
|
||||
ASSERT(app);
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
if (script::Engine* engine = app->scriptEngine()) {
|
||||
lua_State* L = engine->luaState();
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, m_onclickRef);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string onGetFriendlyName() const override {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void onExecute(Context* context) override {
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onclickRef);
|
||||
if (lua_pcall(L, 0, 1, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
Console().printf("Error: %s", s);
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_title;
|
||||
int m_onclickRef;
|
||||
};
|
||||
|
||||
void deleteCommandIfExistent(Extension* ext, const std::string& id)
|
||||
{
|
||||
auto cmd = Commands::instance()->byId(id.c_str());
|
||||
if (cmd) {
|
||||
Commands::instance()->remove(cmd);
|
||||
ext->removeCommand(id);
|
||||
delete cmd;
|
||||
}
|
||||
}
|
||||
|
||||
int Plugin_gc(lua_State* L)
|
||||
{
|
||||
get_obj<Plugin>(L, 1)->~Plugin();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_newCommand(lua_State* L)
|
||||
{
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
std::string id, title, group;
|
||||
|
||||
lua_getfield(L, 2, "id");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
id = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (id.empty())
|
||||
return luaL_error(L, "Empty id field in plugin:newCommand{ id=... }");
|
||||
|
||||
lua_getfield(L, 2, "title");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
title = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 2, "group");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
group = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
int type = lua_getfield(L, 2, "onclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Delete the command if it already exist (e.g. we are
|
||||
// overwriting a previous registered command)
|
||||
deleteCommandIfExistent(plugin->ext, id);
|
||||
|
||||
auto cmd = new PluginCommand(id, title, onclickRef);
|
||||
Commands::instance()->add(cmd);
|
||||
plugin->ext->addCommand(id);
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
// Add a new menu option if the "group" is defined
|
||||
if (!group.empty() &&
|
||||
App::instance()->isGui()) { // On CLI menus do not make sense
|
||||
if (auto appMenus = AppMenus::instance()) {
|
||||
std::unique_ptr<MenuItem> menuItem(new AppMenuItem(title, cmd));
|
||||
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_deleteCommand(lua_State* L)
|
||||
{
|
||||
std::string id;
|
||||
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_getfield(L, 2, "id");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
id = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else if (const char* s = lua_tostring(L, 2)) {
|
||||
id = s;
|
||||
}
|
||||
|
||||
if (id.empty())
|
||||
return luaL_error(L, "No command id specified in plugin:deleteCommand()");
|
||||
|
||||
// TODO this can crash if we delete the command from the same command
|
||||
deleteCommandIfExistent(plugin->ext, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_get_preferences(lua_State* L)
|
||||
{
|
||||
if (!lua_getuservalue(L, 1)) {
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setuservalue(L, 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Plugin_set_preferences(lua_State* L)
|
||||
{
|
||||
lua_pushvalue(L, 2);
|
||||
lua_setuservalue(L, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg Plugin_methods[] = {
|
||||
{ "__gc", Plugin_gc },
|
||||
{ "newCommand", Plugin_newCommand },
|
||||
{ "deleteCommand", Plugin_deleteCommand },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
const Property Plugin_properties[] = {
|
||||
{ "preferences", Plugin_get_preferences, Plugin_set_preferences },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
DEF_MTNAME(Plugin);
|
||||
|
||||
void register_plugin_class(lua_State* L)
|
||||
{
|
||||
REG_CLASS(L, Plugin);
|
||||
REG_CLASS_PROPERTIES(L, Plugin);
|
||||
}
|
||||
|
||||
void push_plugin(lua_State* L, Extension* ext)
|
||||
{
|
||||
push_new<Plugin>(L, ext);
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -22,7 +22,6 @@
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
@ -41,15 +40,32 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
|
||||
std::vector<tile_index> tiles;
|
||||
|
||||
RangeObj(Site& site) {
|
||||
const DocRange& docRange = site.range();
|
||||
updateFromSite(site);
|
||||
}
|
||||
RangeObj(const RangeObj&) = delete;
|
||||
RangeObj& operator=(const RangeObj&) = delete;
|
||||
|
||||
void updateFromSite(const Site& site) {
|
||||
if (!site.sprite()) {
|
||||
type = DocRange::kNone;
|
||||
spriteId = NullId;
|
||||
return;
|
||||
}
|
||||
|
||||
const DocRange& range = site.range();
|
||||
|
||||
spriteId = site.sprite()->id();
|
||||
type = docRange.type();
|
||||
type = range.type();
|
||||
|
||||
if (docRange.enabled()) {
|
||||
for (const Layer* layer : docRange.selectedLayers())
|
||||
layers.clear();
|
||||
frames.clear();
|
||||
cels.clear();
|
||||
colors.clear();
|
||||
|
||||
if (range.enabled()) {
|
||||
for (const Layer* layer : range.selectedLayers())
|
||||
layers.insert(layer->id());
|
||||
for (const frame_t frame : docRange.selectedFrames())
|
||||
for (const frame_t frame : range.selectedFrames())
|
||||
frames.push_back(frame);
|
||||
|
||||
// TODO improve this, in the best case we should defer layers,
|
||||
@ -57,7 +73,7 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
|
||||
// it might not be possible because we have to save the IDs of the
|
||||
// objects (and we cannot store the DocRange because it contains
|
||||
// pointers instead of IDs).
|
||||
for (Cel* cel : get_cels(site.sprite(), docRange))
|
||||
for (const Cel* cel : get_cels(site.sprite(), range))
|
||||
cels.insert(cel->id());
|
||||
}
|
||||
else {
|
||||
@ -73,8 +89,6 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
|
||||
if (site.selectedTiles().picks() > 0)
|
||||
tiles = site.selectedTiles().toVectorOfIndexes();
|
||||
}
|
||||
RangeObj(const RangeObj&) = delete;
|
||||
RangeObj& operator=(const RangeObj&) = delete;
|
||||
|
||||
Sprite* sprite(lua_State* L) { return check_docobj(L, doc::get<Sprite>(spriteId)); }
|
||||
|
||||
@ -149,6 +163,23 @@ int Range_containsTile(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Range_clear(lua_State* L)
|
||||
{
|
||||
auto obj = get_obj<RangeObj>(L, 1);
|
||||
auto ctx = App::instance()->context();
|
||||
|
||||
// Set an empty range
|
||||
DocRange range;
|
||||
ctx->setRange(range);
|
||||
|
||||
// Set empty palette picks
|
||||
doc::PalettePicks picks;
|
||||
ctx->setSelectedColors(picks);
|
||||
|
||||
obj->updateFromSite(ctx->activeSite());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Range_get_isEmpty(lua_State* L)
|
||||
{
|
||||
auto obj = get_obj<RangeObj>(L, 1);
|
||||
@ -240,8 +271,53 @@ int Range_get_tiles(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Range_set_layers(lua_State* L)
|
||||
{
|
||||
auto obj = get_obj<RangeObj>(L, 1);
|
||||
app::Context* ctx = App::instance()->context();
|
||||
DocRange range = ctx->activeSite().range();
|
||||
|
||||
doc::SelectedLayers layers;
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 2) != 0) {
|
||||
if (Layer* layer = may_get_docobj<Layer>(L, -1))
|
||||
layers.insert(layer);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
range.setSelectedLayers(layers);
|
||||
|
||||
ctx->setRange(range);
|
||||
obj->updateFromSite(ctx->activeSite());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Range_set_frames(lua_State* L)
|
||||
{
|
||||
auto obj = get_obj<RangeObj>(L, 1);
|
||||
app::Context* ctx = App::instance()->context();
|
||||
DocRange range = ctx->activeSite().range();
|
||||
|
||||
doc::SelectedFrames frames;
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 2) != 0) {
|
||||
doc::frame_t f = get_frame_number_from_arg(L, -1);
|
||||
frames.insert(f);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
range.setSelectedFrames(frames);
|
||||
|
||||
ctx->setRange(range);
|
||||
obj->updateFromSite(ctx->activeSite());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Range_set_colors(lua_State* L)
|
||||
{
|
||||
auto obj = get_obj<RangeObj>(L, 1);
|
||||
app::Context* ctx = App::instance()->context();
|
||||
doc::PalettePicks picks;
|
||||
if (lua_istable(L, 2)) {
|
||||
@ -254,7 +330,9 @@ int Range_set_colors(lua_State* L)
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->setSelectedColors(picks);
|
||||
obj->updateFromSite(ctx->activeSite());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -281,6 +359,7 @@ const luaL_Reg Range_methods[] = {
|
||||
{ "contains", Range_contains },
|
||||
{ "containsColor", Range_containsColor },
|
||||
{ "containsTile", Range_containsTile },
|
||||
{ "clear", Range_clear },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
@ -288,8 +367,8 @@ const Property Range_properties[] = {
|
||||
{ "sprite", Range_get_sprite, nullptr },
|
||||
{ "type", Range_get_type, nullptr },
|
||||
{ "isEmpty", Range_get_isEmpty, nullptr },
|
||||
{ "layers", Range_get_layers, nullptr },
|
||||
{ "frames", Range_get_frames, nullptr },
|
||||
{ "layers", Range_get_layers, Range_set_layers },
|
||||
{ "frames", Range_get_frames, Range_set_frames },
|
||||
{ "cels", Range_get_cels, nullptr },
|
||||
{ "images", Range_get_images, nullptr },
|
||||
{ "editableImages", Range_get_editableImages, nullptr },
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +10,7 @@
|
||||
#endif
|
||||
|
||||
#include "app/cmd/set_tag_anidir.h"
|
||||
#include "app/cmd/set_tag_color.h"
|
||||
#include "app/cmd/set_tag_name.h"
|
||||
#include "app/cmd/set_tag_range.h"
|
||||
#include "app/script/docobj.h"
|
||||
@ -82,6 +83,20 @@ int Tag_get_aniDir(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Tag_get_color(lua_State* L)
|
||||
{
|
||||
auto tag = get_docobj<Tag>(L, 1);
|
||||
doc::color_t docColor = tag->color();
|
||||
app::Color appColor = app::Color::fromRgb(doc::rgba_getr(docColor),
|
||||
doc::rgba_getg(docColor),
|
||||
doc::rgba_getb(docColor),
|
||||
doc::rgba_geta(docColor));
|
||||
if (appColor.getAlpha() == 0)
|
||||
appColor = app::Color::fromMask();
|
||||
push_obj<app::Color>(L, appColor);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Tag_set_fromFrame(lua_State* L)
|
||||
{
|
||||
auto tag = get_docobj<Tag>(L, 1);
|
||||
@ -127,6 +142,16 @@ int Tag_set_aniDir(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Tag_set_color(lua_State* L)
|
||||
{
|
||||
auto tag = get_docobj<Tag>(L, 1);
|
||||
doc::color_t docColor = convert_args_into_pixel_color(L, 2, doc::IMAGE_RGB);
|
||||
Tx tx;
|
||||
tx(new cmd::SetTagColor(tag, docColor));
|
||||
tx.commit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg Tag_methods[] = {
|
||||
{ "__eq", Tag_eq },
|
||||
{ nullptr, nullptr }
|
||||
@ -139,6 +164,7 @@ const Property Tag_properties[] = {
|
||||
{ "frames", Tag_get_frames, nullptr },
|
||||
{ "name", Tag_get_name, Tag_set_name },
|
||||
{ "aniDir", Tag_get_aniDir, Tag_set_aniDir },
|
||||
{ "color", Tag_get_color, Tag_set_color },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -151,7 +151,6 @@ FOR_ENUM(app::gen::ColorProfileBehavior)
|
||||
FOR_ENUM(app::gen::EyedropperChannel)
|
||||
FOR_ENUM(app::gen::EyedropperSample)
|
||||
FOR_ENUM(app::gen::FillReferTo)
|
||||
FOR_ENUM(app::gen::HueSaturationMode)
|
||||
FOR_ENUM(app::gen::OnionskinType)
|
||||
FOR_ENUM(app::gen::PaintingCursorType)
|
||||
FOR_ENUM(app::gen::PivotPosition)
|
||||
@ -162,12 +161,14 @@ FOR_ENUM(app::gen::StopAtGrid)
|
||||
FOR_ENUM(app::gen::SymmetryMode)
|
||||
FOR_ENUM(app::gen::TimelinePosition)
|
||||
FOR_ENUM(app::gen::WindowColorProfile)
|
||||
FOR_ENUM(app::gen::ToGrayAlgorithm)
|
||||
FOR_ENUM(app::tools::FreehandAlgorithm)
|
||||
FOR_ENUM(app::tools::InkType)
|
||||
FOR_ENUM(app::tools::RotationAlgorithm)
|
||||
FOR_ENUM(doc::AniDir)
|
||||
FOR_ENUM(doc::BrushPattern)
|
||||
FOR_ENUM(doc::ColorMode)
|
||||
FOR_ENUM(filters::HueSaturationFilter::Mode)
|
||||
FOR_ENUM(filters::TiledMode)
|
||||
FOR_ENUM(render::OnionskinPosition)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,7 +12,7 @@
|
||||
#include "app/site.h"
|
||||
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/base.h"
|
||||
#include "base/clamp.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/layer.h"
|
||||
@ -60,7 +60,7 @@ Image* Site::image(int* x, int* y, int* opacity) const
|
||||
image = cel->image();
|
||||
if (x) *x = cel->x();
|
||||
if (y) *y = cel->y();
|
||||
if (opacity) *opacity = MID(0, cel->opacity(), 255);
|
||||
if (opacity) *opacity = base::clamp(cel->opacity(), 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user