The full suite of editong tools of Horizon, and the IDE of HIDL. VS Code's shape, a title bar of its own with the menus in it, an activity bar and a side bar (the explorer, search, the outline, the designer's toolbox, components and layers), tabs and breadcrumbs over the editor, the problems and the output under it, the properties on the right and a status bar, icons throughout (Codicons). Ctrl+Shift+P is every command there is. Its own UI is a layout like the ones it edits, drawn by the same code a game draws them with, so what is in the canvas is the real thing. Components out of a palette, a tree of the layout, properties in sections that fold, the code it writes as it goes, undo, layers, templates, the skin's art to pick from, and a self test that clicks through the lot.
A layout is a file that says #define layout at the top (Hex writes it, Code, Make this file a layout puts it in an
old one) and opens as its designer, the canvas the whole of the middle, the palette, its tree, its layers and its
properties round it. F7, or the two buttons over the middle, turns it into its code instead, with the explorer and
what is wrong with the file round it, and what was typed goes into the layout on the way back (code that doesn't
build keeps it on the code).
A save keeps the comments somebody wrote into the file, the ones over the header and over each declaration, and writes
them back over the same declaration, renamed or moved (a comment inside the braces of one is lost). A drag lands on
tenths of a pixel, so a position in the code reads 26.8 and not whatever the zoom made of the pointer.
It edits every other .hor as well. A file that doesn't build a layout (data, a language, a skin, a move set) opens as
code, coloured, numbered, checked as it is typed, with its outline on the left. Every tab has an x, and closing a
tab or the window with something unsaved asks first. Ctrl+F finds, Ctrl+H replaces, Ctrl+G goes to a line, Ctrl+P opens any file of
the repository by a few letters of its name, Ctrl+Shift+F finds in all of them, F8 checks all of them for what doesn't
parse, F5 runs a file and lists what it declares. Ctrl+/ comments lines, Alt+Up and Down move them, Ctrl+D duplicates.
What is typed is completed (Ctrl+Space, or as it is typed), a component's properties inside its braces, the game's
text keys after an @, the names of the whole workspace anywhere else. F12 goes to where something is declared,
Shift+F12 finds everywhere it is used. Every key is in Help, Keyboard Shortcuts, a list typed into to narrow it
down, enter on a command to press a new key for it and delete to take its key off, kept in keys.hor next to
hex_settings.hor, and the command palette and every chooser go up and down with the arrows.
HIDL is a language of values first, so the right side of a file of code is what it comes to (Values), run a moment after
it was last typed into on a thread of its own, the way the game that reads it would see it, as a tree. A click on a
value goes to where it was written, in whichever file that was, the comment over a key is its tooltip, and what went
wrong running it is underlined where it went wrong. Under the editor, next to the problems and the output, is the HIDL
console, a line typed in runs in what the file came to (moves.jab.phases, len(keys(strings)), a behaviour called
with a made up view), what it declares kept for the next lines, Up and Down for what was typed before.
The workspace is any number of folders (File, Open folder, Add folder), a tree in the explorer on the left (Ctrl+Shift+E, Ctrl+Shift+O for the outline) with new, rename and delete on the right click, saved as a .hex-workspace and opened again with File, Open workspace. The one that was open comes back when Hex starts.
An extension is HIDL that drives Hex, a folder with an extension.hor in it. The ones that come with Hex are in
Assets/hex/extensions, your own go in an extensions folder next to Hex. They are switched on and off on their page
of the settings (the puzzle piece on the activity bar), and one that goes wrong is switched off for the rest of the run
and puts a card up saying why. Hello Dolly, the greeting in the title bar, is the first of them.
let extension = { name: "Hello Dolly", description: "...", version: "1.0.0", author: "bogz" };
let welcome_greetings = ["Hello, Dolly!", "..."];
// Called once Hex is up, with what an extension may do to it
func on_start(hex) {
hex.title_note(math.pick(welcome_greetings));
hex.command("Another greeting", func(hex) {
hex.title_note(math.pick(welcome_greetings));
});
}
What hex has for now. hex.title_note(text) puts a few words after Hex in the title bar, in brackets. hex.say(text)
says something in the status bar, hex.notify(text) puts a card up in the corner (hex.notify(text, true) an error
that stays). hex.command(name, func(hex) { ... }) puts a command in the command palette under the extension's name,
and hex.command(name, func(hex) { ... }, "Ctrl+Alt+J") puts it on that key as well, the palette saying which.
hex.file() is the path of the file on screen. hex.version, hex.name and hex.folder are Hex's version, the
extension's name and its folder. Besides on_start(hex), an extension hears about on_open(hex, file) and
on_save(hex, file), and on_change(hex, file) a moment after a file is typed into. What it prints goes in the output
with its name in front. An extension runs in a runtime of its own, a second at the most for anything it does, and it is
read again by itself when a .hor of its folder changes, its views back on the files that had them up, so writing one
is save and look rather than starting Hex again.
It can ask things. hex.menu([{ label, icon, key, on_pressed, enabled }, "-", { label, items: [...] }]) puts a menu up at
the pointer, a "-" a rule and items a menu inside it, hex.ask({ title, text, buttons: ["Yes", "No"] }, func(hex, pressed) { ... }) a question with buttons, and hex.prompt({ title, text, value, placeholder, button }, func(hex, typed) { ... }) a line of text.
It can have a UI too, a view of a file (hex.view, offered on the files that say its #define and opened on them by
itself, left: false or right: false in it putting Hex's sides away while it's up, hex.show_side("right", false)
doing it for good), a page of the panel (hex.panel) or a little window (hex.window), each a layout of its folder, and it can read
and write files (hex.read, hex.write, hex.load, hex.find_up), draw sprites into a drawing (hex.sprite), ask
for a file or a folder with Windows' dialogs (hex.pick_file({ title, folder, filter: ["Programs (*.exe)", "*.exe"] }, func(path) { ... }), hex.pick_folder(func(path) { ... })) and start a program (hex.start(path, [arguments])). hex.try_load(path) is hex.load that can't go wrong, { values, error }, for a file that may be half typed.
It can change a file by the paths of its values, written where they are in the text with the file's comments and spacing
kept, one step of the file's undo a call (null when it went, why it didn't as a text). hex.edit(file, ["moves", "jab", "damage"], 8), hex.insert(file, path, key, value, after), hex.remove(file, path), hex.rename(file, path, key),
hex.reorder(file, path, keys), hex.edits(file, [{ edit: path, value }, { insert: path, key, value }, ...]) for several
as one step, and hex.undo_file(file) and hex.redo_file(file). A file that isn't open is opened behind the one on screen.
hex.problems(file, [{ path or line, message, warning }]) puts what it finds wrong in Hex's problems, hex.watch(path)
has it told on_file_changed(hex, path), hex.open(file, path) opens a file at a value, hex.copy(text) puts text on
the clipboard. See docs/extensions.md, the move editor (Fighter2D's moves, extensions/move_editor) is all of it at
once.
What an extension can be told goes in its extension object, settings: { game: { title, about, kind, default } }, the
kind being text, path, number or flag. It gets a tab of its own in the settings (made from the template the settings'
pages name in settings.hor, extension_tab.hor), a row for each of them there, or its own layout for the tab if it says
settings_layout: "file.hor", which Hex hands to on_settings(hex, ui) once it's made. hex.setting(name) is what it
was told (or its default), hex.set_setting(name, value) changes it, and Hex keeps the lot in hex_settings.hor under
extension_settings. A change made on its tab is told to the extension, on_setting(hex, name, value).
Hex is one scene, HexScene, a partial class in parts by what they're about, and a few classes of its own next to them.
Shell/HexScene.cs is where it starts (the constructor finds the parts of Assets/hex/editor.hor by name, and
editor.hor is the skeleton that includes the parts in Assets/hex/shell/, the activity bar, the side bar, the tabs,
the panel and the properties, each a layout of its own that opens on its own in Hex) and its
UpdateState is a tick of the whole editor in a handful of steps, read that first. Every part of the class says what it is in
a comment at its top.
Shell/the frame round everything, the size of things (Chrome), the menus and keys, the command palette, the side bar and its trees, the tabs and breadcrumbs, the panel under the editor, the status bar, which view is on screen (Views), the welcome, Hex's own texts, andHexProfiler.Designer/a layout's designer, the canvas and the pointer on it, the handles, selecting, editing and arranging, the palette of components and what's in it (Catalog), the inspector, the tree and layers (Lists).Documents/an open file (HexDocument, a layout with its history and code, or text), opening, saving and closing, the recent files.Code/a file as code, its values, the console, completion, hints, finding, the outline, the lints and the table.Editing/changing a .hor file's text by what's in it (a key set, renamed, taken out) and keeping the rest as written, which the extensions'hex.editand friends are.Extensions/loading extensions and everythinghexis to them, their views, panels and windows.Workspace/the folders open, the explorer, the trash, and the work done on other threads over all of the files.Settings/hex_settings.hor, the settings window and the skin Hex is drawn in.Platform/the dialogs of Windows, opening, saving and picking a folder.Testing/the self test, a file per part of Hex, and--check.
HEX_PROFILE=20 (or any number of milliseconds) has Hex write out every tick that took longer than that with the steps
that took it, and when it's closed what every step cost altogether. See docs/performance.md.
git clone --recursive https://github.com/bogzxo/hex
dotnet build Hex.sln
Horizon.Hex/bin/Debug/net10.0/Horizon.Hex --selftest --exit --size 1600x900
The self test is a hundred seconds all of it, so it comes in parts, --selftest code (seven seconds) or any of layout
(the designer, the minute and more of it), code, workspace, extensions and garbage, as many as you like. A part
that wants a file an earlier part makes is given a stand-in for it.
The engine is a submodule at horizon/ (on its development), with the language inside it. Work on
development, master is what's released.