Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugins can have simple dialog and receive current time and selected layer xpath #2996

Merged
merged 8 commits into from
May 21, 2023

Conversation

rodolforg
Copy link
Contributor

@rodolforg rodolforg commented Jan 29, 2023

Plugins can now present to user a simple (not dynamic) configuration dialog and can also get the current state of the canvas (current time and selected layers).

Configuration dialog

The dialog, if provided by the plugin, will be shown every time before the plugin actually runs.
It is static: it will not enable/disable widget according to user interaction, neither validate data unless the widgets themselves perform validation.

How to provide the configuration dialog

Synfig Studio will automatically search for a file in the plugin script folder, with the same filename, except the extension, that must be ".ui".

  • The .ui file must be in Glade format, as it will be constructed via Gtk::Builder.
  • Synfig Studio runs on Gtk3, not Gtk4, so write a compatible .ui file.
  • Currently, we prefer to support Gtk 3.18. For now, avoid widgets provided later than this version.
  • The root widget of the .ui file will be embedded in a dialog with two buttons: OK and Cancel.
  • The root widget MUST be have its ID set as "dialog_contents".
  • The configuration fields MUST be named (set "name", not "ID"). Keep in mind that the field name will be used to identify the data to be passed to script; so, set a unique name to each one.
  • Supported control widgets (i.e. their data will be passed to plugin script):
    • Entry,
    • ComboBox (use item id),
    • ComboBoxText (use item id),
    • SpinButton,
    • FileChooserButton (a single file per button, for now),
    • ColorButtonChooser,
    • FontButtonChooser (the font value will be in PangoDescription format),
    • CheckButton,
    • Switch,
    • ToggleButton,
    • Scale,
    • ScaleButton,
    • SpinButton,
    • Volume,
    • AppChooserButton
  • Currently it doesn't support RadioButton widget.
  • You can use visual (e.g. Label) and helper (e.g. Containers) widgets freely.

How to get what was set by user on the configuration dialog

If the dialog exists and user presses its OK button, the plugin script will receive, as its second argument, a filename.
If using Python, get it via sys.argv[2].

The file contains a JSON dictionary. The dict entry named "dialog" contains a nested dictionary with the data provided/chosen by user. The file may contain extra data, such as the Canvas state, as described in next section.

The "dialog" dictionary is a list of key-value pair, being the key the widget name (not ID) and the value that one provided by user.
Notes:

  • ComboBox(Text) value is the ID of selected item, not the text presented to user.
  • Boolean values (e.g. CheckButton) are "1" or "0".
  • All field values will be passed as strings.
  • The data file is temporary: it will deleted as soon as plugins finish running (successfully or not)
Example of data file contents
{
  "dialog": {
    "my_entry": "what user typed",
    "the_checkbox": "1"
  }
}

Canvas State

Synfig Studio can now pass the current canvas view state, and the plugin script can request them selectively.
These info are supported:

  • current time (in seconds)
  • the selected layers (list of pseudo XPath)

How to request

The metafile plugin.xml was extended to let plugin author tells Synfig Studio what info it needs.

The <exec> element now has two optional attributes:

  • To receive current time, add this attribute to "exec" element: current_time="mandatory".
  • To receive XPath of selected layers, add this attribute to "exec" element: selected_layers="optional".

Both attribute accepts one of three possible values:

  • "unused": plugin does not need this info, and such data will not be passed to it (default)
  • "mandatory": plugin depends on this info. If user does not provide it, an error message will pop up warning him/her.
  • "optional": plugin can run without that info, so if it does not exist, it is not a problem.

Example: If selected_layers="mandatory" attribute exists, the user MUST select at least one layer to run the plugin. Otherwise, Synfig Studio will warn user and abort the plugin execution (by not starting it).

How to get the data

If any of the canvas state request attribute exists, the plugin script will receive, as its second argument, a filename.
If using Python, get it via sys.argv[2].

The file contains a JSON dictionary. The dict entry named "canvas_state" contains a nested dictionary with the requested data. The file may contain extra data, such as the Config Dialog entries, as described in previous section.

The possible keys for "canvas_state" dict are:

  • "current_time": value will be in seconds.
  • "selected_layers": an JSON array of pseudo XPath to selected layers.

Notes:

  • Only the requested data will be in the file.
  • The XPath for each layer is not 100% compliant.:
    • It may start with a filename, if selected layer comes from an imported sif file. In this case, it will be followed by # character, to mark the file path end.
    • It may have a starting ":" and that means the root canvas element of the file.
    • If it is an exported canvas (or from an exported value node), it won't have the initial path, but its ID. So place properly defs path.

Examples of file contents

{
  "canvas_state": {
    "current_time": "1.35"
  }
}

Only "time" was requested

{
  "canvas_state": {
    "selected_layers": [
      ":layer[2]"
    ]
  }
}

