Ved technical documentation

This page is intended to document Ved's internals as well as possible. The page is still being worked on, and more things are being added over time. If you'd like to know more about anything specific in Ved that I haven't explained yet here, or not well enough (except for the easter eggs :P), feel free to ask! If you'd like more information on how to make plugins, check this page. The repository is here.

Last updated: Sunday 19 January 2020 23:31 (UTC) (this is the last edit date of the file)

Files

First of all, the following source files are used in Ved:

FilenameDescription
clargs.luaStores and formats the command line help output when requested.
This file was added in Ved 1.1.0.
conf.luaLÖVE's configuration file, controlling default window settings and loaded LÖVE modules.
const.luaConstants - Contains tile numbers for all tilesets, known scripting commands, music names, and other lookup tables.
coordsdialog.luaContains code related to the little room coordinates input after hitting Q in the main editor. Before 1.4.0, this was part of dialog.lua.
corefunc.luaContains a few functions that are used so early in loading (and/or are used on the crash screen), they must exist before things like plugins and the error handler are loaded.
devstrings.luaUsed for defining new text strings during development of a new version, before putting them in all the language files.
dialog.luaContains code related to dialog boxes. Before 1.4.0, this also contained code for right click menus, scrollbars and VVVVVV-style text boxes, which have each been moved to their own separate files as of 1.4.0.
dialog_uses.luaContains callback functions and definitions of fields for dialogs, which are used as arguments for dialog.create(...)
drawhelp.luaHolds drawhelp(), called by love.draw() in state 15 (the help state). The help system is also used for level notes and the plugins list.
drawlevelslist.luaHolds drawlevelslist(), called by love.draw() in state 6 (the loading screen state).
drawmaineditor.luaHolds drawmaineditor(), called by love.draw() in state 1 (the main editor state).
drawmap.luaHolds drawmap(), called by love.draw() in state 12 (the map state).
drawscripteditor.luaHolds drawscripteditor(), called by love.draw() in state 3 (the script editor state).
drawsearch.luaHolds drawsearch(), called by love.draw() in state 11 (the search state).
errorhandler.luaContains code for both the crash screen and the plugin error screen.
filefunc_linmac.luaSince Ved 1.5.0, this contains functions necessary for accessing the VVVVVV levels and graphics folders on Linux and macOS. This uses the vedlib_filefunc_* library found in the libs folder via LuaJIT FFI. Also see love.load() in main2.lua: On Linux this library is compiled locally, if unsuccessful (due to missing gcc) we'll fallback to filefunc_lin_fallback.lua instead. On Mac, an already compiled version of the library is used.
Before Ved 1.5.0, this was split in filefunc_lin.lua and filefunc_mac.lua and used terminal utilities to list level files.
filefunc_lin_fallback.luaContains functions necessary for accessing the VVVVVV levels and graphics folders on Linux, if compiling the filefunc library was not successful (due to missing gcc). This uses command line utilities like ls to list level files and some other file-related things.
filefunc_luv.luaContains fallback love.filesystem functions for accessing fallback levels and graphics folders if the operating system is something other than Windows, macOS or Linux.
filefunc_win.luaContains functions necessary for accessing the VVVVVV levels and graphics folders on Windows. As of Ved 1.5.0, this uses the Windows API for everything (including reading and writing level files, due to io.open being non-Unicode on Windows), before 1.5.0, it used command line utilities like dir.
func.luaContains many functions, especially general-purpose ones and core Ved functions.
helpfunc.luaContains certain functions related to (editing) level notes, and the rest of the help system.
imagefont.luaLoads and readies font.png for use inside Ved.
This file was added in Ved 1.4.0.
incompatmain8.luaIf LÖVE 0.8 or lower is used, this is loaded from main.lua. It displays a message that outdated LÖVE is being used in all available languages.
Before Ved 1.4.5, this file was called incompatmain.lua.
incompatmain9.luaIf LÖVE 0.9.0 is used, this is loaded from main.lua. It displays a message that LÖVE 0.9.0 is no longer supported in all available languages.
This file was added in Ved 1.4.5.
keyfunc.luaHandles the shortcut that can be used in the help screen to make text editable.
loadallmetadata.luaReturns level metadata for the levels list from a different thread.
loadconfig.luaHandles anything related to the settings.
love10compat.luaLoaded only when LÖVE 0.10.0 or higher is used, and provides compatibility with those versions. Contains the new love.wheelmoved callback.
love11compat.luaLoaded only when LÖVE 11.0 or higher is used, and provides compatibility with those versions. For example, this hijacks color functions so they work with 0-255 instead of 0-1.
main.luaThe first file that is loaded. Loads the fonts, sets a few basic variables, and loads plugins.lua, errorhandler.lua and, most importantly, main2.lua.
main2.luaLoads most other source files and assets, and contains pretty much all love.* callbacks.
mapfunc.luaContains functions related to rendering and updating the map overview screen.
This file was added in Ved 1.4.2.
music.luaHandles reading and writing vvvvvvmusic.vvv, mmmmmm.vvv, and other custom music files.
This file was added in Ved 1.6.0.
plugins.luaMakes sure plugins and their file edits and hooks are loaded
resizablebox.luaHas a system for a box that can be resized by dragging borders with the mouse. Was formerly used for resizing script boxes, but it was glitchy so it's now unused.
rightclickmenu.luaContains code related to right click menus. Before 1.4.0, this was part of dialog.lua.
roomfunc.luaContains functions related to rooms in levels, tiles and such.
scaling.luaHijacks/Decorates a couple of LÖVE functions to make scaling work perfectly
scriptfunc.luaContains functions related to scripts.
scrollbar.luaContains code related to scrollbars. Before 1.4.0, this was part of dialog.lua.
searchfunc.luaContains functions related to searching levels.
slider.luaUsed for the number control in the options screen, holds the function int_control
updatecheck.luaChecks what the latest version of Ved is via HTTP, and reports back. This is run inside a separate thread.
vvvvvv_textbox.luaContains code related to VVVVVV-style text boxes. Before 1.4.0, this was part of dialog.lua.
vvvvvvxml.luaLoads and parses levels from .vvvvvv level files, and creates and saves them. Also has a function for "loading" a blank level.

