Introduction
Welcome to the tophat documentation. This documentation contains some guides for using tophat and reference for tophat's Umka API.
What is tophat?
Tophat is a 2d game library for the Umka programming language. It is small and simple to use. You can visit the homepage at tophat2d.dev.
Getting started
This chapter contains guides for getting started with tophat.
Installation
The first step on the road to using tophat is to install it. If you download the prebuilt binaries, it is a very simple process.
Installing tophat on Linux
All commands are prefixed either using
$
or#
. The former is used for commands that can be run as a normal user, latter is used for commands requiring root privileges.
First you have to download tophat from this page. You
can do that manually or using this curl
command.
$ curl https://tophat2d.dev/dl/tophat-linux -o tophat
When you successfully download the binary, the next step is to install it to a
directory, which is in the system path. For example /usr/bin
.
# install tophat /usr/bin
To check if tophat was installed correctly, run tophat version
.
Installing tophat on Windows
Download the tophat exe from the downloads page. Then move it to a folder of your liking. Whenever you want to make a tophat project, it is recommended to make a shortcut to tophat in the project directory. This way you won't have to use the file dialog every time you run your game.
Running the examples
To run the examples, clone the tophat repository.
The examples are in the examples/
folder. On linux, cd to an example you
want to run and type tophat
. On windows, launch tophat and choose the folder
of the example you want to run.
Text editors with umka support
No text editors support umka out-of-the-box, but there are available extensions for the most popular ones.
Hello Tophat!
This chapter shows you how to make a simple hello world program in tophat.
Firstly make a new folder. In that folder create a file called main.um
, open
it in your favorite text editor and write the following in:
import (
"canvas.um"
"th.um"
"window.um"
)
fn init*() {
window.setup("Hello tophat!", 600, 600)
window.onFrame.register({
canvas.drawText("Hello tophat!", { 1, 1 }, th.black, 2)
})
}
Now run the file. On linux you would cd
into the directory and run tophat
.
On windows, open tophat
and choose the directory or make a shortcut to tophat
in the folder, so you can run the game by just double-clicking it.
Does everything work? Great! Is there a problem? Contact me at marek@mrms.cz.
Now let's explain all the parts of the example.
import (
"canvas.um"
"th.um"
"window.um"
)
The import statement imports all the modules you will need. The strings in the
import
statements are file-paths, but all the modules included in the example
are tophat builtin modules. This means you can import them even though they
aren't on the file system. All modules have their documentation available
here.
fn main() {
This declares the init
function and exports it. This function your game's
entry point.
window.setup("Hello tophat!", 600, 600)
This function call creates a window. It will set the title to "Hello tophat!" and the dimensions to 600x600.
window.onFrame.register({
This registers a callback to the onFrame
signal. What this means is that the
code in the brackets will be called on every frame. This is you game's main
loop.
canvas.drawText("Hello tophat!", { 1, 1 }, th.black, 2)
This function draws the text Hello tophat!
at position 1, 1
, with black
color and scaled 2 times.
Tophat tips
The API reference is your best friend.
Rotations are in degrees.
Assets paths are prefixed with the
dir
. That is./
by default, but you can specify it using thedir
flag. If you prefix a path withraw://
, it will not be changed by tophat.
You can get last frame length from
th.delta
. This is useful for making your game FPS independant. A good rule of thumb is to multiply any kind of movement by theth.delta
value.
Resizing the window won't change how the game is displayed - the viewport stays the same. The viewport size is in the
window.wp
variable. You can change it to your liking.
Dimensions of one
canvas.drawText
character is 5x5 px, and the character spacing is 1px.
input.getMouseDelta
works even if the cursor is frozen usingwindow.freezeCursor
Tutorials
This chapter contains various tophat tutorials. Please suggest tutorial topics at marek@mrms.cz.
Program structure
Your every project has to have a main file. By default that is main.um
, but
that can be changed using the main
flag. In your main file you need to
declare an init
function, which needs to be exported. If it isn't
exported, you will receive this error:
error: <PWD>/tophat_main.um (4, 11): Unknown identifier init
In this function, you can set up the window, load resources and register to the
window.onFrame
signal. This signal is emitted on every
frame. The following script will print "init" when it is launched and then
follow up by printing "frame" on every frame.
import (
"signal.um"
"window.um"
)
fn init*() {
window.setup("Main file structure example")
printf("init\n")
window.onFrame.register({
printf("frame\n")
})
}
Loading and drawing images
First you need to import the image.um module, which houses
all sorts of image functions. We are interested in image.load
. It takes a
path to an image, which it loads and then returns. Example:
apple := image.load("gfx/apple.png")
You can check the image using the validate
method.
if !apple.validate() {
error("Could not load gfx/apple.png")
}
That's all. The image is now ready to be drawn. You can do that using it's draw method. You need to pass it a transform.
apple.draw({
p: {5, 20},
s: {1, 1},
r: 45 })
This draws the image at position [5, 20] rotated by 45 degrees.
Full example:
import (
"image.um"
"signal.um"
"th.um"
"window.um"
)
var (
apple: image.Image
)
fn init*() {
window.setup()
apple = image.load("gfx/apple.png")
if !apple.validate() {
error("Could not load gfx/apple.png")
}
window.onFrame.register({
apple.draw({
p: {5, 20},
s: {1, 1},
r: 45 })
})
}
Keyboard input
Getting keyboard and mouse input in tophat is very simple. It is done using the input.um module.
There are two ways to get keyboard input in tophat. The is*
functions and the
getStr
function. The former returns info about a physical key on the
keyboard. getStr
returns a string typed by the user last cycle.
Example:
for i:=0; i < 256; i++ {
if input.isPressed(char(i)) {
printf("%d is pressed\n", i)
}
}
printf("the user wrote: %s\n", input.getStr())
Getting mouse input is also simple. Mouse buttons act as normal keys, so you
can use the is*
fuctions. Mouse position can be retrieved using the
getMousePos
function.
import (
"input.um"
"signal.um"
"th.um"
"window.um"
)
fn init*() {
window.setup()
window.onFrame.register({
for i:=0; i < 256; i++ {
if input.isPressed(char(i)) {
printf("%d is pressed\n", i)
}
}
printf("the user wrote: %s\n", input.getStr())
printf("the cursor is at %s\n", repr(input.getMousePos()))
})
}
Drawing text
There are two main ways of drawing text on the screen. The first one is the drawText function from canvas.um. It draws a simple 5*5 pixel text. It is useful for debugging, since it's easy to use, but doesn't produce good looking results and only supports ASCII. This makes it unfit for production use.
The second one uses tophat's font.um module,
which provides functions for operating with TrueType fonts. You can obtain
a Font
like this:
f := font.load("unifont.ttf", 32)
Then you can just use it's draw
method to draw text. Example:
f.draw("Hello tophat", th.Vf2{20, 20}, th.green)
import (
"canvas.um"
"font.um"
"th.um"
"window.um"
)
var (
f: font.Font
)
fn init*() {
window.setup()
f := font.load("unifont.ttf", 32, font.filteringNearest)
window.onFrame.register({
canvas.drawText("Hello tophat using canvas.um", th.Vf2{1, 1}, th.black, 1)
f.draw("Hello tophat using unifont", th.Vf2{1, 7}, th.black, 1)
})
}
Playing sounds
All of the sound stuff is in the audio.um. First step is loading a sound from a file.
mySound := audio.load("sfx/music.mp3")
Supported formats are mp3, flac and wave. You can modify some properties of the sound.
mySound.setLooping(true)
mySound.setVol(0.5)
If you want to play it, use the play method.
mySound.play()
Keep in mind that you can't play a sound multiple times at once. A solution to
this is to make copies of a sound using the copy
method.
mySound.copy().play()
import (
"audio.um"
"th.um"
"window.um"
)
fn init*() {
window.setup()
mySound := audio.load("sfx/music.mp3")
mySound.setLooping(true)
mySound.setVol(0.5)
mySound.play()
}
Making web builds
Tophat games can be exported to run on the web.
Linux
On Linux you can use the
th_emscripten_link
script. When
passed files, the script will make a web build of your game and save it to the
wasm-out
directory. Example:
Before using the script, you need to install emscripten. On Ubuntu, you can do that using
apt install emscripten
.
$ ls
main.um image_the_game_needs.png another_resource.txt
$ th_emscripten_link main.um image_the_game_needs.png another_resource.txt
$ ls
main.um image_the_game_needs.png another_resource.txt wasm-out
If you run the script for the first time, it will download some resources from
the internet. The script can take some time to run. After it finished, the
result is in the wasm-out
directory.
Windows
Currently there is no way of making web builds directly on windows. It is recommended to make them using WSL2.
Sprite sheet animations
This tutorial shows how to turn your spritesheets into animation. This is a builtin tophat feature, which makes this a very easy task.
We will need to declare these global variables:
var (
spriteSheet: image.Image
atl: atlas.Atlas
anm: anim.Anim
)
First you have to load the image with your spritesheet:
spriteSheet = image.load("spritesheet.png")
We then use the image to create an atlas. An atlas is used to operate with images that store subimages in a grid. The first argument to the mk function is the image we will create the atlas from. The second argument are the dimensions of the atlas.
atl = atlas.mk(spriteSheet, th.Vf2{ 6, 1 })
The atlas is then used to create an animation. The first argument is the atlas with the spritesheet. The second argument is the fps count. You can also add optional arguments specifying start and end frame and the start offset.
anm = anim.mk(atl, 1)
We now have the animation ready. Now we just have to call it's animate
method. The only argument it the time. We can use just th.time
in this
case.
anm.animate(th.time)
The animate
method uses the the base image's crop
method to select the
current frame. This means we can now just draw the image.
spriteSheet.draw(th.mkTransform(th.vf2f(1)))
Full program:
import (
"anim.um"
"atlas.um"
"image.um"
"signal.um"
"th.um"
"window.um"
)
var (
spriteSheet: image.Image
atl: atlas.Atlas
anm: anim.Anim
)
fn init*() {
window.setup("animation example", 500, 500)
window.setViewport(th.Vf2{ 5, 5 })
spriteSheet = image.load("spritesheet.png")
atl = atlas.mk(spriteSheet, th.Vf2{ 6, 1 })
anm = anim.mk(atl, 1)
window.onFrame.register({
anm.animate(th.time)
spriteSheet.draw(th.mkTransform(th.vf2f(1)))
})
}
You can see the full result here.
ui.um tutorial
You can see the result of this tutorial here.
var gui: ui.Gui
var tbName, tbPwd: ui.TextBox
Here we declare three variables. gui
is of the type ui.Gui
, which holds the
whole UI instance. tbName
and tbPwd
are both textboxes, which will be used
later to get input from the user.
gui = ui.mk({ 0, 0, 200, 200 }, ui.getDefaultStyle())
This initializes the UI instance. The first argument is the rect the gui will
occupy, the second is the style used. For now you can use tophat's default
style available using ui.getDefaultStyle()
.
layout := ui.LayoutFn{
...
}
This is the layout function, which builds the UI by calling methods on the
gui
struct. It is ran two times - first time to evaluate user input and
second time to draw the user interface.
gui.stack({ padding: 10 })
First we use the stack
container. It puts all the other elements on top of
each other. You can also apply padding to it.
gui.box({
dimension: 10,
dir: ui.BoxDirectionDown,
growType: ui.BoxGrowDimension })
Then we initialize a box
container. This container puts all the elements next
to each other. The configuration specifies three things:
dir
- the direction the elements are put in, in this case they will go from up to downgrowType
- specifies the way box will move the elementsdimension
- this specifies the number of pixels to grow by. This is needed asui.BoxGrowDimension
is specified
gui.label("User name:", { centerY: true })
gui.textBox(&tbName)
gui.label("Password:", { centerY: true })
gui.textBox(&tbPwd)
Now we can add the textboxes into the container.
gui.dupStyle()
gui.getStyle().containerBox.color = 0
gui.box({
dimension: 10,
dir: ui.BoxDirectionUp,
growType: ui.BoxGrowDimension })
gui.box({
subdivisions: 2,
dir: ui.BoxDirectionRight,
growType: ui.BoxGrowSubdivision })
This initializes the container for the buttons. Since we want the buttons to be
on the bottom, a box with ui.BoxDirectionUp
is put onto the stack. However
the style dictates, that a box shall have a gray background, which needs to be
removed as not to cover the textboxes. Then we push another box. It grows to
the right and uses the subdivision grow type. We specify that there will be two
elements in it.
if gui.qbutton("Cancel") {
window.quit()
}
if gui.qbutton("Login") {
printf("Login: %s %s\n",
tbName.buffer, tbPwd.buffer)
}
Now we can just add the two buttons. We use the method qbutton
, which adds a
button with a label.
gui.popContainer()
gui.popContainer()
gui.popStyle()
gui.popContainer()
}
Now we have to pop all the styles and containers. At the end of the layout
function, len(gui.container)
should equal 1.
gui.eval(layout)
gui.draw(layout)
This applies the layout with our UI instance gui
.
Full code:
import (
"th.um"
"ui.um"
"window.um"
)
var gui: ui.Gui
var tbName, tbPwd: ui.TextBox
fn init*() {
window.setup("ui.um tutorial", 600, 600)
window.setViewport({ 200, 200 })
gui = ui.mk({ 0, 0, 200, 200 }, ui.getDefaultStyle())
window.onFrame.register({
layout := ui.LayoutFn{
gui.stack({ padding: 10 })
gui.box({
dimension: 10,
dir: ui.BoxDirectionDown,
growType: ui.BoxGrowDimension })
gui.label("User name:", { centerY: true })
gui.textBox(&tbName)
gui.label("Password:", { centerY: true })
gui.textBox(&tbPwd)
gui.popContainer()
gui.dupStyle()
gui.getStyle().containerBox.color = 0
gui.box({
dimension: 10,
dir: ui.BoxDirectionUp,
growType: ui.BoxGrowDimension })
gui.box({
subdivisions: 2,
dir: ui.BoxDirectionRight,
growType: ui.BoxGrowSubdivision })
if gui.qbutton("Cancel") {
window.quit()
}
if gui.qbutton("Login") {
printf("Login: %s %s\n",
tbName.buffer, tbPwd.buffer)
}
gui.popContainer()
gui.popContainer()
gui.popStyle()
gui.popContainer()
}
gui.eval(layout)
gui.draw(layout)
})
}
Umka API reference
Documentation for various builtin tophat modules. They are categorized into multiple groups.
struct Anim
type Anim* = struct {
// the source atlas
atl: atlas.Atlas
// the first cell of the animation
min: int
// the last cell of the animation
max: int
fps: real32
// offset in time
offset: int
}
Anim allows you to animate between individual frames of an atlas.
fn mk
fn mk*(atl: atlas.Atlas, fps: int, min: int = 0, max: int = -1 /* len(atl) - 1 */, offset: int = -1 /* th.time */): Anim {
Anim
constructor
fn Anim.animate
fn (anm: ^Anim) animate*(time: int) {
Crops the base atlas to the cell that should be visible at time
.
fn Anim.framesPlayed
fn (anm: ^Anim) framesPlayed*(time: int): int {
Returns how many frames were played at time
.
struct Atlas
type Atlas* = struct {
i: image.Image // source image
cs: th.Vf2 // size of a cell in pixels
dm: th.Vf2 // amount of cells in image
}
Atlas is an image containing tiles in a square grid.
fn mk
fn mk*(i: image.Image, dm: th.Vf2): Atlas {
i: source image dm: amount of cells
fn Atlas.coords
fn (a: ^Atlas) coords*(n: int): th.Vf2 {
returns the coordinates of the nth tile
fn Atlas.cropSource
fn (a: ^Atlas) cropSource*(at: th.Vf2) {
Crops the sourse image to only show a wanted tile
fn Atlas.draw
fn (a: ^Atlas) draw*(at: th.Vf2, t: th.Transform) {
Draws the tile at at
Module for audio loading and playback.
opaque Sound
type Sound* = struct { _: ^struct{} }
Represents an instance of a playable sound. It is an opaque structure.
fn load
fn load*(path: str, flags: LoadFlag = 0): Sound {
Loads a sounds at path, if there is an error, the underlying pointer
will be NULL
and validate
will return false.
fn Sound.validate
fn (s: ^Sound) validate*(): bool {
Returns true
if s
loaded correctly.
fn Sound.copy
fn (s: ^Sound) copy*(): Sound {
Copies the sound. This will create another sound which can be configured and played independently from the original sound.
fn Sound.isPlaying
fn (s: ^Sound) isPlaying*(): bool {
Returns true if the sound is still playing.
fn Sound.play
fn (s: ^Sound) play*() {
Plays the sound.
fn Sound.start
fn (s: ^Sound) start*(): ^Sound {
The start function allows you to play a single sound multiple times. It will create a copy and return a pointer to it, so you can controll it while it is playing. The returned pointer can be discarded.
fn Sound.stop
fn (s: ^Sound) stop*() {
Stops the sound, but keeps the progress. If you want to start from the
begginning, use audio.Sound.seekToFrame(0)
.
fn Sound.setVol
fn (s: ^Sound) setVol*(vol: real32) {
Sets the volume as a multiplier of the base volume.
fn Sound.setPan
fn (s: ^Sound) setPan*(pan: real32) {
Sets the pan of the sound.
fn Sound.setPitch
fn (s: ^Sound) setPitch*(pitch: real32) {
Sets the pitch of the sound.
fn Sound.setLooping
fn (s: ^Sound) setLooping*(looping: bool) {
Sets whether the sound will loop upon finishing.
fn Sound.seekToFrame
fn (s: ^Sound) seekToFrame*(frame: uint) {
Seeks to a specified PCM frame.
fn Sound.frameCount
fn (s: ^Sound) frameCount*(): uint {
Returns length of the sound in PCM frames.
fn Sound.lengthMs
fn (s: ^Sound) lengthMs*(): uint {
Returns length of the sound in ms.
fn Sound.setStartTimeMs
fn (s: ^Sound) setStartTimeMs*(t: uint) {
fn Sound.setStopTimeMs
fn (s: ^Sound) setStopTimeMs*(t: uint) {
Canvas library allowing for drawing basic shapes. Coordinates are based on the screen.
fn drawText
fn drawText*(text: str, pos: th.Vf2, color: uint32, size: th.fu) {
Draws a basic pixel text. Only ascii is supported.
fn textSize
fn textSize*(text: str, scale: th.fu): th.Vf2 {
Returns the size of text taken by an equivalent drawText call.
fn drawRect
fn drawRect*(color: uint32, r: rect.Rect) {
Draws a Rectangle.
fn drawLine
fn drawLine*(color: uint32, b, e: th.Vf2, thickness: th.fu) {
Draws a line.
fn drawRectLines
fn drawRectLines*(color: uint32, r: rect.Rect, thickness: real32 = 1.0) {
Draws rect border.
fn drawQuad
fn drawQuad*(color: uint32, q: th.Quad) {
Draws a convex quad.
fn beginScissorRect
fn beginScissorRect*(r: rect.Rect, debug: bool = false) {
Disable rendering outside of rect r
fn endScissor
fn endScissor*() {
Stops cropping
Color operations. Operate on RGBA uint32 values.
fn hsv
fn hsv*(h, s, v: th.fu, a: th.fu = 1.0): uint32 {
Converts HSV values into RGBA uint32 color. NOTE: Hue is between 0 and 1
fn alpha
fn alpha*(c: uint32, to: th.fu): uint32 {
Sets alpha of the color c to a value in to.
fn rgb
fn rgb*(r, g, b: th.fu, a: th.fu = 1.0): uint32 {
Constructs RGBA uint32 from RGBA of reals.
A CSV parser, which also works for similar formats. It doesn't support quotes, but you can escape characters using a backslash.
fn parse
fn parse*(inp: str, sep: char = ','): [][]str {
Parses input into a 2d string array.
fn encode
fn encode*(inp: [][]str, sep: char = ','): str {
Converts 2d array to csv string.
Module for font rendering. Unicode is supported, but only left to right.
Filtering constants
const (
filterNearest* = 0
filterLinear* = 1
)
opaque Font
type Font* = struct { _: ^struct{} }
fn load
fn load*(path: str, size: th.fu, filter: uint32 = filterLinear): Font {
fn Font.validate
fn (f: ^Font) validate*(): bool {
fn Font.draw
fn (f: ^Font) draw*(text: str, pos: th.Vf2, color: uint32, scale: th.fu = 1.0) {
fn Font.measure
fn (f: ^Font) measure*(text: str): th.Vf2 {
opaque Image
type Image* = struct{ _: ^struct{} }
Represents a drawable image. It is an opaque structure. Images support a color filter. It is applied by multiplying the color of each pixel with the filter.
opaque RenderTarget
type RenderTarget* = struct { _: ^struct{} }
An image you can render to.
fn createRenderTarget
fn createRenderTarget*(size: th.Vf2, filter: int): RenderTarget {
Creates a render target you can draw to, like to a window.
Filter specifies specfifies filtering for resulting image.
Image can be retrieved via toImage
.
fn RenderTarget.end
fn (rt: ^RenderTarget) begin*() {
Begins the render target rendering pass.
fn RenderTarget.end
fn (rt: ^RenderTarget) end*(wp: th.Vf2) {
Ends the render target rendering pass.
fn RenderTarget.toImage
fn (rt: ^RenderTarget) toImage*(): Image {
Returns the image of the render target.
Do not call setfilter
on the resulting image.
fn load
fn load*(path: str): Image {
Loads an image at path.
fn Image.validate
fn (i: ^Image) validate*(): bool {
Returns true, if i's handle points to an image.
fn Image.flipv
fn (i: ^Image) flipv*(flip: bool) {
Flips image on it's vertical axis.
fn Image.fliph
fn (i: ^Image) fliph*(flip: bool) {
Flips image on it's horizontal axis.
fn Image.draw
fn (i: ^Image) draw*(t: th.Transform, color: uint32 = th.white) {
Draws the image in screen coordinates. It transforms it with t and applies color as a color filter.
fn Image.drawNinepatch
fn (i: ^Image) drawNinepatch*(outer, inner, dest: rect.Rect, color: uint32 = th.white, scale: real = 1.0) {
Draws "nine-patch" image.
outer
specifies the texture coordinates of the outer rect of source image,
inner
specifies coordinates of inner rect of source image, positioned relative to outer
.
You can tint with color
.
fn Image.drawOnQuad
fn (i: ^Image) drawOnQuad*(q: th.Quad, color: uint32 = th.white) {
Draws the image on top of a quad with corners of the image positioned on the verticies of the quad.
fn Image.getDims
fn (i: ^Image) getDims*(): th.Vf2 {
Returns width and heigth.
fn Image.crop
fn (i: ^Image) crop*(tl, br: th.Vf2) {
Crops an image. Coordinates are between 0, 0 (top left) and 1, 1 (bottom right)
fn Image.cropPx
fn (i: ^Image) cropPx*(tr, br: th.Vf2) {
Same as Image.crop
, but the positions are in pixels.
fn Image.cropRect
fn (i: ^Image) cropRect*(r: rect.Rect) {
Same as Image.crop
, but uses a rect instead of two positions.
fn Image.cropQuad
fn (i: ^Image) cropQuad*(q: th.Quad) {
Crop an image using a quad.
fn Image.getCropQuad
fn (i: ^Image) getCropQuad*(): th.Quad {
Crop an image using a quad.
fn mk
fn mk*(data: []uint32, dm: th.Vf2): Image {
Creates an image from raw data.
fn Image.copy
fn (i: ^Image) copy*(): Image {
Copies image into a new one.
fn Image.setfilter
fn (i: ^Image) setfilter*(filter: int) {
Sets a mag/min filter. 0 is nearest, others are linear. This function will regenerate the texture. This means it shouldn't be used in a loop. https://learnopengl.com/img/getting-started/texture_filtering.png left is nearest, right is linear.
fn Image.setData
fn (i: ^Image) setData*(data: []uint32, dm: th.Vf2) {
Updates the image data. dm are the dimensions of the new image. The new image doesn't have to be the same size as the old one.
fn Image.getData
fn (i: ^Image) getData*(): []uint32 {
Gets the image data. This downloads the data from the GPU on each call. Don't use in performance critical sections.
Module for getting keyboard and mouse input. is* functions return info based on a us QWERTY layout. They are supposed to be used for game controls. For text input use getStr.
Keycode constants
const (
mouse1* = 1 // NOTE: mouse 2 and 3 key codes are swapped
mouse2* = 3 // because sokol uses 3 for middle mouse
mouse3* = 2 // button.
key_ctrl* = 16
key_shift* = 17
key_alt* = 18
key_space* = 32
key_apostrophe* = 39 /* ' */
key_comma* = 44 /* , */
key_minus* = 45 /* - */
key_dot* = 46 /* . */
key_slash* = 47 /* / */
key_0* = 48
key_1* = 49
key_2* = 50
key_3* = 51
key_4* = 52
key_5* = 53
key_6* = 54
key_7* = 55
key_8* = 56
key_9* = 57
key_semicolon* = 59 /* ; */
key_equal* = 61 /* = */
key_a* = 65
key_b* = 66
key_c* = 67
key_d* = 68
key_e* = 69
key_f* = 70
key_g* = 71
key_h* = 72
key_i* = 73
key_j* = 74
key_k* = 75
key_l* = 76
key_m* = 77
key_n* = 78
key_o* = 79
key_p* = 80
key_q* = 81
key_r* = 82
key_s* = 83
key_t* = 84
key_u* = 85
key_v* = 86
key_w* = 87
key_x* = 88
key_y* = 89
key_z* = 90
key_left_bracket* = 91 /* [ */
key_backslash* = 92 /* \ */
key_right_bracket* = 93 /* ] */
key_grave_accent* = 96 /* ` */
key_world_1* = 161 /* non-US #1 */
key_world_2* = 162 /* non-US #2 */
key_escape* = 256
key_enter* = 257
key_tab* = 258
key_backspace* = 259
key_insert* = 260
key_delete* = 261
key_right* = 262
key_left* = 263
key_down* = 264
key_up* = 265
key_page_up* = 266
key_page_down* = 267
key_home* = 268
key_end* = 269
key_caps_lock* = 280
key_scroll_lock* = 281
key_num_lock* = 282
key_print_screen* = 283
key_pause* = 284
key_fn* = 289
key_fn1* = 290
key_fn2* = 291
key_fn3* = 292
key_fn4* = 293
key_fn5* = 294
key_fn6* = 295
key_fn7* = 296
key_fn8* = 297
key_fn9* = 298
key_fn10* = 299
key_fn11* = 300
key_fn12* = 301
key_fn13* = 302
key_fn14* = 303
key_fn15* = 304
key_fn16* = 305
key_fn17* = 306
key_fn18* = 307
key_fn19* = 308
key_fn20* = 309
key_fn21* = 310
key_fn22* = 311
key_fn23* = 312
key_fn24* = 313
key_fn25* = 314
key_kp_0* = 320
key_kp_1* = 321
key_kp_2* = 322
key_kp_3* = 323
key_kp_4* = 324
key_kp_5* = 325
key_kp_6* = 326
key_kp_7* = 327
key_kp_8* = 328
key_kp_9* = 329
key_kp_decimal* = 330
key_kp_divide* = 331
key_kp_multiply* = 332
key_kp_subtract* = 333
key_kp_add* = 334
key_kp_enter* = 335
key_kp_equal* = 336
key_left_shift* = 340
key_left_control* = 341
key_left_alt* = 342
key_left_super* = 343
key_right_shift* = 344
key_right_control* = 345
key_right_alt* = 346
key_right_super* = 347
key_menu* = 348
)
fn getMousePos
fn getMousePos*(): th.Vf2 {
Returns the position of mouse cursor in relation to the screen.
fn getGlobalMousePos
fn getGlobalMousePos*(): th.Vf2 {
Returns the position of mouse cursor in relation to cam.
fn isPressed
fn isPressed*(code: int): bool {
Returns true if key is pressed. Either use codes defined in the file above, or pass lower case char/number.
fn isPressedc
fn isPressedc*(code: char): bool {
Like isPressed
, but you can pass char as the code.
fn isJustPressed
fn isJustPressed*(code: int): bool {
Returns, whether code was just pressed this loop.
fn isJustPressedc
fn isJustPressedc*(code: char): bool {
Like isJustPressed
, but you can pass char as the code.
fn isPressedRepeat
fn isPressedRepeat*(code: int): bool {
Returns, whether code was just pressed this loop, with key repeat.
fn isPressedRepeatc
fn isPressedRepeatc*(code: char): bool {
Like isPressedRepeat
, but you can pass char as the code.
fn isJustReleased
fn isJustReleased*(code: int): bool {
Returns true if a key was just released.
fn isJustReleasedc
fn isJustReleasedc*(code: char): bool {
Like isJustReleased
, but you can pass char as the code.
fn clear
fn clear*(code: int) {
Clears both the pressed and justPressed state of a code.
fn clearc
fn clearc*(code: char) {
Like clear
, but you can pass char as the code.
fn getStr
fn getStr*(): str {
Returns a string entered by the user in the last cycle.
fn getMouseDelta
fn getMouseDelta*(): th.Vf2 {
Returns the difference between mouse positions in the last cycle. Will work
even if window.freezeCursor
is enabled.
fn getMouseScroll
fn getMouseScroll*(): th.Vf2 {
Returns the scroll wheel value
Particles allow for performant and random particle systems.
struct Particle
type Particle* = struct {
Particle struct. You can tweak the start_time for godot-like explossivness.
struct Emitter
type Emitter* = struct {
pos: th.Vf2 // position
dm: th.Vf2 // size of the emittion area
gravity: th.Vf2 // gravity
repeat: bool // if false, particle won't be renewed
active: bool // false, if there aren't any active particles anymore
angle: th.Vf2 // angle in which particles are emitted
lifetime: th.uu // lifetime of particles
lifetimeRandomness: th.fu // randomness in %/100
velocity: th.fu // velocity
velocityRandomness: th.fu // randomness in %/100
size: th.fu // size
sizeRandomness: th.fu // randomness in %/100
maxSize: th.fu // size at the end of particles lifetime
rotation: th.fu
rotationRandomness: th.fu
maxRotation: th.fu
colors: []uint32 // array of colors, which are interpolated between
particles: []Particle // list of particles
}
Emitter. This is where everything is configured.
fn Emitter.draw
fn (e: ^Emitter) draw*(t: int32) {
Draws and updates the particles.
fn Emitter.genParticles
fn (e: ^Emitter) genParticles*(time, count: uint, explosiveness: th.fu = 0.0) {
Generates particles for an emitter. The time specifies the time the first
particles is emitted. The explosiveness argument specifies the interval at
which particles are emitted using this formula:
/ umka / e.lifetime / count * explosiveness /
Builtin collision functions. The ic argument stores the collision position.
fn lineToLine
fn lineToLine*(b1, e1, b2, e2: th.Vf2, ic: ^th.Vf2): bool {
Checks for a collision between 2 lines specified by their end points.
fn vf2ToQuad
fn vf2ToQuad*(p: th.Vf2, q: th.Quad, ic: ^th.Vf2): bool {
Checks for a collision between a vf2 and a quad.
fn lineToQuad
fn lineToQuad*(b, e: th.Vf2, q: th.Quad, ic: ^th.Vf2): bool {
Check for a collision between a line and quad edges.
fn quadToQuad
fn quadToQuad*(q1, q2: th.Quad, ic: ^th.Vf2): bool {
Check for a collision between two quads.
fn vf2ToRect
fn vf2ToRect*(p: th.Vf2, r: rect.Rect): bool {
Check for a collision between a vf2 and a rectangle.
fn rectToRect
fn rectToRect*(r1, r2: rect.Rect): bool {
Check for a collision between two rects
struct Ent
type Ent* = struct {
// used as a collider, used as backup when invalid image is supplied
r: rect.Rect
// used in drawing
i: image.Image
// used to transform and translate the image and rect
t: th.Transform
// used as a color of the rect and a color filter for the image
c: uint32
}
Entity is the main game object. It features drawing and collisions. Every entity has an image used for drawing and a rectangle used for collisions. It also has a transform used for transforming it's image and rectangle.
struct Coll
type Coll* = struct {
index: th.uu
pos: th.Vf2
}
Value returned by get coll. It contains a position where the collision happened and the index of the entity involved in said collision.
fn Ent.draw
fn (e: ^Ent) draw*() {
Draws the entity onto the screen.
fn mk
fn mk*(img: image.Image = image.Image{}, t: th.Transform = th.Transform{ s: th.Vf2{1, 1} }): Ent {
ent's constructor
fn Ent.getColl
fn (e: ^Ent) getColl*(s: []^Ent, maxColls: th.uu): []Coll {
Checks collisions of e with entities in s. Checks at max maxColl collisions. If s contains e, the collision won't be returned.
fn Ent.animate
fn (e: ^Ent) animate*(fps: int, frames: ^[]image.Image, t: int) {
Animates the entity's image with one of the anim
array. Won't begin on
the first frame. If you want that, use anim.Anim.
Misc functions.
fn readall
fn readall*(path: str): str {
Reads file content into a string.
fn stepify
fn stepify*(val, step: th.fu): th.fu {
Snaps a value to step.
fn maxf
fn maxf*(a, b: th.fu): th.fu {
fn minf
fn minf*(a, b: th.fu): th.fu {
struct Mesh
type Mesh* = struct {
// The mesh data.
d: []bool
// The dimensions and position of the mesh, r.w == w * s
r: rect.Rect
// Width of the mesh. Used to adress the mesh data.
w: th.uu
// Scale of one cell (cell is a square)
s: th.fu
}
Mesh is a 2d array of bool cells. If a cell is true, the cell can be used
in a path. The mesh is located in a world using the r
field. A cell can
have an arbitrary size as specified by s
.
The mesh can be edited using the addQuad
method, but it should be trivial
to add your own, similar methods.
Please use the mk
constructor to construct a Mesh
, unless you really
know what you're doing.
fn mk
fn mk*(r: rect.Rect, s: th.fu): Mesh {
Creates a new nav mesh.
r
: the rectangle of the mask
's'
: the scale of the mask
fn Mesh.addQuad
fn (m: ^Mesh) addQuad*(q: th.Quad) {
Sets mask's fields overlapping q
to false
.
fn Mesh.nav
fn (m: ^Mesh) nav*(p1, p2: th.Vf2): []th.Vf2 {
Navigates between p1
and p2
. Returns the path as an array of th.Vf2
s.
If it doesn't find any path, or one of the points is outside of the mask,
returns an empty array.
fn Mesh.draw
fn (m: ^Mesh) draw*(alpha: real32 = 1.0) {
Draws the mesh for debugging purposes.
struct Ray
type Ray* = struct {
pos: th.Vf2
l: th.fu // length
r: th.fu // rotation
}
Ray is a line specified by an origin, length and a rotation.
fn mk
fn mk*(pos: th.Vf2, l: th.fu, r: th.fu = 0.0): Ray {
Ray constructor
fn Ray.getColl
fn (r: ^Ray) getColl*(s: []^ent.Ent, maxColls: th.uu): []ent.Coll {
Checks the ray's collisions with a scene of ents. Similar to
ent.Ent.getColl
fn Ray.getTilemapColl
fn (r: ^Ray) getTilemapColl*(t: tilemap.Tilemap, ic: ^th.Vf2): bool {
Gets ray's collision to a tilemap.
fn Ray.getEnd
fn (r: ^Ray) getEnd*(): th.Vf2 {
Returns the other point of the ray.
fn Ray.draw
fn (r: ^Ray) draw*(color: uint32, thickness: th.fu) {
Draws the ray to the screen.
struct Rect
type Rect* = struct {
x, y, w, h: th.fu
}
A set of points representing a rectangle.
fn mk
fn mk*(x, y, w, h: th.fu): Rect {
fn mk
fn fromVf2*(p: th.Vf2, dm: th.Vf2): Rect {
Creates a rect from two Vf2s - the position and the dimensions.
fn Rect.getPos
fn (r: ^Rect) getPos*(): th.Vf2 {
fn Rect.getDims
fn (r: ^Rect) getDims*(): th.Vf2 {
fn Rect.getEnd
fn (r: ^Rect) getEnd*(): th.Vf2 {
returns where the second point of the rectangle lies.
fn Rect.transformed
fn (r: ^Rect) transformed*(t: th.Transform): th.Quad {
Transforms a rect into a quad. Order:
- scale
- rotation
- position
fn Rect.shrink
fn (r: ^Rect) shrink*(p: th.Vf2): Rect {
Shrink the rectangle by p
pixels from all sides.
fn Rect.shift
fn (r: ^Rect) shift*(p: th.Vf2): Rect {
Shift the rectangle by p
pixels.
fn Rect.scale
fn (r: ^Rect) scale*(p: th.Vf2): Rect {
Multiply the dimensions by p
fn Rect.center
fn (r: ^Rect) center*(): th.Vf2 {
Returns the position, which is the center of the rect.
fn Rect.centerWithinRect
fn (r: ^Rect) centerWithinRect*(child: Rect): Rect {
Centers child
with the rect r
.
A module for importless communication between modules. A signal is a set of callbacks. You can use signals directly in your own structs if you want them to be instance specific, of you can use global signals which are adressed by a string name.
type Callback
type Callback* = fn(args: []any)
args
is a list of arguments passed to the emit
method.
type Id
type Id* = uint
type Signal
type Signal* = map[Id]Callback
fn mk
fn mk*(): Signal {
Signal
constructor
fn Signal.register
fn (this: ^Signal) register*(callback: Callback): Id {
Registers a callback to a signal and returns the callback id.
fn Signal.remove
fn (this: ^Signal) remove*(id: Id) {
Removes a callback by id.
fn Signal.emit
fn (this: ^Signal) emit*(args: ..any) {
Emits a signal.
fn Signal.clear
fn (this: ^Signal) clear*() {
Removes all signal handlers.
fn register
fn register*(name: str, callback: Callback): Id {
Registers a callback to a global signal. There is no need to explicitly create global signals. Returns id of the added callback
fn remove
fn remove*(name: str, id: Id) {
Removes a callback from a global signal by id.
fn remove
fn clear*(name: str) {
Removes all signal handlers from a global signal.
fn emit
fn emit*(name: str, args: ..any) {
Calls all callbacks associated with the passed global signal name.
Module with useful variables and types. Variables: time, delta, platform Constants: black, white, red, green, blue, yellow, magenta, cyan.
Tophat type aliases
type fu* = real32
// standard type for integer values
type iu* = int32
// standard type for unsigned values
type uu* = uint32
standard type for real values
struct Vf2
type Vf2* = struct {
x, y: fu
}
vector 2
fn mkVf2
fn mkVf2*(x: fu = 0, y: fu = 0): Vf2 {
return Vf2{x, y}
}
Vf2 constructor
fn Vf2.rotated
fn (p: ^Vf2) rotated*(origin: Vf2, rot: fu): Vf2 {
rotates p
around origin
with rot
in degrees
fn Vf2.distanceTo
fn (p1: ^Vf2) distanceTo*(p2: Vf2): fu {
distance between p1 and p2
fn Vf2.angleTo
fn (p1: ^Vf2) angleTo*(p2: Vf2): real {
Angle between p1 and p2
fn Vf2.abs
fn (p: ^Vf2) abs*(): Vf2 {
Absolute value of a vector.
fn Vf2.round
fn (p: ^Vf2) round*(): Vf2 {
Rounds a vector.
fn Vf2.trunc
fn (p: ^Vf2) trunc*(): Vf2 {
Truncates a vector.
fn Vf2.floor
fn (p: ^Vf2) floor*(): Vf2 {
Floors a vector.
fn Vf2.ceil
fn (p: ^Vf2) ceil*(): Vf2 {
Ceils a vector.
fn vf2f
fn vf2f*(f: fu): Vf2 {
Creates a vector with both x and y set to f
fn Vf2.sub
fn (p: ^Vf2) sub*(p2: Vf2): Vf2 {
Subtracts a vector from another one.
fn Vf2.subf
fn (p: ^Vf2) subf*(f: fu): Vf2 {
Subtracts a fu from a vector.
fn Vf2.add
fn (p: ^Vf2) add*(p2: Vf2): Vf2 {
Adds a vector to another one.
fn Vf2.addf
fn (p: ^Vf2) addf*(f: fu): Vf2 {
Adds a fu to a vector.
fn Vf2.div
fn (p: ^Vf2) div*(p2: Vf2): Vf2 {
Divides a vector by another one.
fn Vf2.divf
fn (p: ^Vf2) divf*(f: fu): Vf2 {
Divides a vector by a fu.
fn Vf2.mul
fn (p: ^Vf2) mul*(p2: Vf2): Vf2 {
Multiplies a vector by another one.
fn Vf2.mulf
fn (p: ^Vf2) mulf*(f: fu): Vf2 {
Multiplies a vector by a fu.
fn Vf2.mag
fn (p: ^Vf2) mag*(): fu {
Returns the magnitude of a vector p.
fn Vf2.norm
fn (p: ^Vf2) norm*(): Vf2 {
Normalizes a vector.
fn Vf2.dot
fn (p: ^Vf2) dot*(q: Vf2): fu {
Calculates dot product between 2 vectors.
struct Transform
type Transform* = struct {
p: Vf2 // position
s: Vf2 // scale
o: Vf2 // origin
r: fu // rotation
}
Struct defining transformation. Used for example by entities.
fn mkTransform
fn mkTransform*(p: Vf2, s: Vf2 = Vf2{1, 1}, o: Vf2 = Vf2{0, 0}, r: fu = 0.0): Transform {
Transform constructor
fn Transform.transformed
fn (o: ^Transform) transformed*(t: Transform): Transform {
Transforms a transform with another transform.
fn Vf2.transformed
fn (v: ^Vf2) transformed*(t: Transform): Vf2 {
Transforms a vf2 to another vf2. Order:
- scale
- rotation
- position
This allows conversion from a relative to an absolute vf2.
type Quad
type Quad* = [4]Vf2
fn Quad.getMax
fn (q: ^Quad) getMax*(): Vf2 {
Gets the maximum coordinate.
fn Quad.getMin
fn (q: ^Quad) getMin*(): Vf2 {
Gets the minimum coordinate.
fn Quad.getDims
fn (q: ^Quad) getDims*(): Vf2 {
Returns the dimensions of the quad's bounding box
fn getGlobal
fn getGlobal*(): ^struct{} {
returns a pointer to the th_global. Set this as your extensions thg.
fn getFuncs
fn getFuncs*(): ^struct{} {
returns pointer to tophat functions. Pass this to th_ext_set.
var enableErrrors
var enableErrors*: bool = true
If true, errors will result in a call to error(), otherwise printf is used.
Color constants
const (
black* = 0xff
white* = 0xffffffff
red* = 0xff0000ff
green* = 0x00ff00ff
blue* = 0x0000ffff
yellow* = 0xffff00ff
magenta* = 0xff00ffff
cyan* = 0x00ffffff
)
enum Platform
type Platform* = int
const (
PlatformUnknown* = 0
PlatformLinux*
PlatformWindows*
PlatformMacOs*
PlatformWeb*
)
Misc variables
var (
// time in ms from start of the game
time*: uint
// length of the last frame in ms
delta*: int
// platform tophat is running on
platform*: Platform
)
Tilemaps allow for easy level construction and fast collisions. You can even use them for some games instead of entities (tetris comes to mind)
Direction constants used for autotile
const (
top* = 1
right* = 2
bot* = 4
left* = 8
)
struct Tilemap
type Tilemap* = struct {
atlas: atlas.Atlas
pos: th.Vf2
w: th.uu // width of tilemap
cells: []th.uu // all cells (this will draw the tile in tiles with number in cells - 1)
collMask: []bool // if true, the tile collides
scale: th.fu
}
Tilemap struct
fn mk
fn mk*(cells: []th.uu, w: th.uu, at: atlas.Atlas, scale: th.fu = 1): Tilemap {
fn Tilemap.edit
fn (t: ^Tilemap) edit*(x, y, tile: int) {
Sets tile at [x, y] to tile.
fn Tilemap.draw
fn (t: ^Tilemap) draw*() {
Draws the tilemap.
fn Tilemap.getColl
fn (t: ^Tilemap) getColl*(e: ent.Ent, ic: ^th.Vf2, pos: ^th.Vf2): bool {
Checks whether e
collides with any of the tiles in t
, which are in the
collmask.
ic
[out] - the position where a collision occuredpos
[out] - coordinates of a tile where a collision occured
Note: While there may be multiple collisions with a tilemap, this function will only return one.
fn Tilemap.autotile
fn (t: ^Tilemap) autotile*(src, tileCfg: []th.uu, tile: th.uu) {
Autotile turns all tile
tiles in src
into tiles in tileCfg
, so they
follow up correctly. tileCfg
is an array of 16 tiles. They are placed in
a way where OR of all the places where the tile continues (top, right bot,
right). The constants for them are defined in this file. Example:
tileCfg[top | bot] = 21
top | bot would look something like this: |
ui.um
offers an immediate GUI library suitable both for simple game menus
and more complex applications. See the tutorial
for example usage.
struct BoxStyle
type BoxStyle* = struct {
img: image.Image
outer, inner: rect.Rect
scale: th.fu
color: uint32
}
BoxStyle
describes how a box within the GUI is styled. In this case box
can be anything, ranging from a button to a container. By default the box
is drawn using the image.Image.drawNinepatch
method. However if the image
is invalid, a rectangle with color color
is drawn.
interface Font
type Font* = interface {
draw(text: str, pos: th.Vf2, color: uint32, scale: th.fu = 1.0)
measure(test: str): th.Vf2
}
This interface is used by all elements that draw text. A font.Font
implements this interface.
struct PixelFont
type PixelFont* = struct { }
This struct implement the Font
interface using the canvas.um
pixel font.
struct Style
type Style* = struct {
// current font
ft: Font
// font scale
ftScale: th.fu
// text color
ftColor: uint32
// Positive box - i. e. unpressed button
posBox: BoxStyle
// Negative box - i. e. pressed button, text box
negBox: BoxStyle
// Used to draw containers
containerBox: BoxStyle
}
Style
is used as a global state for styling the GUI.
interface Container
type Container* = interface {
// This adds a rectangle to the container, and returns the rectangle
// which was actually added (the container can modify the rectangle).
// See individual containers for further documentation.
pushRect(r: rect.Rect): rect.Rect
getDims(): rect.Rect
}
Containers are used to layout elements or other containers.
struct Gui
type Gui* = struct {
// user context passed to layout functions
ctx: any
// the index of the current selection. TODO implement properly
selection: int
// true, if the layout is being evaluated, not drawn
isEval: bool
// contains more unexported fields
This is the main struct of any UI. Styles and containers are in a stack.
fn mk
fn mk*(r: rect.Rect, s: Style): Gui
Creates a new gui spanning r
, with style s
.
type LayoutFn
type LayoutFn* = fn(gui: ^Gui)
The layout function calls different element or container methods to create
the user interface itself. It is called in the eval
and draw
.
fn BoxStyle.draw
fn (this: ^BoxStyle) draw*(r: rect.Rect) {
Draws a rectangle using a BoxStyle
fn Gui.pushStyle
fn (this: ^Gui) pushStyle*(s: Style) {
Pushes a style onto the style stack.
fn Gui.popStyle
fn (this: ^Gui) popStyle*() {
Pops a style from the style stack.
fn Gui.getStyle
fn (this: ^Gui) getStyle*(): ^Style {
Returns a pointer to the style atop the style stack.
fn Gui.getContainer
fn (this: ^Gui) getContainer*(): Container {
Returns the container atop the container stack.
fn Gui.pushRect
fn (this: ^Gui) pushRect*(r: rect.Rect): rect.Rect {
Shortcut to this.getContainer().pushRect(r)
fn Gui.getDims
fn (this: ^Gui) getDims*(): rect.Rect {
Shortcut to this.getContainer().getDims(r)
fn Gui.dupStyle
fn (this: ^Gui) dupStyle*() {
Duplicates the current style.
fn Gui.pushContainer
fn (this: ^Gui) pushContainer*(c: Container) {
Pushes a container onto the container stack.
fn Gui.popContainer
fn (this: ^Gui) popContainer*() {
Pops a container from the container stack.
fn Gui.eval
fn (this: ^Gui) eval*(layout: LayoutFn) {
Runs the evaluation phase on layout
.
fn Gui.draw
fn (this: ^Gui) draw*(layout: LayoutFn) {
Runs the draw phase on layout
.
enum BoxGrow
type BoxGrow* = uint
const (
// Increments by an amount set by the user.
BoxGrowDimension* = BoxGrow(0)
// Divides the container into `n` equal parts.
BoxGrowSubdivision* = BoxGrow(1)
)
The different types of "growing" the box can do.
enum BoxDirection
type BoxDirection* = uint
const (
BoxDirectionDown* = BoxDirection(0)
BoxDirectionRight* = BoxDirection(1)
BoxDirectionUp* = BoxDirection(2)
BoxDirectionLeft* = BoxDirection(3)
)
Direction in which the box will grow.
struct BoxConfig
type BoxConfig* = struct {
// dimension to grow by if `BoxGrowDimension` is used
dimension: th.fu
// number of subdivisions if `BoxGrowSubdivisions` is used
subdivisions: uint
// the grow type
growType: BoxGrow
// the grow direction
dir: BoxDirection
// rect passed to the current container
rect: rect.Rect
// padding inserted after each element
padding: th.fu
}
Configuration of the Box
container.
struct Box
type Box* = struct {
grow: th.fu
dm: rect.Rect
cfg: BoxConfig
}
Box
is the main layout. It puts the elements next to each other,
according to the config.
If the dimensions of the rect passed to pushRect
are non zero, they will
be kept. Position is always forced.
fn Gui.box
fn (gui: ^Gui) box*(cfg: BoxConfig = {
dimension: 30,
growType: BoxGrowDimension,
dir: BoxDirectionDown }) {
Adds the Box
container to the gui.
struct StackConfig
type StackConfig* = struct {
rect: rect.Rect
padding: th.fu
}
Configuration for the Stack
container.
struct Stack
type Stack* = struct {
dm: rect.Rect
cfg: StackConfig
}
The stack container puts elements on top of each other.
If a property of the rect passed to pushRect
is zero, it will be changed
to an equivalent property of the containers' dimensions (minus the padding),
else it will stay the same. This means stack can be used either to put
multiple elements/containers on top of each other, or for absolutely
positioned elements.
fn Gui.stack
fn (gui: ^Gui) stack*(cfg: StackConfig = {}) {
Adds the Stack
container to the gui.
struct ScrollAreaConfig
type ScrollAreaConfig* = struct {
rect: rect.Rect
// scroll speed. Default is 1
speed: real32
// if true, scrolling will be horizontal
horizontal: bool
}
Configuration for the scroll area.
struct ScrollArea
type ScrollArea* = struct {
dm: rect.Rect
cfg: ScrollAreaConfig
scroll: ^real32
maxScroll: real32
}
Scroll area is a container which allows the user to scroll. It acts as a stack container, but all the elements are shifted by the scroll.
fn Gui.scrollArea
fn (gui: ^Gui) scrollArea*(scroll: ^real32, maxScroll: real32, cfg: ScrollAreaConfig = {}) {
Pushes a scroll area. scroll
is both input and output value. Both scroll
and maxScroll
are in pixels.
struct ButtonConfig
type ButtonConfig* = struct {
rect: rect.Rect
}
Configuration for the button.
fn Gui.button
fn (gui: ^Gui) button*(cfg: ButtonConfig = {}): bool {
Adds a button to the gui. The button acts like a Stack
container, but it
is drawn using the pos/nexBox styles and handles clicks. If the button is
pressed and the gui is in the eval phase, the return value will be true.
struct LabelConfig
type LabelConfig* = struct {
// centers the label along the X axis, enables `stretchX`
centerX: bool
// centers the label along the Y axis, enables `stretchY`
centerY: bool
// if false, the rect passed to `pushRect` will have the width of
// the text, else it will be 0
stretchX: bool
// if false, the rect passed to `pushRect` will have the height of
// the text, else it will be 0
stretchY: bool
// forces the rectangle the label will use
rect: rect.Rect
}
fn Gui.label
fn (gui: ^Gui) label*(text: str, cfg: LabelConfig = {
Draws a label using the current font style.
fn Gui.qbutton
fn (gui: ^Gui) qbutton*(text: str, cfg: ButtonConfig = {}): bool {
Adds a button with a label to gui.
struct TextBoxConfig
type TextBoxConfig* = struct {
// force the rect of the text box
rect: rect.Rect
}
struct TextBox
type TextBox* = struct {
// the content of the textbox
buffer: str
// index of the cursor
cursor: int
}
fn TextBox.clear
fn (this: ^TextBox) clear*() {
this.buffer = ""
this.cursor = 0
}
Clears the textbox
gui.idx++
r := gui.pushRect(cfg.rect)
hover := gui.hover(r)
if gui.isEval {
if input.isJustPressed(input.mouse1) && hover {
gui.selection = gui.idx
}
if input.isJustPressed(input.mouse1) && !hover &&
gui.selection == gui.idx {
gui.selection = 0
}
if input.isJustPressed(input.key_escape) && gui.selection == gui.idx {
gui.selection = 0
}
if gui.selection != gui.idx {
return
}
if input.isJustPressed(input.key_left) ||
input.isPressedRepeat(input.key_left) {
if tb.cursor > 0 { tb.cursor-- }
}
if input.isJustPressed(input.key_right) ||
input.isPressedRepeat(input.key_right) {
if tb.cursor < len(tb.buffer) { tb.cursor++ }
}
if input.isJustPressed(input.key_backspace) ||
input.isPressedRepeat(input.key_backspace) {
if tb.cursor > 0 {
tb.buffer = slice(tb.buffer, 0, tb.cursor - 1) +
slice(tb.buffer, tb.cursor)
tb.cursor--
}
}
v := true
if input.isPressed(input.key_ctrl) && input.isJustPressedc('u') {
tb.clear()
v = false
}
s := input.getStr()
for i in s {
if int(s[i]) < int(' ') || int(s[i]) == int('z') + 1 {
v = false
}
}
if len(s) > 0 && v {
tb.buffer = slice(tb.buffer, 0, tb.cursor) + s +
slice(tb.buffer, tb.cursor)
tb.cursor += len(s)
}
return
}
style := gui.getStyle()
style.negBox.draw(r)
canvas.beginScissorRect(r)
dm := style.ft.measure(tb.buffer).mulf(style.ftScale)
p := th.Vf2{}
p.y = r.y + r.h/2 - dm.y/2
c := th.Vf2{r.x, p.y}
cdmx := style.ft.measure(slice(tb.buffer, 0, tb.cursor)).x * style.ftScale
if cdmx < r.w - 2 {
p.x = r.x + 1
c.x = p.x + cdmx
} else {
c.x = r.x + r.w - 1
p.x = c.x - cdmx
}
aW := style.ft.measure("A").x * style.ftScale
style.ft.draw(tb.buffer, p, style.ftColor, style.ftScale)
if gui.selection == gui.idx {
canvas.drawRect(style.ftColor, rect.mk(c.x, c.y, aW / 4, dm.y))
}
canvas.endScissor()
}
if cfg.centerX { cfg.stretchX = true }
if cfg.centerY { cfg.stretchY = true }
r := cfg.rect
dm := i.getDims().mul(cfg.scale)
if r.w == 0 { r.w = dm.x }
if r.h == 0 { r.h = dm.y }
if cfg.stretchX { r.w = 0 }
if cfg.stretchY { r.h = 0 }
r = gui.pushRect(r)
if gui.isEval { return }
x := r.x
y := r.y
if cfg.centerX {
x += (r.w - dm.x) / 2
}
if cfg.centerY {
y += (r.h - dm.y) / 2
}
i.draw(th.Transform{
p: { x, y },
s: cfg.scale }, cfg.color)
}
Cursor types
const (
cursorDefault* = 0 // Default system cursor
cursorArrow* // Normal cursor; Arrow cursor
cursorIBeam* // 'I' text cursor; I-Beam
cursorCrosshair* // '+' cursor; Select region cursor
cursorFinger* // Index finger pointing cursor; Click cursor
cursorSizeEW* // '<->' cursor; Resize width cursor; Resize horizontally cursor; East-West resize cursor
cursorSizeNS* // Resize height cursor; Resize vertically cursor; North-South resize cursor
cursorSizeNWSE* // Resize width and height from the right side cursor; Northwest-Southeast resize cursor
cursorSizeSWNE* // Resize width and height from the left side cursor; Southwest-Northeast resize cursor
cursorSizeAll* // Resize all cursor; Move cursor
cursorNo* // '(/)' cursor; Disabled cursor; Disallowed cursor
cursorCount_*
)
Window dimensions
var (
w*, h*: int32
)
Viewport size
var wp*: th.Vf2
signal OnFrame
var onFrame*: signal.Signal
signal OnDestroy
var onDestroy*: signal.Signal
fn setViewport
fn setViewport*(dm: th.Vf2) {
Sets the dimensions of the viewport. The dimensions are saved in the wp
variable.
dm
: dimension of the viewport
fn isDpiEnabled
fn isDpiEnabled*(): bool {
Returns true if DPI awareness was enabled
fn getDpiScaleFactor
fn getDpiScaleFactor*(): th.fu {
Returns the DPI scaling of the current window.
If dpiAware
was not enabled in window setup, this function will return 1.0 (default scaling).
fn setup
fn setup*(title: str = "tophat game", width: int = 400, height: int32 = 400) {
Sets up the engine and opens a window.
fn cycle
fn cycle(delta: real) {
Cycle needs to be called every cycle for the window to work. If the window was closed, it returns false.
fn setFullscreen
fn setFullscreen*(fullscreen: bool) {
Makes window go full screen
fn isFullscreen
fn isFullscreen*(): bool {
Returns true if window is fullscreen
fn getDims
fn getDims*(): th.Vf2 {
Returns dimensions of the window in screen pixels.
fn setTargetFps
fn setTargetFps*(fps: int) {
Sets the fps limit.
fps
: amount of fps the limit should be set to
fn setDims
fn setDims*(dm: th.Vf2) {
Sets the dimensions of the window.
dm
: the target dimensions in screen pixels
fn setIcon
fn setIcon*(img: image.Image) {
Sets the window icon.
fn showCursor
fn showCursor*(show: bool) {
Show or hide the cursor, linux only.
fn freezeCursor
fn freezeCursor*(freeze: bool) {
Freezes the cursor in place. input.getMouseDelta
will still report mouse
movements. The cursor will be automatically hidden.
fn setCursor
fn setCursor*(cursor: int) {
Allows you to set the displaying cursor. Refer to the cursors section for available cursors.
fn quit
fn quit*() {
Exits the application. Use this instead of umka's default exit
.
fn setClipboard
fn setClipboard*(s: str) {
Puts a string to the system clipboard.
fn getClipboard
fn getClipboard*(): str {
Gets a string from the system clipboard.
fn setViewportOffset
fn setViewportOffset*(s: th.Vf2) {
Sets the offset of the viewport.
fn getViewportOffset
Gets the offset of the viewport (as set by setViewportShift
)