Lua Script Extension and menu API

I have a suggestion for the Lua API.
I use Lua scripts a lot, but I find it a bit inconvenient.

  1. Call from the Script menu.
    If I make the directory structure easy to see in Aseprite, it is troublesome to manage the download source.
    If the directory structure is easy for the download source to manage, the provided functions and menu structure in Aseprite conflict.
    (And, one script is one menu, the number of files tends to increase.)

  2. Call from the shortcut key.
    Unnecessarily, the number of shortcut keys increases.I forget the scripts only used occasionally.

Therefore, I want to support Lua Script Extension and adding menu Lua API.
What does everyone think?

I made a sample that works in Windows 10.

Screenshot Image


Lua Script

Sample extension is here.

The Aseprite source is here.

The fix commit is here.


I’m sorry for poor English.
thank you for reading!

1 Like

Hi @msk-k, actually I had some plans to register actions in different menu items (even items in the main menu bar but different locations, not only File > Extensions). I’ve found my incomplete patch.

Some notes about your changes:

  • As in issue 1949, we have plans to register several things from extension scripts (commands, file formats, inks, etc.), so maybe app.register() is too much, or we should need app.registerMenu() or something like app.register{ menu="..." } (my original API use names like app.newCommand and app.newMenu).
  • There should be a way to unload a extension/script (remove all registered items)
  • I would prefer other word for "context" (to avoid confusion with keyboard shortcuts “context”), but still not sure.

I think some design work is still needed for the exact API that we want for extension scripts. Something like this might be a possibility:

app.plugin{
  load=function() ... end,
  unload=function() ... end
}

Or other example:

function init() -- or load() or activate()
end

function exit() -- or unload() or deactivate()
end

Some ideas from VSCode.

Another good thing would be to have all registered items automatically unloaded:

function init(plugin)
  plugin.register{ menu="edit.myMenu", ... }
end

So there is no need to create a exit() function.

1 Like

@dacap Thank you for your reply and reading my commit!

I haven’t seen the above issue, sorry.
I understand that my suggestion is included in the V1.2.X milestone (and it is still under design).

The plugin load / unload design was too difficult for me…I couldn’t get an idea.

I will post again if I come up with an idea that could contribute.

I look forward to the V1.2.X release.

Thank you!

2 Likes

I was working on this issue and making some progress with the final API:

This is an example:

function init(plugin)
  print('foo init')

  if plugin.preferences.count == nil then
    plugin.preferences.count = 0
  end

  plugin:newCommand{
    id="fooFirstCommand",
    title="Foo First Command",
    group="cel_popup",
    onclick=function()
      plugin.preferences.count = plugin.preferences.count+1
    end
  }
end

function exit(plugin)
  print("foo exit - count=" .. plugin.preferences.count)
end

Both functions init() and exit() will be optional. The "group" attribute should indicate the exact menu where to put the new command (anyway if no group is specified, the command will be available for keyboard shortcuts anyway). (All commands are deleted/unregistered automatically when the extension is disabled/uninstalled or at exit, so generally there will no need to define an exit() function).

2 Likes

That is awesome! How do I paint over the user drawing?
And how to publish/install a plugin?

Welcome @melanke, we still need to write the documentation to create plugins, anyway about your points:

  1. to draw in the canvas you can use the app.useTool() function
  2. you can create a .zip with a package.json file similar to other extensions like palettes but instead of "palettes" section you have to include a scripts section, for example:
    {
      "name": "dacap-scripts",
      "displayName": "dacap scripts",
      "description": "dacap scripts",
      "version": "0.1",
      "author": { "name": "dacap scripts",
                  "email": "david@igarastudio.com",
                  "url": "https://davidcapello.com/" },
      "contributors": [ ],
      "publisher": "aseprite",
      "license": "CC-BY-4.0",
      "categories": [ "Scripts" ],
      "contributes": {
        "scripts": [
            { "path": "./my-script.lua" },
        ]
      }
    }
    

The my-script.lua can be something like the given example above. After that you compress both files (package.json and my-script.lua) in a .zip, and rename the .zip to .aseprite-extension. To install the extension just double-clicking just be enough on Windows or macOS.

2 Likes

I tried a new feature!

The sample is working perfectly just by rewriting the third argument (group) of the newCommand function to the group in gui.xml.

gui.xml here: https://github.com/aseprite/aseprite/blob/master/data/gui.xml

I will try various things!

Thank you very much!

2 Likes

Great @dacap! I am very happy with this update.
I believe there will be a lot of amazing new possibilities for Aseprite now.

Thank you all for creating and improving this great tool Aseprite is :wink:

1 Like

@msk-k is the parameter the id of the xml property from the gui?

@dacap any chance you could clear up the steps to get something listed under a menu?

In the example it uses “cel_popup” which I don’t see directly listed in gui.xml instead I see things like “cel_popup_edit” would the group for mine be one be “cel_popup_myfirstcommand”?

Another example, if I wanted to put this under the menu bar like under “view” or “edit” how would I do that?

I have the plugin running as I’m seeing the print(“foo init”) in the example but I can’t find my command anywhere in the menus. right now mine looks something like this:

    function init(plugin)
        print("foo init")
        plugin:newCommand{
            id="dothething",
            text="Do A Thing",
            group="help",
            onclick=DoTheThing()
        }
    end
    function DoTheThing()
        print("Doing a thing!")
    end

Finally Even if I can’t find it in the menu, I also don’t see it listed under the keyboard shortcuts, do I need to define something specific for shortcuts?

Anyway thanks for any help anyone can provide! I’m excited to get this working!

You would need to specify the “group” of the menu that exists in gui.xml as the “group” argument to newCommand.

Then your menu will be added underneath the menu you specified.

My code is below.

  ------------------------------------------------------------------------------
  -- Top Menu
  ------------------------------------------------------------------------------
  plugin:newCommand{
    id="toLoopExtending",
    title="Loop Extending",
    group="sprite_crop",
    onclick=LoopExtending
  }
  
  ------------------------------------------------------------------------------
  -- Dialogs
  ------------------------------------------------------------------------------
  plugin:newCommand{
    id="toEditLayerMetaDataDialogShow",
    title="Mask Options",
    group="layer_popup_new",
    onclick=EditLayerMetaDataDialogShow
  }
  
  plugin:newCommand{
    id="toEditCelMetaDataDialogShow",
    title="Mask Options (Cel)",
    group="cel_popup_new",
    onclick=EditCelMetaDataDialogShow
  }

  ------------------------------------------------------------------------------
  -- Frame Menu
  ------------------------------------------------------------------------------
  plugin:newCommand{
    id="toFrameCopyout",
    title="new Sprite From frames",
    group="frame_popup_reverse",
    onclick=FrameCopyout
  }

  ------------------------------------------------------------------------------
  -- Layer Menu
  ------------------------------------------------------------------------------
  -- Mask and Link(Src/Dst) Cel Menu
  plugin:newCommand{
    id="toUpdateAll",
    title="Update (Copy & Paste)",
    group="layer_popup_new",
    onclick=UpdateAll
  }

  plugin:newCommand{
    id="toStoreAll",
    title="Store Offset",
    group="layer_popup_new",
    onclick=StoreAll
  }

And one thing that struck me is the way you specified the “onclick” argument.
It looks like the result of the DoTheThing run is specified as “onclick”.

I think the correct way is as follows

    function DoTheThing()
        print("Doing a thing!")
    end
    function init(plugin)
        print("foo init")
        plugin:newCommand{
            id="dothething",
            text="Do A Thing",
            group="help",
            onclick=DoTheThing
        }
    end