diff --git a/build-scripts/build-web-examples.pl b/build-scripts/build-web-examples.pl new file mode 100755 index 000000000..a14b334b4 --- /dev/null +++ b/build-scripts/build-web-examples.pl @@ -0,0 +1,183 @@ +#!/usr/bin/perl -w + +# Simple DirectMedia Layer +# Copyright (C) 1997-2024 Sam Lantinga +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. + +use warnings; +use strict; +use File::Basename; +use Cwd qw(abs_path); +use IPC::Open2; + +my $examples_dir = abs_path(dirname(__FILE__) . "/../examples"); +my $project = undef; +my $emsdk_dir = undef; +my $compile_dir = undef; +my $output_dir = undef; + +sub usage { + die("USAGE: $0 \n\n"); +} + +sub do_system { + my $cmd = shift; + $cmd = "exec /usr/bin/bash -c \"$cmd\""; + print("$cmd\n"); + return system($cmd); +} + +sub do_mkdir { + my $d = shift; + if ( ! -d $d ) { + print("mkdir '$d'\n"); + mkdir($d) or die("Couldn't mkdir('$d'): $!\n"); + } +} + +sub build_latest { + # Try to build just the latest without re-running cmake, since that is SLOW. + print("Building latest version of $project ...\n"); + if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && cd '$compile_dir' && ninja") != 0) { + # Build failed? Try nuking the build dir and running CMake from scratch. + print("\n\nBuilding latest version of $project FROM SCRATCH ...\n"); + if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && rm -rf '$compile_dir' && mkdir '$compile_dir' && cd '$compile_dir' && emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel .. && ninja") != 0) { + die("Failed to build latest version of $project!\n"); # oh well. + } + } +} + +sub handle_example_dir { + my $category = shift; + my $example = shift; + + my @files = (); + my $files_str = ''; + opendir(my $dh, "$examples_dir/$category/$example") or die("Couldn't opendir '$examples_dir/$category/$example': $!\n"); + my $spc = ''; + while (readdir($dh)) { + next if not /\.c\Z/; # only care about .c files. + my $path = "$examples_dir/$category/$example/$_"; + next if not -f $path; # only care about files. + push @files, $path; + $files_str .= "$spc$path"; + $spc = ' '; + } + closedir($dh); + + my $dst = "$output_dir/$category/$example"; + + do_mkdir($dst); + print("Building $category/$example ...\n"); + + # !!! FIXME: hardcoded SDL3 references, need to fix this for satellite libraries and SDL2. + do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && emcc -s USE_SDL=0 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1gb -s ASSERTIONS=0 -o '$dst/index.js' '-I$examples_dir/../include' $files_str '$compile_dir/libSDL3.a'") == 0 + or die("Failed to build $category/$example!\n"); + + my $highlight_cmd = "highlight '--outdir=$dst' --style-outfile=highlight.css --fragment --stdout --syntax=c '--plug-in=$examples_dir/highlight-plugin.lua'"; + print("$highlight_cmd\n"); + my $pid = open2(my $child_out, my $child_in, $highlight_cmd); + + my $htmlified_source_code = ''; + foreach (@files) { + my $path = $_; + open my $srccode, '<', $path or die("Couldn't open '$path': $!\n"); + my $fname = "$path"; + $fname =~ s/\A.*\///; + print $child_in "/* $fname ... */\n\n"; + while (<$srccode>) { + print $child_in $_; + } + close($srccode); + } + + close($child_in); + + while (<$child_out>) { + $htmlified_source_code .= $_; + } + close($child_out); + + waitpid($pid, 0); + + + my $html = ''; + open my $htmltemplate, '<', "$examples_dir/template.html" or die("Couldn't open '$examples_dir/template.html': $!\n"); + while (<$htmltemplate>) { + s/\@project_name\@/$project/g; + s/\@category_name\@/$category/g; + s/\@example_name\@/$example/g; + s/\@htmlified_source_code\@/$htmlified_source_code/g; + $html .= $_; + } + close($htmltemplate); + + open my $htmloutput, '>', "$dst/index.html" or die("Couldn't open '$dst/index.html': $!\n"); + print $htmloutput $html; + close($htmloutput); + + +} + +sub handle_category_dir { + my $category = shift; + + do_mkdir("$output_dir/$category"); + + opendir(my $dh, "$examples_dir/$category") or die("Couldn't opendir '$examples_dir/$category': $!\n"); + + while (readdir($dh)) { + next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries. + next if not -d "$examples_dir/$category/$_"; # only care about subdirectories. + handle_example_dir($category, $_); + } + + closedir($dh); +} + + +# Mainline! + +foreach (@ARGV) { + $project = $_, next if not defined $project; + $emsdk_dir = $_, next if not defined $emsdk_dir; + $compile_dir = $_, next if not defined $compile_dir; + $output_dir = $_, next if not defined $output_dir; + usage(); # too many arguments. +} + +usage() if not defined $output_dir; + +build_latest(); + +do_mkdir($output_dir); + +print("Examples dir: $examples_dir\n"); + +opendir(my $dh, $examples_dir) or die("Couldn't opendir '$examples_dir': $!\n"); + +while (readdir($dh)) { + next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries. + next if not -d "$examples_dir/$_"; # only care about subdirectories. + handle_category_dir($_); +} + +closedir($dh); + +exit(0); # success! + diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..28b485bd4 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,67 @@ +# Examples + +## What is this? + +In here are a collection of standalone SDL application examples. Unless +otherwise stated, they should work on all supported platforms out of the box. +If they don't [please file a bug to let us know](https://github.com/libsdl-org/SDL/issues/new). + + +## What is this SDL_AppIterate thing? + +SDL can optionally build apps as a collection of callbacks instead of the +usual program structure that starts and ends in a function called `main`. +The examples use this format for two reasons. + +First, it allows the examples to work when built as web applications without +a pile of ugly `#ifdef`s, and all of these examples are published on the web +at [examples.libsdl.org](https://examples.libsdl.org/), so you can easily see +them in action. + +Second, it's example code! The callbacks let us cleanly break the program up +into the four logical pieces most apps care about: + +- Program startup +- Event handling +- What the program actually does in a single frame +- Program shutdown + +A detailed technical explanation of these callbacks is in +docs/README-main-functions.md (or view that page on the web on +[the wiki](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3). + + +## I would like to build and run these examples myself. + +When you build SDL with CMake, you can add `-DSDL_BUILD_EXAMPLES=On` to the +CMake command line. When you build SDL, these examples will be built with it. + +But most of these can just be built as a single .c file, as long as you point +your compiler at SDL3's headers and link against SDL. + + +## What is the license on the example code? Can I paste this into my project? + +All code in the examples directory is considered public domain! You can do +anything you like with it, including copy/paste it into your closed-source +project, sell it, and pretend you wrote it yourself. We do not require you to +give us credit for this code (but we always appreciate if you do!). + +This is only true for the examples directory. The rest of SDL falls under the +[zlib license](https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt). + + +## What is template.html and highlight-plugin.lua in this directory? + +This is what [examples.libsdl.org](https://examples.libsdl.org/) uses when +generating the web versions of these example programs. You can ignore this, +unless you are improving it, in which case we definitely would love to hear +from you! + + +## What is template.c in this directory? + +If writing new examples, this is the skeleton code we start from, to keep +everything consistent. You can ignore it. + + diff --git a/examples/highlight-plugin.lua b/examples/highlight-plugin.lua new file mode 100644 index 000000000..2c9fd79a5 --- /dev/null +++ b/examples/highlight-plugin.lua @@ -0,0 +1,77 @@ +-- This code adapted from https://gitlab.com/saalen/highlight/-/wikis/Plug-Ins + +-- first add a description of what the plug-in does +Description="Add wiki.libsdl.org reference links to HTML, LaTeX or RTF output" + +-- define the plugin categories (ie. supported output formats; languages) +Categories = { "c", "c++" } + +-- the syntaxUpdate function contains code related to syntax recognition +function syntaxUpdate(desc) + + -- if the current file is not C/C++ file we exit + if desc~="C and C++" then + return + end + + -- this function returns a qt-project reference link of the given token + function getURL(token) + -- generate the URL + url='https://wiki.libsdl.org/SDL3/'.. token + + -- embed the URL in a hyperlink according to the output format + -- first HTML, then LaTeX and RTF + if (HL_OUTPUT== HL_FORMAT_HTML or HL_OUTPUT == HL_FORMAT_XHTML) then + return ''.. token .. '' + elseif (HL_OUTPUT == HL_FORMAT_LATEX) then + return '\\href{'..url..'}{'..token..'}' + elseif (HL_OUTPUT == HL_FORMAT_RTF) then + return '{{\\field{\\*\\fldinst HYPERLINK "' + ..url..'" }{\\fldrslt\\ul\\ulc0 '..token..'}}}' + end + end + + -- the Decorate function will be invoked for every recognized token + function Decorate(token, state) + + -- we are only interested in keywords, preprocessor or default items + if (state ~= HL_STANDARD and state ~= HL_KEYWORD and + state ~=HL_PREPROC) then + return + end + + -- SDL keywords start with SDL_ + -- if this pattern applies to the token, we return the URL + -- if we return nothing, the token is outputted as is + if string.find(token, "SDL_")==1 then + return getURL(token) + end + + end +end + +-- the themeUpdate function contains code related to the theme +function themeUpdate(desc) + -- the Injections table can be used to add style information to the theme + + -- HTML: we add additional CSS style information to beautify hyperlinks, + -- they should have the same color as their surrounding tags + if (HL_OUTPUT == HL_FORMAT_HTML or HL_OUTPUT == HL_FORMAT_XHTML) then + Injections[#Injections+1]= + "a.hl, a.hl:visited {color:inherit;font-weight:inherit;text-decoration:none}" + + -- LaTeX: hyperlinks require the hyperref package, so we add this here + -- the colorlinks and pdfborderstyle options remove ugly boxes in the output + elseif (HL_OUTPUT==HL_FORMAT_LATEX) then + Injections[#Injections+1]= + "\\usepackage[colorlinks=false, pdfborderstyle={/S/U/W 1}]{hyperref}" + end +end + +-- let highlight load the chunks +Plugins={ + { Type="lang", Chunk=syntaxUpdate }, + { Type="theme", Chunk=themeUpdate }, +} + diff --git a/examples/renderer/clear/renderer-clear.c b/examples/renderer/clear/renderer-clear.c new file mode 100644 index 000000000..948884d75 --- /dev/null +++ b/examples/renderer/clear/renderer-clear.c @@ -0,0 +1,80 @@ +/* + * This example code creates an SDL window and renderer, and then clears the + * window to a different color every frame, so you'll effectively get a window + * that's smoothly fading between colors. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; + +/* the current red color we're clearing to. */ +static Uint8 red = 0; + +/* When fading up, this is 1, when fading down, it's -1. */ +static int fade_direction = 1; + + +/* This function runs once at startup. */ +int SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + if (SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer) == -1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL); + return SDL_APP_FAILURE; + } + SDL_SetRenderVSync(renderer, 1); /* try to show frames at the monitor refresh rate. */ + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +int SDL_AppEvent(void *appstate, const SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +int SDL_AppIterate(void *appstate) +{ + /* since we're always fading red, we leave green and blue at zero. + alpha doesn't mean much here, so leave it at full (255, no transparency). */ + SDL_SetRenderDrawColor(renderer, red, 0, 0, 255); + + /* clear the window to the draw color. */ + SDL_RenderClear(renderer); + + /* put the newly-cleared rendering on the screen. */ + SDL_RenderPresent(renderer); + + /* update the color for the next frame we will draw. */ + if (fade_direction > 0) { + if (red == 255) { + fade_direction = -1; + } else { + red++; + } + } else if (fade_direction < 0) { + if (red == 0) { + fade_direction = 1; + } else { + red--; + } + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate) +{ + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/examples/template.c b/examples/template.c new file mode 100644 index 000000000..6add81654 --- /dev/null +++ b/examples/template.c @@ -0,0 +1,45 @@ +/* + * This example code $WHAT_IT_DOES. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; + + +/* This function runs once at startup. */ +int SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + if (SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer) == -1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL); + return SDL_APP_FAILURE; + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +int SDL_AppEvent(void *appstate, const SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +int SDL_AppIterate(void *appstate) +{ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate) +{ + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/examples/template.html b/examples/template.html new file mode 100644 index 000000000..67c4d015a --- /dev/null +++ b/examples/template.html @@ -0,0 +1,117 @@ + + + + + + @project_name@ Example: @category_name@/@example_name@ + + + + +
+ +
+ + +
@htmlified_source_code@
+ + + + + + +