States

Ved uses state numbers to represent different screens, menus and interfaces. Blue state numbers are not normally used anymore, and/or are not normally accessible, and many of them are leftover testing states.

#Description
-3Black screen
-2tostate 6
-1Display error (expected: errormsg)
0Temp main menu (enter state). Can be accessed in debug mode by pressing F12.
1The editor (will expect things to have been loaded)
2Syntax highlighting test
3Scripting editor
4Some XML testing
5Filesystem testing
6Listing of all files in the levels folder, and load a level from here (loading screen)
7Display all sprites from sprites.png where you can get the number of the sprite you're hovering over
8Ancient save screen (you can type in a name and press enter)
9Dialog test, and right click menu test
10List of scripts, and enter one to load
11Search
12Map
13Options screen
14Enemy picker preview
15Help/Level notes/Plugins list
16Scroll bar test
17folderopendialog utility
18Show undo/redo stacks
19Flags list
20Resizable box test
21Display overlapping entities (may be a visible function later) (maybe doesn't work properly)
22Load a script file in the 3DS format (lines separated by dollars)
23Load a script file NOT in the 3DS format (lines separated by \r\n or \n)
24Simple plugins list (already not used)
25Syntax highlighting color settings
26Font test
27Display/Scale settings
28Level stats
29Plural forms test
30Assets viewer main menu
31Music player/editor, sound player
32Graphics viewer
100 and further can be allocated by plugins (next paragraph)

State allocation

In Ved 1.1.4 and higher, plugins can allocate an amount of states for their own use, without using hardcoded state numbers, making it unnecessary to think of unique state numbers that won't interfere with any other plugins or future Ved updates. The following functions can be used:

allocate_states(name [, amount=1])
This function is used to allocate the given amount of states with identifier name.
in_astate(name [, s=0])
This function returns true if the current state is s for identifier name. These state numbers start at 0.
to_astate(name [, new=0 [, dontinitialize=false]])
Change state to state number new for identifier name, and if dontinitialize is set, call hook func_loadstate.

For example, take a plugin called My First Plugin, which uses three states. Upon startup, like in hook love_load_start or love_load_end, the plugin calls allocate_states("my_1st_plug", 3). If this is the only plugin, or the first plugin to call allocate_states(), the allocated states will now, internally, be 100, 101 and 102. Let's say My First Plugin has three buttons to go to each of the allocated states. The first button, when clicked, would call to_astate("my_1st_plug", 0), the second would call to_astate("my_1st_plug", 1) and the third would call to_astate("my_1st_plug", 2). Hook love_draw_state, would contain something like this:

if in_astate("my_1st_plug", 0) then
	-- Insert drawing code for first state!
	statecaught = true
elseif in_astate("my_1st_plug", 1) then
	-- Insert drawing code for second state!
	statecaught = true
elseif in_astate("my_1st_plug", 2) then
	-- Insert drawing code for third state!
	statecaught = true
end

The hook func_loadstate could contain something similar for initialization code for all the states (but without statecaught = true)

The identifying name can be anything, but this name should be unique to one plugin. It's also possible to allocate multiple blocks of state numbers within the same plugin, if you use different names. If your plugin only has one state, you can leave out the number (allocate_states("my_1st_plug"), in_astate("my_1st_plug"), to_astate("my_1st_plug")). And of course, this means you can have multiple states that are only referred to by string names (I can see how in_astate("my_1st_plug_menu") and in_astate("my_1st_plug_display") can be more pleasing than in_astate("my_1st_plug", 0) and in_astate("my_1st_plug", 1)). It's up to you to choose whatever you like most, or whatever works best for your plugin.

LÖVE version compatibility

Ved is compatible with all revisions of LÖVE 0.9.x, 0.10.x and 11.x (except LÖVE 0.9.0 as of Ved 1.4.2), but its code is written for 0.9.x. Compatibility with newer versions is mostly achieved by causing update changes to be undone; for example, LÖVE functions that were renamed or expect different arguments are redefined/hijacked and then called by those redefinitions if arguments or return values need to be passed differently, and callbacks that get "new-style" data from LÖVE get a bit of conversion code at the top. There are a few instances of conditionals depending on the version number in regular code, but that is not very common.

In summary: (the unsupported features per version are more detailed below)

LÖVEVed support
0.8.0-Has never been supported, but a message is shown
0.9.0Support dropped in 1.4.5 (broken since 1.4.2)
0.9.1 Supported, with the following restrictions:
  • font.png from the VVVVVV graphics folder cannot be used
  • F9 to display hotkeys never displays ⌘ on Mac and never translates keys
  • The music player can't show song durations
  • The plugin hooks love_filedropped and love_directorydropped are never called
0.9.2
0.10.0Supported since a42
0.10.1
0.10.2
11.0Supported since 1.3.3
11.1
11.2
11.3

Checking the LÖVE version Ved is running under

Ved has a dedicated function to check if the current LÖVE version is at least a certain version or later, love_version_meets(). E.g. love_version_meets(10) means "LÖVE version is 0.10.0 or later", love_version_meets(9, 2) means "LÖVE version is 0.9.2 or later". It automatically takes care of the difference between 0.x and 11.x, too, so love_version_meets(10) means "LÖVE version is 0.10.0 or later" while love_version_meets(11) means "LÖVE version is 11.0 or later".

Features unsupported in older LÖVE versions

Nevertheless, there are simply some features or improved behavior added in later LÖVE versions, which Ved takes advantage of, that simply can't be backported to previous LÖVE versions. None of these are particularly important features for Ved's main purpose of editing levels, but it is still good to document them.

Being able to use font.png from the VVVVVV graphics folder as the main font
This feature is only supported in LÖVE versions 0.10.0 and up.

This is because in LÖVE versions previous to 0.10.0, the font returned by love.graphics.newImageFont() automatically had 1 pixel of extra horizontal spacing, and there was no way to change this. If you used a custom font.png with 1 pixel of extra spacing for each glyph, it would look really ugly, partly because you wouldn't be used to it being rendered that way, but mostly because Ved prints text assuming there's no 1 pixel of extra space for each glyph.

This problem is fixed in LÖVE 0.10.0+ because it added an optional third argument to love.graphics.newImageFont() to specify the spacing, which also lets you use negative values.

On a side note, tinynumbers, Ved's F9 hotkey font, doesn't have this problem. This is because of a semi-hacky workaround: the font image is intentionally made with 1 less pixel of space per glyph, and then when it gets passed to love.graphics.newImageFont(), it gets 1 pixel of extra spacing either because it's below LÖVE 0.10.0 and it's forced or because we've specified 1 pixel of spacing in LÖVE 0.10.0+.

Having the F9 hotkey font change depending on operating system, language, etc.
This feature is only supported in LÖVE versions 0.10.0 and up.

This is referring to the feature where the characters on the hotkeys that show up when you hold down F9 will change to match your operating system and language. This means that, for example, Ctrl will change to Cmd on macOS, and Ctrl will change to Strg if your language is German. (If you're a German macOS user then it will still be Cmd.)

This feature depends on Font:setFallbacks(), which was only added in LÖVE 0.10.0. Not much we can do without it.

Basically anything to do with jumping around the track of the currently playing audio in the music and sound effect viewers
This feature is only supported in LÖVE versions 0.10.0 and up.

In LÖVE versions before 0.10.0, you can't jump around the track of the currently playing music or sound effect. That means you cannot click on the track to go to a certain position, nor can you use (Shift)+(kp)Left/Right to move 5 or 10 seconds forwards or backwards.

The reason is simple: we need to know the duration of the currently playing audio. The only function that does this is Source:getDuration(), and it only exists starting in LÖVE 0.10.0.

Without knowing the duration of the audio, clicking on the track becomes meaningless, because one end is supposed to be the start of the audio (time t=0) and the other end is supposed to be the end of the audio (time t=<duration of audio>). Without the duration, we don't know what timecode the other end should be. So if one song is, let's say, 2:30 long and the other is 5:00 long, then in the 5:00-long song the middle of the track is 2:30, and in the 2:30-long song the middle of the track is 1:15 - but without knowing the duration of each we don't know where each timecode is supposed to be placed on the track for each song.

Another consequence of not knowing the duration is that we can't make sure that you don't go past the end of the track when you use (Shift)+(kp)Left/Right to jump around. The end of the track is determined by its duration, which we don't know. So we wouldn't know if you went past the end or not without knowing the duration of the audio.

Debug mode

Debug mode is a special mode used to access certain features and information that can be useful for debugging and developing Ved. Enabling debug mode has the following effects:

Debug mode is enabled by the boolean variable allowdebug. It is possible to enable it in-app by going from the load screen to the Ved options, clicking and holding the OK button, and "dragging" over the Send Feedback button holding the right mouse button as well.

Editor-related variables

Current room

The coordinates for the current room are stored in roomx and roomy, these start at 0 like in internal scripting.

Selected tool

The number for the currently selected tool is stored in selectedtool (which starts at 1). The selected subtools are stored for each separate tool, and are stored in the table selectedsubtool, which has 17 elements.

Selected tileset

Even though tileset information is also stored in the room metadata, the currently selected tileset and tile color has always stored in the variables selectedtileset and selectedcolor.

Undo/redo stacks

The variable that keeps track of actions that can be undone is undobuffer, and the one that keeps track of the actions that have been undone (and can be redone) is redobuffer. These are tables. If an undoable action is taken, an entry is added to the end of undobuffer, and redobuffer is cleared. When undoing, the function undo() is called, which undoes the latest action appropriately, and moves the last entry in undobuffer to the end of redobuffer. When redoing, the function redo() is called, which redoes the latest action appropriately, and moves the last entry in redobuffer to the end of undobuffer. Entries in undobuffer and redobuffer are tables with different properties depending on the undotype it has.

Other things

The number of the currently selected tile is stored in selectedtile.

To edit roomtext and (re)name scripts in script boxes and terminals, Ved uses editingroomtext. The entity ID of the entity data attribute currently being edited is stored in editingroomtext. You can get the entity being edited by simply doing entitydata[editingroomtext]. Since tables in Lua are 1-indexed, editingroomtext cannot be 0, so to check if we are currently editing the roomtext, just do editingroomtext > 0; to check if we are not, just do editingroomtext == 0.

editingroomname is a boolean that is true when the current room's roomname is being edited, and false when it isn't. However, you should use toggleeditroomname() to start and stop editing the roomname.

When editing enemy and platform boundaries, Ved uses editingbounds. It is 0 when no boundaries are being edited. Its magnitude (i.e. its absolute value, i.e. ignore the negative sign if there is one) will be 1 for platform bounds, and 2 for enemy bounds. Its sign (i.e. whether it's positive or negative) will be negative when placing the first corner (the top-left corner), and will be positive when placing the second corner (the bottom-right corner). So to reiterate: when editing platform bounds, editingbounds will go from 0, to -1, to 1; and when editing enemy bounds, editingbounds will go from 0, to -2, to 2.
You can start a boundary edit by calling either changeplatformbounds() or changeenemybounds().

The variable that controls the eraser (i.e. whether right-clicking will erase tiles if holding a tile brush) is eraserlocked. When it is true (by default), you can erase tiles using right-click. When false, you cannot.

Whether or not enemy and platform bounds are rendered is controlled by showepbounds.

The tiles picker (e.g. what pops up when you click on "Show all", or press/hold Ctrl+Shift) being open or not is controlled by tilespicker. tilespicker_shortcut controls whether or not you are holding the shortcut, so Ved knows to close it when you release Ctrl+Shift. But using RCtrl+RShift will keep the tiles picker open (and mixing Left/Right Ctrl+Shift will be the same as LCtrl+LShift - that is, it won't "stick" and you have to keep holding the key combo).

Level-related variables and functions

Level metadata

The variable metadata is a table with the different options as key-value pairs. The map width, map height and level music are not part of the metadata in the VVVVVV level format, but internally in Ved, they are. So all of the metadata variables are as follows:
metadata["Creator"]
metadata["Title"]
metadata["Created"]
metadata["Modified"]
metadata["Modifiers"]
metadata["Desc1"]
metadata["Desc2"]
metadata["Desc3"]
metadata["website"]
metadata["mapwidth"]
metadata["mapheight"]
metadata["levmusic"]

Room tiles

roomdata is a big table of tables that stores all the tiles in a level. roomdata[roomy][roomx] is a 1D array with all the tiles for the current room (the variables roomx and roomy are the current room's coordinates, and they start at 0). The tiles in the room are numbered starting at 1, so the 3rd tile from the left and top is roomdata[roomy][roomx][(2*40)+(2+1)].

Entities

entitydata contains all the entities in a level. Each element of entitydata is structured as follows:
{
	x = 12,
	y = 34,
	t = 17,
	p1 = 0,
	p2 = 0,
	p3 = 0,
	p4 = 0,
	p5 = 320,
	p6 = 240,
	data = "Roomtext or script name"
}

Room metadata

levelmetadata is the table containing room metadata, or, looking at the VVVVVV level format, each edLevelClass inside the levelMetaData tags. Indexes for this table start at 1 (because Lua, as you may know). So the metadata for the current room is levelmetadata[roomy*20 + roomx+1], since roomx and roomy start at 0. Each element is structured as follows:
{
	tileset = 0,
	tilecol = 0,
	platx1 = 0,
	platy1 = 0,
	platx2 = 320,
	platy2 = 240,
	platv = 4,
	enemyx1 = 0,
	enemyy1 = 0,
	enemyx2 = 320,
	enemyy2 = 240,
	enemytype = 0,
	directmode = 0,
	warpdir = 0,
	roomname = "Roomname",
	auto2mode = 0,
}
directmode is always present, even after a VVVVVV 2.0 level is loaded. If auto2mode == 1 then multi-tileset mode is used for that room, and in that case directmode should be 0. However, when saving, directmode is set to 1 in the level file because auto2mode is not saved to it.

Scripts

Two tables for this one: scriptnames which is a simple table of all the script names with numeric keys in the correct order, and scripts which contain the actual scripts, with script names as keys. Each element of scripts is a table itself, with all the lines in that script. An example population can be given as follows:
scriptnames = {
	[1] = "mynewscript",
	[2] = "mynewscript_load"
}
scripts = {
	mynewscript_load = {
		[1] = "ifflag(2,stop)",
		[2] = "flag(2,on)",
		[3] = "iftrinkets(0,mynewscript)"
	},
	mynewscript = {
		[1] = "reply(3)",
		[2] = "I probably don't really need it,",
		[3] = "but it might be nice to take it",
		[4] = "back to the ship to study..."
	}
}
If you're wondering how Ved stores internal scripts: for both loadscript and say(-1) internal script modes, text(1,0,0,3) #v and say(x) #v are put in between each block if the script has to be split. The loadscript mode starts with squeak(off) #v and say(x) #v, and ends with loadscript(stop) #v and text(1,0,0,3) #v. The say(-1) mode starts with squeak(off) #v, say(-1) #v, text(1,0,0,3) #v, and say(x) #v, and ends with loadscript(stop) #v (with no extra text line like the loadscript internal script mode has). If you want to check, hold down the shift key while opening a script. The same goes for checking checking flag names - Ved converts them to numbers when leaving the script editor and converts them back into names when opening it, unless you hold shift while opening.

Counts

The table count keeps count of the number of trinkets, crewmates and entities in the level, and keeps track of the integer key of the start point entity. It also counts the number of sanity checks that failed when loading the level. As can be seen in vvvvvvxml.lua:
	local mycount = {trinkets = 0, crewmates = 0, entities = 0, startpoint = nil, FC = 0}  -- FC = Failed Checks
(mycount is local to that function, and will be returned and then stored to count. So this whole system of creating tables locally and then returning references to them works? Yeah, it does, funny eh? I should probably rewrite that a little bit though.)

Metadata entity (level notes, flag names)

The metadata entity is used by Ved to store data that is specific to a certain level. It is a roomtext entity at x=800 y=600, which means it's in room 21,21 (1-indexed). The data is formatted with several levels of separation characters:

|primary separator (see this as separating different "tables" of data)
$secondary separator (see this as separating different "rows" or "records" inside a "table")
@tertiary separator (see this as separating different "columns" or "properties" inside a "rows"/"records")

For example, one of the "tables" contains level notes. Each level note is separated by $, and inside a level note, the name and the contents of the note are separated by @.

Accent grave (`) is the escape character; if the real versions of certain characters need to be represented, they are escaped as follows:

``g
|`p
$`d
@`a
(newline)`n
(2 spaces)`_

The function despecialchars(text) encodes the raw characters to escaped format, undespecialchars(text) decodes escaped characters back to raw characters.

As said, | is the primary separator, which separates the following items:

Description≥V
1Metadata entity version number0?
2Flag names2
3(Reserved)-
4Level variables, for use by Ved or plugins (Why haven't I documented this out of commit messages)3
5Level notes0?

(more coming soon)

Clipboard room format

Currently, when you copy a room to the clipboard, it's stored in a comma-separated format. It consists of 1215 values separated by commas, and it's structured as follows (indices start at 1, as in Lua):

12345678910111213141516 — 1215
Tileset Tilecol Platf. bounds x1,y1,x2,y2 Platv Enemy bounds x1,y1,x2,y2 Enemy type Direct mode Warp dir Room name* 1200 tile numbers

In the room name, commas are replaced by the character ´ (U+00B4 ACUTE ACCENT).

In this format, entities are not copied. I'm planning to replace this CSV system by an XML format, which will also contain entities. Data in the old format will still be pasteable, and I'll probably also leave in a way to copy as CSV.

Dialogs

In Ved 1.4.0, the dialogs system was overhauled. To create a new dialog, you can call dialog.create:
dialog.create(message, buttons, handler, title, fields, noclosechecker, identifier)
message is the body of the dialog box. This is the only required argument.
buttons speaks for itself as to what it is (but see below), if not specified, only an OK button will be present.
handler is a function that will be called when the dialog is closed, and will be provided with the button that was pressed and the dialog fields.
title is the title text of the dialog. Setting this to an empty string is not needed anymore.
fields defines the input fields that the dialog has, see below. If not given, the dialog has no input fields.
noclosechecker is a function of which the main purpose is to check whether a button shouldn't actually close the dialog, and should return true if so - think of apply buttons
identifier is just an extra internal label indicating the type of dialog, rarely used (it's used for the quit dialog to know that a quit dialog is already on top)

Buttons

Strictly speaking, the buttons parameter accepts a list/table of buttons. For each element, if that element is a string, that string will be displayed as-is. But if possible, it should be an integer, representing one of the built-in buttons. A list of buttons is available as DB, so for example, DB.YES is a "Yes" button. The list is as follows:

ConstantValueButton label
DB.OK1OK
DB.CANCEL2Cancel
DB.YES3Yes
DB.NO4No
DB.APPLY5Apply
DB.QUIT6Quit
DB.DISCARD7Discard
DB.SAVE8Save
DB.CLOSE9Close
DB.LOAD10Load

DB.LOAD was added in Ved 1.6.0.

There's also built-in lists of buttons available as DBS, like DBS.YESNO, which stands for {DB.YES, DB.NO}, meaning a Yes and No button.

ConstantButtons
DBS.OKOK
DBS.QUITQuit
DBS.YESNOYes, No
DBS.OKCANCELOK, Cancel
DBS.OKCANCELAPPLYOK, Cancel, Apply
DBS.SAVEDISCARDCANCELSave, Discard, Cancel
DBS.YESNOCANCELYes, No, Cancel
DBS.SAVECANCELSave, Cancel
DBS.LOADCANCELLoad, Cancel

DBS.SAVECANCEL and DBS.LOADCANCEL were added in Ved 1.6.0.

Handler

The purpose of the handler function is to take action after closing a dialog. For example, if a question is asked whether the user wants to destroy something, then that should be done if (and only if) the user chooses DB.YES.

The handler is a function that can take up to five arguments:

The input fields are provided regardless of the button pressed - a Cancel button doesn't have any special meaning and the dialog handler should deal with it to ignore the input fields in that case (if that's what you want).

Dialog handlers as used in Ved's code can be found in the file dialog_uses.lua, starting with dialog.callback. An example handler is the following. Assume the buttons for this dialog are DBS.YESNOCANCEL. In this example, users press Yes if they want a new dialog to be created showing what they entered in a field with the key name, press No if they still want a dialog but don't want to know their input, and press Cancel if they want no new dialog.

function(button, fields)
	if button == DB.YES then
		dialog.create("You pressed Yes, and the input with key \"name\" is " .. fields.name)
	elseif button == DB.NO then
		dialog.create("You pressed No! Apparently you don't want to know what you entered, but you still want a dialog.")
	end
end
DB.CANCEL is not checked, therefore the handler does nothing if that button is pressed.

No-close checker

No-close checkers are similar to handlers, but they are called before the dialog closes. This function can stop the dialog from being closed by returning true, and thus are useful for creating error messages if the user puts in invalid input. They can also be used to not close the dialog if an Apply button is pressed, for example.

The arguments for the no-close checker are the same as for the main handler, except the fourth argument (closing prevented by no-close checker) doesn't exist, which means the fourth argument is the dialog object. If the no-close checker returns true (and thus stops the dialog from closing) the handler will still be called, and its fourth argument will be set to true as well. Here's an example of how a no-close checker can be used to give an error message in case someone enters a value for the field with key inp that is empty or above 20 characters:

function(button, fields)
	if button == DB.OK and (fields.inp == "" or fields.inp:len() > 20) then
		dialog.create("Your input must not be empty or longer than 20 characters.")
		return true
	end
end
Now the handler might be defined as follows:
function(button, fields, identifier, notclosed)
	if notclosed then
		return
	end

	if button == DB.OK then
		-- Save your fields.inp here.
	end
end
You can find more examples in dialog_uses.lua in Ved, no-close checkers are generally prefixed _validate.

Fields

Each dialog can have a list of input fields that will be shown in the dialog. Each input field starts with the following sequential properties:

The X and Y positions for the field both start at 0, which is the position the regular dialog text also starts.
These are the different types of input fields:

0DF.TEXTText input (this is the default)
1DF.DROPDOWNDropdown
2DF.LABELPlain text label (does not take input)
3DF.CHECKBOXCheckbox
4DF.RADIOSRadio button list
5DF.FILESFiles list and directory navigation

The DF. constants were added in Ved 1.5.0. More information about how the different types work:

(0) DF.TEXT - Text input

A text field is what it says it is. An example is given as follows: {"name", 0, 1, 40, "", DF.TEXT}
Here, the key is name, it is positioned on the start of the second line of text, it is 40 characters wide (but more characters will fit less elegantly) and its default value is an empty string.

(1) DF.DROPDOWN - Dropdown

Dropdowns require at least one more argument:

Basically, there's two forms: first the simpler one. In the simpler form, you only need a list of items that will appear in the dropdown, and whenever the user selects an item, the value of the input field is set to the text of the option that the user selected. This means what's readable as an option will be passed. You may want to set the default value to an option in the list.
An example: {"drop", 0, 0, 30, "Option A", DF.DROPDOWN, {"Option A", "Option B", "Option C"}}
The width is set to 30 because that's how wide dropdown menus are (currently). If you want a function to be called every time an option is selected in this case, there'd be two more arguments: false (as a filler for the second table) and then the function.

For the second form, let's take the example of a user selecting between percentages, let's say 50%, 100% and 200%. You want to pass this as a number instead, so if the user selects 50%, you want the actual value to be 0.5. When the user does select 50%, the "onchange" function is called, and converts the "50%" into 0.5, and returns that. The second table that was mentioned (the one that converts a value to a displayable "current selection") has this 0.5 background value as a key, and that maps to a value of "50%".
So this is that example:

{
	"drop2", 0, 2, 30, 0.5, DF.DROPDOWN, {"50%", "100%", "200%"}, {[0.5] = "50%", [1] = "100%", [2] = "200%"},
	function(picked)
		if picked == "50%" then
			return 0.5
		elseif picked == "100%" then
			return 1
		elseif picked == "200%" then
			return 2
		end
	end
}

As of version 1.5.0, you can use the function generate_dropdown_tables(tuples) to generate these last three arguments. The function takes a table as an argument with key-value tuples as elements (not keys as keys and values as values). It returns the three required arguments for the second form (list of displayed items, converter key-value table and "onchange" function). For example, the previous example could be written more elegantly as follows:

{
	"drop2", 0, 2, 30, 0.5, DF.DROPDOWN, generate_dropdown_tables({{0.5, "50%"}, {1, "100%"}, {2, "200%"}})
}

(2) DF.LABEL - Plain text label

This is just a bit of text that can be displayed anywhere in the dialog you want. It can therefore be used to label other input fields without having to include those labels in the dialog contents.

The "default value" will be used as text, but it can also be a function that returns the text dynamically.

An example for a plain-text label is as follows: {"", 0, 5, 10, "Label", DF.LABEL}
The key is left empty, because it has not much use. But we can't set it to nil, otherwise Lua might think the table ends there. It is positioned on the start of the 6th line. The width is 10 characters, which means it will merely wrap beyond that point. Then the label is just a string, and will be displayed. 2 is the type.

Here's an example of a label that keeps changing: {"", 0, 5, 40, function() return love.math.random() end, DF.LABEL}
This will continuously display a different random number between 0 and 1. Note that the key and the table of fields are passed to the function, but it's not used here.

(3) DF.CHECKBOX - Checkbox

Checkboxes are useful for true/false values. The width of the checkbox is not the width of the actual checkbox, but the width of the clicking area. This is useful to make the label clickable as well.
Example: OPTIONLABEL = "Option"
{"option", 0, 5, 2+font8:getWidth(OPTIONLABEL), true, DF.CHECKBOX},
{"", 2, 5, 40, OPTIONLABEL, DF.LABEL}
The default state of this checkbox is checked, since the default value is set to true here. It is followed by a plain text label (type 2), and the label can be clicked as well to toggle the checkbox.

(4) DF.RADIOS - Radio button list

Radio buttons were added in Ved 1.5.0. These function exactly like dropdowns do, the only differences are the type and the way they behave in the GUI. The width argument is not used. You can use the same generate_dropdown_tables function for radio buttons that you can use for dropdowns.

For example, the time format picker in the language dialog works like this:

		{
			"timeformat", 23, 8, 0, s.new_timeformat, DF.RADIOS,
			generate_dropdown_tables(
				{{24, "23:59"}, {12, "11:59pm"}}
			)
		},

The initial value is s.new_timeformat, which is the setting for the time format, which is either 24 or 12. Selecting "23:59" sets the value to 24, selecting "11:59pm" sets the value to 12. (Note that the dialog handler should apply the change, the value that you fill in as 5th argument is merely the initial state of the 'field'.)

(5) DF.FILES - Files list and directory navigation

The files list type was added in Ved 1.6.0. The default value is the full path to the current directory. It takes 7 more arguments (note that despite me giving each of these arguments names, they don't actually have keys by those names, and this is only to make it easier to understand):

  1. menuitems - A table of files, where each file is a table of the form returned by listfiles_generic(). That is, it has the attributes of name, isdir, and lastmodified.
  2. folder_filter - If argument 7 (filter_on) is on, then the files listed will only be the ones ending in this string. Usually it'll be a file extension like .vvv. You can make this filter only directories by passing the operating system's directory separator, which should be dirsep.
  3. folder_show_hidden - Whether or not to show hidden files or not. This is passed to listfiles_generic(), which goes off of the operating system's definition of hidden.
  4. listscroll - The Y offset of the files list due to the scrollbar, as (indirectly) generated by scrollbar().
  5. folder_error - A string for indicating errors. It should be non-empty when there's an error.
  6. list_height - The amount, in blocks of 8, of the list.
  7. filter_on - Whether or not to apply the filename-ending filter from argument 2 (folder_filter).

Note that you need a field with the key of "name" to select a file, and you need a checkbox to toggle showing only directories, showing only files that are filtered, or showing hidden files.

For this reason, it is recommended to make a full file selection dialog with dialog.form.files_make() instead. In fact, all code in Ved currently uses that function instead of making the files list manually.

dialog.form.files_make(startfolder, defaultname, filter, show_hidden, list_height)
All of the arguments are required.
startfolder is the full file path to the folder you start the dialog in, just like the default value for DF.FILES.
defaultname is what to put as the default value for the "Name:" field.
filter will filter for filenames that end in this string, unless you pass it dirsep, in which case directories will be filtered instead. A checkbox toggling the filter will be created. If you decide to filter directories, then there will no longer be a "name"-key field.
show_hidden is whether or not to show hidden files, by the operating system's definition of hidden.
list_height is the height (in blocks of 8) of the list.

Dialogs (old, deprecated system, fully removed in Ved 1.5.2)

Dialog boxes in the system before Ved 1.4.0 can be created by calling dialog.new:
dialog.new(message, title, showbar, buttons, questionid)
message is the body of the text box.
title will be shown in the title bar (if the title bar is shown by setting showbar to 1, which is always done in Ved. In fact, as of 1.4.0, the showbar parameter has no effect anymore). The title is often set to an empty string in Ved.
buttons decides what buttons are shown, and questionid decides what to do with the button that is pressed, 0 to take no action. The question ID system is not optimized for plugins, but the new dialog system allows plugins to specify their own question handlers for dialogs.

Dialog buttons

The fourth argument of dialog.new (called buttons above) can be set to one of the following values V:

VButton 3Button 2Button 1
0
1OK
2Quit
3YesNo
4OKCancel
5OKCancelApply
6SaveDiscardCancel

(6 has been added in Ved 1.3.0)

Text input

General text input can be started by a single call to startinput(). There's also a function startinputonce(), which can be used if you decide to do it in update/drawing code, but expect me to remove that sooner or later. Input can be stopped (or locked) by calling stopinput(). The text input to the left of the cursor can be found in input, and text to the right of the cursor can be found in input_r. The variable __ (two underscores) contains the text cursor and the text to the right of it. So, to display the input field, you can concatenate input and __ (input .. __).

It's also possible to include text boxes in dialogs, see the above part on fields in dialogs.

Hotkeys

If you didn't know already, you can hold F9 to reveal hotkeys.

Since Ved 1.6.1, Ved has had a dedicated function to display any hotkey on the screen if F9 is held down, showhotkey:
showhotkey(hotkey, x, y, align, topmost, dialog_obj)
hotkey is the code (or sequence of codes) for the hotkey in question. More on what these codes are later. This is required.
x is the x-position of the hotkey. This is required.
y is the y-position of the hotkey. This is required.
align can be either one of ALIGN.LEFT (default), ALIGN.CENTER, or ALIGN.RIGHT, and will align the hotkey appropriately.
If you are calling this from a dialog, you will need to pass the next two arguments (and consequently, will need to pass align as well):
topmost is whether the given dialog is the topmost dialog or not. You should be in a cDialog drawing function and just pass the topmost from that function.
dialog_obj is the dialog object the hotkey is located on. You should be in a cDialog drawing function and just pass the self from that function.

Hotkey codes

Each symbol in the hotkey font, tinynumbersfont, is actually mapped to one specific character, and is case-sensitive.

What this means is that, for example, a is Alt, but A is just the letter A. In fact, 0-9 and A-Z (uppercase) are all just themselves, along with a lot of other characters. You can also easily combine symbols togetter like so: aS would simply be Alt+S, and show up as such accordingly.

In the following list of usable symbols, a character is simply itself if it has no text saying otherwise:

The top row of letters of the QWERTY keyboard, lowercase, along with k and l, is F1-F12. Here they are for reference:

Easter eggs

Ved contains several easter eggs.