Only "layers" was requested, and user only selected one layer before calling the plugin. That layer is in the root canvas, being the second one from bottom to top.

{
  "canvas_state": {
    "current_time": "1.35",
    "selected_layers": [
      ":layer[2]",
      ":layer[7]"
    ]
  }
}

Metadata

Plugin can now provide its author name, release name and version number.

The file plugin.xml now has new elements:

  • element <plugin> now supports some metadata child elements:
    • "author" : author name [string]
    • "release" : release name [translatable string]
    • "version" : version number [integer]
    • "url" : url pointing to the plugin project/website [string]
    • "description" : a description text to guide the user [translatable string]

For now, these information do not appear anywhere in the GUI.
They can be used in the future by a plugin manager dialog in Synfig Studio.

@rodolforg rodolforg marked this pull request as draft January 29, 2023 21:11
@rodolforg
Copy link
Contributor Author

I just realized that it has the Gtk::Entry, and I didn't make any JSON escape for it. So, back to draft.

@rodolforg
Copy link
Contributor Author

@veermetri05 as you are a/the plugin maker, could you please test/evaluate/review this proposal? :)

@ice0
Copy link
Collaborator

ice0 commented Jan 30, 2023

Passing data through the command line can easily reach the maximum length limit. For example for Windows this is just 8191 chars.

https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation

Because you start with selected layers, next you will need additional metadata, etc.
So maybe pass the data through a json file and pass the name of that file as an argument?
Or maybe use pipes as data-channel?

@rodolforg
Copy link
Contributor Author

On Windows it is actually 32767 .
https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553#:~:text=The%20maximum%20command%20line%20length,you%20have%20to%20worry%20about.

I forgot to add this problem in the description ;)

Anyway, I'll learn how Synfig creates temp files.

@rodolforg
Copy link
Contributor Author

@ice0 Now I changed to pass the filenames as arguments.
So, it doesn't need to be in JSON format, right?
One entry per line should be fine.
key:data

@ice0
Copy link
Collaborator

ice0 commented Jan 30, 2023

So, it doesn't need to be in JSON format, right?

Yes, but JSON is preferable because it doesn't require manual parsing from Python side.
This looks like:

import json
  
f = open('data.json')
data = json.load(f)
f.close()

Anyway, you need some rules to escape data values, so it's better to use some common format.

@rodolforg
Copy link
Contributor Author

rodolforg commented Jan 30, 2023

@veermetri05, I updated the PR description with a better (and updated) explanation.

@ice0 , please read it and tell me your opinion too XD

@veermetri05
Copy link
Contributor

veermetri05 commented Jan 30, 2023

@rodolforg , I appreciate the work, it a big first step for creating a better ecosystem for Synfig Plugins.

I think it would be good to pass a JSON file.

So, it doesn't need to be in JSON format, right?

I think it would be good to keep in standard JSON/XML format.

Now, they would be passed as a JSON file
python plugin-script.py ./path/to/view-data.json ./path/to/dialog-data.json

Why not just pass everything into one file ?
python plugin-script.py ./path/to/temp-data.json

{
  "canvasState": {
    "time": "1.5",
    "sel_layers": ":layer[2]"
  },
  "dialogData": {
    "my_entry": "some value",
    "checkbox": 1
  }
}

maybe we can just call it data rather than dialogData

Also I would like to know the reason behind why should the plugin request the current time or selected layers explicitly ? Why not always pass it ? If they don't need it they won't use it.

@rodolforg
Copy link
Contributor Author

rodolforg commented Jan 30, 2023

Why not just pass everything into one file ?

Indeed! Lol Sorry, I just made it automatically...

Also I would like to know the reason behind why should the plugin request the current time or selected layers explicitly ?

The selected layers was due to processing the canvas tree, that could take a little while, depending on the open document. There is an intention to be able to provide selected parameters, children (library items), and waypoints too.
And the concern with unnecessary file writing.
But now I guess I was just silly about it!

Edit: and because I warn user a layer needs to be selected!

@veermetri05
Copy link
Contributor

For warning the user, I think it becomes necessary for the plugin to explicitly state it.
So whenever the plugin needs the selected layers, would it be correct to say "The plugin requires layer(s) to be selected" rather than saying "The plugin is requesting for selected layer(s)", because when the plugin requires the layers to be selected it makes sense to show a warning.

@rodolforg
Copy link
Contributor Author

So, I'll change the attributes ("current_time" and "layers") to be one of these options: required, optional and no, instead of boolean value.

Btw, I'll standardize the attribute names with the dictionary keys: current_time and selected_layers. What do you think?

@rodolforg
Copy link
Contributor Author

So, I'll change the attributes ("current_time" and "layers") to be one of these options: required, optional and no, instead of boolean value.

