Library: app.h
Here's a basic sample program which starts a windowed app and plots random pixels.
#define APP_IMPLEMENTATION
#define APP_WINDOWS
#include "app.h"
#include <stdlib.h> // for rand and __argc/__argv
#include <string.h> // for memset
int app_proc( app_t* app, void* )
{
APP_U32 canvas[ 320 * 200 ]; // a place for us to draw stuff
memset( canvas, 0xC0, sizeof( canvas ) ); // clear to grey
app_screenmode( app, APP_SCREENMODE_WINDOW );
// keep running until the user close the window
while( app_yield( app ) != APP_STATE_EXIT_REQUESTED )
{
// plot a random pixel on the canvas
int x = rand() % 320;
int y = rand() % 200;
int color = rand() | ( rand() << 16 );
canvas[ x + y * 320 ] = color;
// display the canvas
app_present( app, canvas, 320, 200, 0xffffff, 0x000000 );
}
return 0;
}
int main( int argc, char** argv )
{
(void) argc, argv;
return app_run( app_proc, NULL, NULL, NULL, NULL );
}
// pass-through so the program will build with either /SUBSYSTEM:WINDOWS or /SUBSYSTEN:CONSOLE
extern "C" int __stdcall WinMain( struct HINSTANCE__*, struct HINSTANCE__*, char*, int ) { return main( __argc, __argv ); }
app.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it,
you just include app.h to get the API declarations. To get the definitions, you must include app.h from one single
C or C++ file, and #define the symbol APP_IMPLEMENTATION
before you do.
As app.h is meant to be a cross platform library (even though only windows is supported right now), you must also define which platform you are running on, like this:
#define APP_IMPLEMENTATION
#define APP_WINDOWS
#include "app.h"
There are a few different things in app.h which are configurable by #defines. Most of the API use the int
data type,
for integer values where the exact size is not relevant. However, for some functions, it specifically makes use of 16,
32 and 64 bit data types. These default to using short
, unsigned int
and unsigned long long
by default, but can be
redefined by #defining APP_S16, APP_U32 and APP_U64 resepectively, before including app.h. This is useful if you, for
example, use the types from <stdint.h>
in the rest of your program, and you want app.h to use compatible types. In
this case, you would include app.h using the following code:
#define APP_S16 int16_t
#define APP_U32 uint32_t
#define APP_U64 uint64_t
#include "app.h"
Note that when customizing the data types, you need to use the same definition in every place where you include app.h, as they affect the declarations as well as the definitions.
The rest of the customizations only affect the implementation, so will only need to be defined in the file where you have the #define APP_IMPLEMENTATION.
Even though app.h attempts to minimize the memory use and number of allocations, it still needs to make some use of
dynamic allocation by calling malloc
. Programs might want to keep track of allocations done, or use custom defined
pools to allocate memory from. app.h allows for specifying custom memory allocation functions for malloc
and free
.
This is done with the following code:
#define APP_IMPLEMENTATION
#define APP_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) )
#define APP_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) )
#include "app.h"
where my_custom_malloc
and my_custom_free
are your own memory allocation/deallocation functions. The ctx
parameter
is an optional parameter of type void*
. When app_run
is called, you can pass in a memctx
parameter, which can be a
pointer to anything you like, and which will be passed through as the ctx
parameter to every APP_MALLOC/APP_FREE call.
For example, if you are doing memory tracking, you can pass a pointer to your tracking data as memctx
, and in your
custom allocation/deallocation function, you can cast the ctx
param back to the right type, and access the tracking
data.
If no custom allocator is defined, app.h will default to malloc
and free
from the C runtime library.
There's a bunch of things being logged when app.h runs. It will log an informational entry with the date and time for when the app is started and stopped, it will log warnings when non-essential initialization fails, and it will log error messages when things go wrong. By default, logging is done by a simple printf to stdout. As some applications may need a different behavior, such as writing out a log file, it is possible to override the default logging behavior through defines like this:
#define APP_IMPLEMENTATION
#define APP_LOG( ctx, level, message ) ( my_log_func( ctx, level, message ) )
#include "app.h"
where my_log_func
is your own logging function. Just like for the memory allocators, the ctx
parameter is optional,
and is just a void*
value which is passed through. But in the case of logging, it will be passed through as the value
off the logctx
parameter passed into app_run
. The level
parameter specifies the severity level of the logging,
and can be used to direct different types of messages to different logging systems, or filter out messages of certain
severity level, e.g. supressing informational messages.
As the app.h library works on the lowest level of your program, interfacing directly with the operating system, there might occur errors which it can not recover from. In these cases, a fatal error will be reported. By default, when a fatal error happens, app.h will print a message to stdout, show a messagebox to the user, and force exit the program.
It is possible to change this behaviour using the following define:
#define APP_IMPLEMENTATION
#define APP_FATAL_ERROR( ctx, message ) ( my_custom_fatal_error_func( ctx, message ) )
#include "app.h"
where my_custom_fatal_error_func
is your own error reporting function. The ctx
parameter fills the same purpose as
for the allocator and logging functions, but here it is the fatalctx
parameter to app_run
which is passed through.
int app_run( int (*app_proc)( app_t*, void* ), void* user_data, void* memctx, void* logctx, void* fatalctx )
Creates a new app instance, calls the given app_proc and waits for it to return. Then it destroys the app instance.
- app_proc - function pointer to the user defined starting point of the app. The parameters to that function are:
app_t* a pointer to the app instance. This is an opaque type, and it is passed to all other functions in the API.
void* pointer to the user defined data that was passed as the
user_data
parameter toapp_run
. - user_data - pointer to user defined data which will be passed through to app_proc. May be NULL.
- memctx - pointer to user defined data which will be passed through to custom APP_MALLOC/APP_FREE calls. May be NULL.
- logctx - pointer to user defined data to be passed through to custom APP_LOG calls. May be NULL.
- fatalctx - pointer to user defined data to be passed through to custom APP_FATAL_ERROR calls. May be NULL.
When app_run is called, it will perform all the initialization needed by app.h, and create an app_t*
instance. It will
then call the user-specified app_proc
, and wait for it to return. The app_t*
instance will be passed to app_proc
,
and can be used to call other functions in the API. When returning from app_proc
, a return value is specified, and
app_run
will perform termination and cleanup, and destroy the app_t*
instance, and then return the same value it got
from the app_proc
. After app_run
returns, the app_t*
value is no longer valid for use in any API calls.
app_state_t app_yield( app_t* app )
Allows for app.h and the operating system to perform internal house keeping and updates. It should be called on each iteration of your main loop.
The return value can be either APP_STATE_NORMAL
or APP_STATE_EXIT_REQUESTED
. APP_STATE_EXIT_REQUESTED
means that
the user have requested the app to terminate, e.g. by pressing the close button on the window, and the user defined
app_proc
needs to handle this, by either returning (to signal that the app should terminate) or by calling
app_cancel_exit
to ignore the request. A typical pattern is to display a message to the user to confirm that the app
should exit. In the case of APP_STATE_NORMAL
, there is no need to do anything.
void app_cancel_exit( app_t* app )
Used to reset the APP_STATE_EXIT_REQUESTED
state. See app_yield
for details.
void app_title( app_t* app, char const* title )
Sets the name of the application, which is displayed in the task switcher and in the title bar of the window.
char const* app_cmdline( app_t* app )
Returns the command line string used to launch the executable. This can be parsed to get command line arguments.
char const* app_filename( app_t* app )
Returns the full filename and path of the executable. The first part of app_cmdline
usually contains the name of the
executable, but not necessarily the full path, depending on how it was launched. app_filename
, however, always returns
the full path.
char const* app_userdata( app_t* app )
Returns the full path to a directory where a users personal files can be stored. Depending on the access rights of the
user, it may or may not be possible to write data to the same location as the executable, and instead it must be stored
in a specific area designated by the operating system. app_userdata
returns the path to the root if that directory.
A typical use for this is to store the users savegame files, by creating a subfolder corresponding to your app, and save
the data there.
char const* app_appdata( app_t* app )
Returns the full path to a directory where application specific files can be stored. Similar to the location returned by
app_userdata
, but suitable for application data shared between users. Typical use for this is to store the result of
cached calculations or temporary files.
APP_U64 app_time_count( app_t* app )
Returns the current value of the high precision clock. The epoch is undefined, and the resolution can vary between
systems. Use app_time_freq
to convert to seconds. Typical use is to make two calls to app_time_count
and calculate
the difference, to measure the time elapsed between the two calls.
APP_U64 app_time_freq( app_t* app )
Returns the number of clock ticks per second of the high precision clock. An example use case could be:
APP_U64 current_count = app_time_count( app );
APP_U64 delta_count = current_count - previous_count;
double delta_time = ( (double) delta_count ) / ( (double) app_time_freq( app ) );
previous_count = current_count;
to measure the time between two iterations through your main loop.
void app_log( app_t* app, app_log_level_t level, char const* message )
app.h will do logging on certain events, e.q when the app starts and ends or when something goes wrong. As the logging
can be customized (see section on customization), it might be desirable for the program to do its own logging the same
way as app.h does it. By calling app_log
, logging will be done the same way as it is done inside app.h, whether custom
logging or default logging is being used.
void app_fatal_error( app_t* app, char const* message )
Same as with app_log, but for reporting fatal errors, app_fatal_error
will report an error the same way as is done
internally in app.h, whether custom or default fatal error reporting is being used.
void app_pointer( app_t* app, int width, int height, APP_U32* pixels_abgr, int hotspot_x, int hotspot_y )
Sets the appearence current mouse pointer. app_pointer
is called with the following parameters:
- width, height - the horizontal and vertical dimensions of the mouse pointer bitmap.
- pixels_abgr - width x height number of pixels making up the pointer bitmap, each pixel being a 32-bit unsigned integer where the highest 8 bits are the alpha channel, and the following 8-bit groups are blue, green and red channels.
- hotspot_x, hotspot_y - offset into the bitmap of the pointer origin, the center point it will be drawn at.
void app_pointer_default( app_t* app, int* width, int* height, APP_U32* pixels_abgr, int* hotspot_x, int* hotspot_y )
Retrieves the width, height, pixel data and hotspot for the default mouse pointer. Useful for restoring the default
pointer after using app_pointer
, or for doing software rendered pointers. Called with the following parameters:
- width, height - pointers to integer values that are to receive the width and height of the pointer. May be NULL.
- pixels_abgr - width x height number of pixels to receive the pointer bitmap. May be NULL
- hotspot_x, hotspot_y - pointers to integer values that are to receive the hotspot coordinates. May be NULL.
A typical pattern for calling app_pointer_default
is to first call it with pixels_abgr
as NULL, to query the bitmaps
dimensions, and then call it again after preparing a large enough memory area.
void app_pointer_pos( app_t* app, int x, int y )
Set the position of the mouse pointer, in window coordinates. The function app_coordinates_bitmap_to_window
can be
used to convert between the coordinate system of the currently displayed bitmap and that of the window.
void app_pointer_limit( app_t* app, int x, int y, int width, int height )
Locks the mouse pointer movements to stay within the specified area, in window coordinates. The function
app_coordinates_bitmap_to_window
can be used to convert between the coordinate system of the currently displayed
bitmap and that of the window.
void app_pointer_limit_off( app_t* app )
Turns of the mouse pointer movement restriction, allowing the pointer to be moved freely again.
void app_interpolation( app_t* app, app_interpolation_t interpolation )
app.h supports two different modes of displaying a bitmap. When using APP_INTERPOLATION_LINEAR
, the bitmap will be
drawn with bilinear interpolations, stretching it to fill the window (maintaining aspect ratio), giving it a smooth, if
somwhat blurry, look. With APP_INTERPOLATION_NONE
, scaling will only be done on whole pixel ratios, using no
interpolation, which is particularly suitable to maintain the clean, precise look of pixel art.
APP_INTERPOLATION_LINEAR
is the default setting.
void app_screenmode( app_t* app, app_screenmode_t screenmode )
Switch between windowed mode and fullscreen mode. APP_SCREENMODE_WINDOW
is used to select windowed mode, and
APP_SCREENMODE_FULLSCREEN
is used to switch to fullscreen mode. APP_SCREENMODE_FULLSCREEN
is the default. Note that
the app.h fullscreenmode is of the "borderless windowed" type, meaning that fullscreen mode just means that the window
is set to cover the entire screen, and its borders are hidden. It does not imply any exclusive locking of GPU resources.
When switching from windowed to fullscreen mode on a multi-display system, the app will go fullscreen on the display
that the window is currently on.
void app_window_size( app_t* app, int width, int height )
Sets the size of the window. If currently in APP_SCREENMODE_FULLSCREEN
screen mode, the setting will not take effect
until switching to APP_SCREENMODE_WINDOW
. width
and height
specifies the size of the windows client area, not
counting borders, title bar or decorations.
int app_window_width( app_t* app )
int app_window_height( app_t* app )
Returns the current dimensions of the window (which might have been resized by the user). Regardless of whether the app
is currently in fullscreen or windowed mode, app_window_width
and app_window_height
returns the dimension the window
would have in windowed mode. Width and height specifies the size of the windows client area, not counting borders,
title bar or decorations.
void app_window_pos( app_t* app, int x, int y )
Sets the position of the top left corner of the window. If currently in APP_SCREENMODE_FULLSCREEN
screen mode, the
setting will not take effect until switching to APP_SCREENMODE_WINDOW
.
int app_window_x( app_t* app )
int app_window_y( app_t* app )
Returns the current position of the windows top left corner. Regardless of whether the app is currently in fullscreen or
windowed mode, app_window_x
and app_window_y
returns the position the window would have in windowed mode.
app_displays_t app_displays( app_t* app )
Returns a list of all displays connected to the system. For each display, the following fields are reported:
- id - a platform specific string used to identify the display. Useful for saving which display was in use.
- x, y - position of the top left corner of the display, relative to the primary dispay which is always at 0,0.
- width, height - size of the display, in pixels.
void app_present( app_t* app, APP_U32 const* pixels_xbgr, int width, int height, APP_U32 mod_xbgr, APP_U32 border_xbgr )
app.h provides a very minimal API for drawing - the only thing you can really do, is provide it with a bitmap for it to
display on the screen. It is then up to the rest of your program to implement code for drawing shapes or sprites onto
that bitmap. When all your drawing is done, you call app_present
passing it the bitmap, and it will be displayed on
the screen. app_present
takes the following parameters:
- pixels_xbgr - width x height number of pixels making up the bitmap to be presented, each pixel being a 32-bit unsigned integer where the highest 8 bits are not used, and the following 8-bit groups are blue, green and red channels. This parameter may be NULL, in which case no bitmap is drawn, to allow for custom rendering. See below for details.
- width, height - the horizontal and vertical dimensions of the bitmap
- mod_xbgr - an rgb color value which will be automatically multiplied with each pixel, component by component, before it is displayed. Can be used to for example fade the bitmap to/from black. Set to 0xffffff for no effect.
- border_xbgr - an rgb color value to be used as border color. The borders are the areas outside of the bitmap, which are visible when the window aspect ratio does not match that of the bitmap, so you get bars above or below it.
Since app.h uses opengl, you can also opt to not pass a bitmap to app_present
, by passing NULL as the pixels_xbgr
parameter (in which case the rest of the parameters are ignored). When doing this, it is up to your program to perform
drawing using opengl calls. In this case app_present
will work as a call to SwapBuffers only. Note that glSetViewPort
will be automatically called whenever the window is resized.
void app_sound_buffer_size( app_t* app, int sample_pairs_count )
The api for playing sound samples is just as minimal as that for drawing. app.h provides a single, looping sound stream,
and it is up to your program to handle sound formats, voices and mixing. By calling app_sound_buffer_size
, a sound
stream of the specified size is initialized, and playback is started. If a sample_pairs_count
of 0 is given, sound
playback will be stopped.
The sound buffer is in 44100hz, signed 16-bit stereo format, and the sample_pairs_count
specified how many left/right
pairs of 16-bit samples the buffer will contain. As an example, to specify a 1 second stream buffer, you would give the
value 44100 for the sample_pairs_count
parameter, which would internally create a sound buffer of 176,400 bytes, from
44100 samples x 2 channels x 2 bytes per sample. However, since the bits per sample and number of channels are fixed,
the exact byte size of the sound buffer is not important in the app.h API.
int app_sound_position( app_t* app )
Returns the current playback position of the sound stream, given in the number of sample pairs from the start of the buffer. Typical use of a streaming sound buffer is to fill the buffer with data, wait for the playback position to get passed the mid point of the buffer, and then write the next bit of data over the part that has already been played, and so on.
void app_sound_write( app_t* app, int sample_pairs_offset, int sample_pairs_count, APP_S16 const* sample_pairs )
Writes sample data to the sound buffer. It takes the following parameters:
- sample_pairs_offset - the offset into the buffer of where to start writing, in number of sample pairs from the start.
- sample_pairs_count - the number of sample pairs to write. Must be less than the buffer size.
- sample_pairs - an array of sound samples, as signed 16-bit integers. Must be at least
sample_pairs_count
in length.
The sample_pairs
parameter can be NULL, in which case the corresponding part of the buffer is cleared.
void app_sound_volume( app_t* app, float volume )
Sets the output volume level of the sound stream, as a normalized linear value in the range 0.0f to 1.0f, inclusive.
app_input_t app_input( app_t* app )
Returns a list of input events which occured since the last call to app_input
. Each input event can be of one of a
list of types, and the type
field of the app_input_event_t
struct specifies which type the event is. The data
struct is a union of fields, where only one of them is valid, depending on the value of type
:
- APP_INPUT_KEY_DOWN, APP_INPUT_KEY_UP, APP_INPUT_DOUBLE_CLICK - use the
key
field of thedata
union, which contains one of the keyboard key identifiers from theapp_key_t
enumeration.APP_INPUT_KEY_DOWN
means a key was pressed, or that it was held long enough for the key repeat to kick in.APP_INPUT_KEY_UP
means a key was released, and is not sent on key repeats. For both these events, akey
may also mean a mouse button, as those are listed in theapp_key_t
enum.APP_INPUT_DOUBLE_CLICK
means a mouse button have been double clicked, and is not sent for keyboard keys. - APP_INPUT_CHAR - use the
char_code
field of thedata
union, which contains the ASCII value of the key that was pressed. This is used to read text input, and will handle things like upper/lower case, and characters which requires multiple keys to be pressed in sequence to generate one input. This means that generally, when a key is pressed, you will get both anAPP_INPUT_KEY_DOWN
event and anAPP_INPUT_CHAR
event - just use the one you are interested in, and ignore the other. - APP_INPUT_MOUSE_MOVE - use the
mouse_pos
field of thedata
union, which contains the x and y position of the mouse pointer, in window coordinates. The functionapp_coordinates_window_to_bitmap
can be used to convert between the coordinate system of the window and that of the currently displayed bitmap. TheAPP_INPUT_MOUSE_MOVE
event is sent whenever the user moves the mouse, as long as the window has focus and the pointer is inside its client area. - APP_INPUT_MOUSE_DELTA - use the
mouse_delta
field of thedata
union, which contains the horizontal and vertical offset which the mouse has been moved by. Ideally, these values should be in normalized -1.0f to 1.0f range, but as there is no standardisation on the hardware and os level for this, it is not possible to do, so instead the value have been scaled to give roughly normalized -1.0f to 1.0f values on a typical setup. For serious use, sensitivity settings and/or user calibration is recommended. TheAPP_INPUT_MOUSE_DELTA
event is sent whenever the user moves the mouse, regardless of whether the window has focus or whether the pointer is inside the window or not. TheAPP_INPUT_MOUSE_DELTA
event is a better option for reading relative mouse movements than using theAPP_INPUT_MOUSE_MOVE
event together withapp_pointer_pos
to re-center the pointer on every update. - APP_INPUT_SCROLL_WHEEL - use the
wheel_delta
field of thedata
union, which contains the number of clicks by which the scroll wheel on the mouse was turned, where positive values indicate that the wheel have been rotated away from the user, and negative values means it has turned towards the user. TheAPP_INPUT_SCROLL_WHEEL
is sent every time the user turns the scroll wheel, as long as the window has focus. - APP_INPUT_TABLET - use the
tablet
field of thedata
union, which contains details about the pen used with a graphical tablet, if connected and installed. Thex
andy
fields are the horizontal and vertical positions of the pen on the tablet, scaled to the coordinate system of the window. The functionapp_coordinates_window_to_bitmap
can be used to convert between the coordinate system of the window and that of the currently displayed bitmap. Thepressure
field is the current pressure of the pen against the tablet, in normalized 0.0f to 1.0f range, inclusive, where 0.0f means no pressure and 1.0f means full pressure. Thetip
field is set toAPP_PRESSED
if the tip of the pen is touching the tablet at all, and toAPP_NOT_PRESSED
otherwise. Theupper
andlower
fields indicate the current state of the buttons on the side of the pen. The "eraser" part of the pen is not currently supported.
void app_coordinates_window_to_bitmap( app_t* app, int width, int height, int* x, int* y )
Functions in the app.h
API expects and returns coordinates in the windows coordinate system, where 0, 0 is the top
left corner of the windows client area (the area inside of the window borders, excluding title bar and decorations), and
width, height is the dimension, in pixels, of the client area. It is often desirable to translate a position given in
window coordinates into a position on the bitmap that is being displayed - taking into account whether the window is in
fullscreen or windowed mode, and how the bitmap is stretched or padded depending on which interpolation mode is being
used: APP_INTERPOLATION_NONE
or APP_INTERPOLATION_LINEAR
. app_coordinates_window_to_bitmap
performs this
translation, and is called with the following parameters:
- width, height - dimensions of the bitmap being presented, the same as the ones passed to
app_present
. - x, y - pointers to integer values containing the coordinate, in the coordinate system of the window, to be translated. When the function returns, their values will have been updated with the corresponding position in the coordinate system of the bitmap.
void app_coordinates_bitmap_to_window( app_t* app, int width, int height, int* x, int* y )
This performs the opposite translation to app_coordinates_window_to_bitmap
- it converts a position given in the
coordinate system of the bitmap into the coordinate system of the window. See app_coordinates_window_to_bitmap
for
details.