File SMDialog.mys
SMDialog class
Introduction
Hi devs!
When you create a floatting window or interface composer, MyrScript allows you to use DialogItem such as button, static text, radio, checkbox, text editor, note head/duration selector...
These items are very useful but you may bump into their limit,
for example create a small square button is impossible, it has minimal width.
An example of script made of "native" DialogItems is Target Editor.
Danièl's scripts
are very inspirating.
Danièl's approch is to graphically create buttons with background of palettes' buttons (Graph.DrawPaletteButton(...)
),
display a text, and handle in Idle()
and Draw()
functions, what to do for each button while you move mouse pointer or click.
The result is really more like a palette than a "dialog", and there is no such limitations as DialogItem have:
- like in palettes, color change when mouse pointer is over a button
- like some palette button, you can display 2 states: active, inactive
- No minimal size restriction
- You can insert text (caption), image or draw a polygon
A simple example of what Danièl scripts look like: Active layers
(but I'm sure you already installed and enjoyed all his wonderful scripts!)
If you have a look to the source code, you'll see a lot of variables for each button B1G, B1H, B1D, B1B
(gauche, haut, droite, bas) which stands for "button #1" left, top, right and bottom.
You'll see also two large Idle()
and Draw()
functions.
So I was looking for a way to simplify the code, and makie it more human readable...
After this long introduction, let's go discovering my powerful componants and code!
How to start?
Init function, SMCore is required
SMDialog is part of SMCore library.
The next lines of code will download automatically the library and made it available.
In a new floatting window, click the title bar and add these lines to Init()
function:
function Init(dialog) -- download/update core local coreUpdater = GetSystemSettingsPathName("Scripts/Includes").."SMCore-Updater.mys" local downloadCore = false if FileExist(coreUpdater)==false then coreUpdater = GetUserSettingsPathName("Scripts/Includes").."SMCore-Updater.mys" if FileExist(coreUpdater)==false then downloadCore = true; end end if downloadCore==true then print("SMCore libraries not found.") print("Downloading SMCore-Updater.mys...") local errorCode = Internet.DownloadFile( "sylvain-machefert.myriad-users.com", "archive/scripts/SMCore/SMCore-Updater.mys", coreUpdater) assert(errorCode==0, "Error occured while downloading. Error code: "..tostring(errorCode)) local file,errorMsg = OpenFile(coreUpdater, "r", UNKNOWN_STR_ENCODING, UNKNOWN_CR) if file then local line = file.Read("l") while line do assert(strfind(line,"404 Not Found ",1,1)==nil,"404 Not Found"); line=file.Read("l"); end file.Close() else error(errorMsg); end end Include "SMCore-Updater" SMCoreRequireVersion(20231130) Include "SMDialog" smDialog = SMDialog:new(dialog) end
In Harmony > 9.9.6c, you'll have debug infos in the Console, except when the script is run from the menu.
If you want to remove debug infos, add a line LOG_LEVEL = LOG_LEVEL_INFO
.
Of course, change the size of your dialog (AreaWidth and AreaHEight).
Last update version is published on the forum.
Older version of SMCore is automatically updated.
KeyDown, OnMouseWheel, Idle and Draw
OnMouseWheel
doesn't exist in floatting window skeleton, the others already exist. They will have one line of code, calling our smDialog object's functions. Just copy/paste ;)
function KeyDown(dialog,dummy,key) -- KeyDown : called each time a key is pressed local ret = smDialog:handleKeyDown(dummy, key) -- Return true if the key will be processed normaly return ret end function OnMouseWheel(dialog,score,amount) smDialog:handleMouseWheel(score, amount) end function Idle(dialog) -- Idle : called when nothing happens to the dialog box smDialog:handleIdle() end function Draw(dialog) -- Draw : called each time the dialog need to be redrawn -- default font size and face -- size is a number, face is a constant from MSDefine -- 11 and FACE_NONE should do the job. smDialog:handleDraw(11, FACE_NONE) end
Concepts
DialogItems
MyrScript native items are DialogItems, called "items" in the source code.
Palette buttons
All elements used by SMDialog are called "button" because they are drawn with a background Graph.DrawPaletteButton()
.
By removing the background, we can create static texts, but for the code these are also... "buttons".
Panels
SMDialog introduces the concept of
You can add items in your window and attach them to a panel in their own function Init(dialog, item)
, in one line:
item.UserTable.Panel="MyPanelName"
Buttons and panels are tables
Buttons and panels are tables with variables like Left, Top, Right, Bottom, Caption, Help... Table elements can be functions, and this is massively used by buttons.
Relative position
Buttons position are relative to their parent panel.
By default there is a "main" panel and positions are from the left top corner of the window, but in case of contextual menu, you'll see that relative positions is very handy.
Events
Buttons have "event" functions: OnClick, OnMouseWheel, OnMouseEnter, OnMouseLeave...
Keyboard navigation and focus
Mouse related events are sent to the button under the mouse pointer, but you can navigate throught buttons using Tab or Shift+Tab. Then, the focus is changed, and the focused button will receive keyboard event: OnKeyDown.
Some components, still based on buttons, such as Slider and Grid have pre-defined behavior when pressing keystrokes.
Dialog.UserTable
Inside these event functions, you don't have direct reference to smDialog object created in Dialog.Init()
, so there are some shortcuts in dialog.UserTable
to interact with SMDialog
(e.g. hide a panel, get a button to change it's properties...). See Dialog.UserTable for list of functions and properties.
Button creation in Dialog.Init()
All buttons, visible or hidden, in main panel or another, *MUST* be created function Init(dialog)
.
See SMDialog:addPaletteButton(...).
-- B1 is a button in dialog top left corner without contextual help smDialog:addPaletteButton("B1", 0, 0, 180, 25, "My button caption") -- B2 is a button wide as the dialog with contextual help smDialog:addPaletteButton("B2", 0, 25, nil, 50, "A wide button", "The wide button help") -- bigger and bold caption, button in near bottom right corner -- Default font size is set to 11 in Dialog.Draw -> smDialog:handleDraw(...) smDialog:addPaletteButton("help", -35, -35, -10, -10, "?", "Click to open help, right click to make coffee", 16, FACE_BOLD)
All these buttons are in the "main" panel = the window. We will focus on panels later.
Run the script, the buttons are drawn, but nothing happen on click. Stop the script.
Let's define the OnClick
events.
Event functions
See Button.button.OnClick(dialog, button, x, y, click)
fired when mouse button is pressedbutton.OnClickRelease(dialog, button, x, y, click)
fired when mouse button is releasedbutton.OnMouseEnter(dialog, button)
fired when mouse pointer enter the button area.button.OnMouseLeave(dialog, button)
fired when mouse pointer exit the button area.button.OnMouseMove(dialog, button, x, y, click)
fired each time mouse pointer move within the button's area.
This is less common event, but can be used for a Slider (cursor) while a mouse button is pressed.button.OnMouseOver(dialog, button, x, y, click)
fired each time the dialog is idle and mouse pointer is in button's area, even not moving.
Really uncommon but why not? It can scroll a long caption or play a little animation.button.OnMouseWheel(dialog, score, amount, button)
fired when mouse wheel is scrolling while mouse pointer is in button's area.
Note that in debug mode, mouse wheel is not fired.button.OnKeyDown(dialog, dummy, key, button)
fired to the focused button when a key is pressed.button.OnDraw(dialog, button)
called each time the button is drawn, after drawing the bacgkground palette button and the caption.
This allow to add picture, polygons, draw a slider (cursor)...
Attach event to button
- Still in the
function Init(dialog)
, get the result of SMDialog:addPaletteButton in a local variable, this is a Button:local B1 = smDialog:addPaletteButton("B1", 0, 0, 180, 25, "My button caption")
- Then, for each event required, write button.Event = function(...) body end:
B1.OnMouseEnter = function(dialog, button) print("Hello little button. How are you?") end B1.OnClick = function(dialog, button, x, y, click) print("OK, you clicked me!") end -- one-line format works as well: B1.OnMouseLeave = function(dialog, button) print("Good bye little button...") end
- If the bodies of the functions are short, you can write them as the above example.
But if you have the same body for several buttons, or a more complex code, you'd better write it in a separate function in your Dialog this way:function Init(dialog) --... B1.OnClick = function(dialog, button, x, y, click) dialog.MyComplexFunction(dialog, button, x, y, click) end end --... and at the bottom of your script: function MyComplexFunction(dialog, button, x, y, click) if button.Name=="B1" and click=LEFT_CLICK then print("Left click on B1") elseif button.Name=="B2" and click==LEFT_CLICK then print("Left click on B2") elseif button.Name=="help" and click==RIGHT_CLICK then dialog.MakeCoffee() -- else ... end end
This way will lighten a bit thefunction Init(dialog)
, and can be used by several buttons for complex tasks.
Button properties
As for events, once you have the button variable, you can read/change its properties.
See Button for complete list of properties.
local B1 = smDialog:addPaletteButton("B1", 0, 0, 180, 25, "My button caption") B1.CaptionHorizontalAlign = ALIGN_LEFT B1.IsSelected = true -- ...
Note: do NOT change Name, Left, Top, Right, Bottom and Panel after creation.
To change a set of button positions you can group them in a panel and move the panel (see further).
Panels
What is a panel?
Panel is a convenient way to group buttons and items: hide, show or move them in one function call,
A panel can be seen as a layer in the case you want to overlap buttons and items (e.g. a context menu that pops up at the x,y location of the right click).
It's also the way used by tabbed user interface:
- first tab show first panel, and hide all others
- second tab show second panel, and hide all others...
Create panels, in function Init(dialog)
Before adding buttons to a panel with the last argument of SMDialog:addPaletteButton, you must create it.
See SMDialog:addPanel(...).
default, all buttons are in "main" panel which is automatically created.
The returne table Panel describe panel's properties:
Name
, Left
, Top
, Right
, Bottom
that should not be modified.
AutoClose
and BackgroundColor
can be modified after creation, but there should not be a lot of case.
See Panel for list of properties.
Show and hide panels
There are three functions:
This will force a redraw of the window, in debug mode, this can create a short blink, but this was not perceptible in normal mode (run from the Scripts menu).
Move panel
In the case you want a panel appear at the click position, or some other strange cases,
do not change Left and Top properties!
Call SMDialog:movePanel(...). This will move all its content (buttons and items).
Note: use movePanel as "one shot", not for frequente moves like a sliding effect or a falling Tetris brick ;)
This would consume more CPU and need more graphical refreshs.
Add an item in a panel
For button, you know, it's the last argument of SMDialog:addPaletteButton(...)
.
For item (knob, list, note head/length selector, text box...), just click on each item and add a line in their Init function:
function Init(dialog,item) item.UserTable.Panel="MyPanel" end
If you also set item.AreaLeft=x
and item.AreaTop=y
,
these are absolute positions from left/top corner of the window.
This will be converted into relative position to the left/top of the panel so the item will follow the panel's moves.
Dialog.UserTable
smDialog is a variable (a SMDialog object) created in your dialog's Init function.
It is only visible by other dialog's function (Draw, Idle) where we call smDialog:handleDraw()
, smDialog.handleIdle()
...
But, in all events such as button click, you may need to access smDialog, to show/hide/move panels,
to get a button and change its properties... and there, smDialog variable is not visible.
In events, the native MyrScript Dialog is visible, given as dialog argument.
Dialog.UserTable is the bridge
A Lua table can contain functions.
Due to technical limitations, it was not possible to add directly function to the native Dialog object,
so I used Dialog.UserTable. See it for list of properties and functions.
Dialog.UserTable functions require dialog as first argument, else they
will raise an error. This is also a Lua standard.
For Java devs, these are static function, while object:doSomething() calls a method of an object).
Example: dialog.UserTable.HidePanel(dialog, name)
calls smDialog:hidePanel(name)
.
Dialog.DrawContent()
The Dialog.DrawContent()
in MyrScript manual, say it redraw completely the window.
But it doesn't know SMDialog at all.
SMDialog optimizes drawing, only needed buttons are redrawn instead of the whole window (it's around 3 times faster).
Don't call
Dialog.DrawContent()
, buttons will disappear!
Call
dialog.UserTable.DrawContent(dialog)
instead.
It forces SMDialog:handleDraw(...) to redraw all buttons.
- Sylvain Machefert
Class
Class | Summary |
---|---|
SMDialog | Powerful user interface. |
Tables
Table | Summary |
---|---|
Button | SMDialog button: the most basic component. |
Dialog.UserTable | Functions and variables in MyrScript's Dialog.UserTable |
Grid | Grid component, a flexible "table" with customizable events and draw for each column. |
GridColumn | Column definition for Grid. |
Panel | SMDialog panel: group of Buttons and DialogItemstr that move, show and hide together. |
Slider | Slider: a component to pick a value between a min and a max, that accept keyboard and mouse wheel inputs. |
Spinner | Spinner: a component that show arrows to select value in a pre-defined list. |
Summary
Constant | Type | Summary |
---|---|---|
CLICK_NONE | int | No mouse click |
DUAL_CLICK | int | Left+Right (dual) mouse button click |
LEFT_CLICK | int | Left mouse button click |
RIGHT_CLICK | int | Right mouse button click |
Constants
int CLICK_NONE
No mouse click
int LEFT_CLICK
Left mouse button click
int RIGHT_CLICK
Right mouse button click
int DUAL_CLICK
Left+Right (dual) mouse button click