Btw, I'll standardize the attribute names with the dictionary keys: current_time and selected_layers. What do you think?

@veermetri05 ping :)

@veermetri05
Copy link
Contributor

I'm okay with the attribute names.
I'm actually ok with any attribute names as long as they are documented properly 😅.

@rodolforg rodolforg force-pushed the live-plugins branch 2 times, most recently from c6a6b53 to d4fcb8c Compare April 16, 2023 11:31
@rodolforg
Copy link
Contributor Author

rodolforg commented Apr 16, 2023

I'm okay with the attribute names. I'm actually ok with any attribute names as long as they are documented properly sweat_smile.

@veermetri05 then I hope it is done now :) Please test it.

@rodolforg rodolforg marked this pull request as ready for review April 16, 2023 13:56
For now, these data are passed as a single extra argument, in JSON
format:
- "time" : current time in seconds [floating point number]
- "sel_layers" : array of xpath for every layer selected

The file plugin.xml now has new elements and attributes:
- element "plugin" now supports some metadata elements:
  - "author"  : author name [string]
  - "release" : release name [translatable string]
  - "version" : version number [integer]
- element "script" now supports some parameters from Studio!
  Attributes describe if they are required:
  - "time"  : if it requires the current time ["false"/"true"]
  - "layers"  : if it requires the selected layers ["false"/"true"]
Info is passed as JSON (dict of strings) as the 3rd or 4th argument
to the script (depending if it needs info about current time and
layer selection).

The UI file must have the same name as the script file, except by the
file extension, that must be ".ui".
Synfig Studio will load the widget (container or not) identified as
"dialog_contents" (i.e. its ID must be "dialog_contents").
Fields must _named_ (its "name", not its "id") as this will be the
dictionary keys.
Now both metadata .xml file (provided by plugin author) and
extra argument .json file (provided by Synfig Studio at runtime)
use same notation: "current_time" and "selected_layers".
In the same way, "canvasState" in json is now "canvas_state".

Besides, plugin author can tell Synfig Studio if an extra argument
(current_time or selected_layers) is unused, mandatory or optional.
@rodolforg
Copy link
Contributor Author

Rebased again. I suggest to squash the commits for this PR.
Some intermediate commits don't properly work.

Copy link
Member

@morevnaproject morevnaproject left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the awesome feature!

@rodolforg rodolforg changed the title feat: plugins can now receive current time and xpath to selected layers; and have simple dialog feat: plugins can have simple dialog and receive current time and selected layer xpath May 21, 2023
@rodolforg rodolforg merged commit 011a34c into synfig:master May 21, 2023
@pgilfernandez
Copy link
Contributor

it looks awesome!
is there any example or video to actually have a visual reference of the feature?

Thanks!

@rodolforg
Copy link
Contributor Author

@pgilfernandez Maybe @veermetri05 make (or adapt) a plugin that uses these new features lol :)

Do you want to write a plugin, @pgilfernandez ?

@rodolforg rodolforg deleted the live-plugins branch May 23, 2023 01:50
@veermetri05
Copy link
Contributor

Thank's for summoning me here. I can't think of any way where I could take advantage of the dialog feature in my previous plugins. But I have some ideas for stroke reveal script and maybe something like a waypoint alignment plugin (still brainstorming).

@pgilfernandez
Copy link
Contributor

@rodolforg well, by now I just want to learn how to do it as I want to first finish the "modern icon theme" (thanks again for your help)...

@pgilfernandez
Copy link
Contributor

@rodolforg @veermetri05 I would like to play a little bit with this dialog feature as I would like to create a plugin to automate text animation.

Do you have any demo or simple test to start it with? I read all the documentation but I would prefer to start with something that actually works (even if it does nothing) so that I can continue with it as reference.

Thanks!

@rodolforg
Copy link
Contributor Author

insertShapeDemo.zip
Here it is

@pgilfernandez
Copy link
Contributor

Awesome!!
I have read the code and it's terrific to have an idea on how it works. I have extended it and now I'm starting to use it as a reference for my "little text project".

I found just an error in your code that you may want to fix (I don't know how, maybe looking deeper into the code): when you run the plugin but you have no layer selected it crashes Synfig when pressing OK:

unhandled exception (type std::exception) in signal handler:
what: unordered_map::at: key not found

Cheers

@rodolforg
Copy link
Contributor Author

I realized that #3178

@pgilfernandez
Copy link
Contributor

@rodolforg I'm unable to retrieve current_time value, I'm probably doing something wrong but I can't find what:

  1. I add the attribute to plugin.xml, in my case <exec current_time="mandatory" selected_layers="optional">animate-text.py</exec>
  2. then in my animate-text.py y try to store the value with current_time = data['canvas_state']['current_time']

I get this error when running the plugin:
error

Here is the whole plugin in case you want to look into it
animate-text_v03.zip

Any idea?
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants