Browse Source

Initial commit.

* 256 color palette viewer.
    * usage: ./palview <palette> <vgamode>
        * If vgamode is omitted or 0, it displays the palette as-is.
	* If vgamode is 1, it multiplies all palette values by 4.
master
Ian Burgmyer 5 years ago
commit
121934c454
  1. 86
      .gitignore
  2. 4
      .idea/encodings.xml
  3. 10
      .idea/inspectionProfiles/Project_Default.xml
  4. 15
      .idea/misc.xml
  5. 8
      .idea/modules.xml
  6. 2
      .idea/palview.iml
  7. 16
      CMakeLists.txt
  8. 24
      LICENSE
  9. 44
      Makefile
  10. 186
      README.md
  11. 19
      src/defs.h
  12. 68
      src/event.c
  13. 15
      src/event.h
  14. 101
      src/game.c
  15. 10
      src/game.h
  16. 52
      src/log.c
  17. 16
      src/log.h
  18. 31
      src/main.c
  19. 80
      src/video.c
  20. 19
      src/video.h
  21. 23
      sublime/sdl2-sandbox.sublime-project

86
.gitignore vendored

@ -0,0 +1,86 @@
# Created by https://www.gitignore.io/api/clion
# Edit at https://www.gitignore.io/?templates=clion
### CLion ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### CLion Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/sonarlint
# End of https://www.gitignore.io/api/clion
# StarLight stuff!
src/version.h

4
.idea/encodings.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

10
.idea/inspectionProfiles/Project_Default.xml

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

15
.idea/misc.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="CidrRootsConfiguration">
<sourceRoots>
<file path="$PROJECT_DIR$/src" />
</sourceRoots>
</component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="NodePackageJsonFileManager">
<packageJsonPaths />
</component>
</project>

8
.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/palview.iml" filepath="$PROJECT_DIR$/.idea/palview.iml" />
</modules>
</component>
</project>

2
.idea/palview.iml

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

16
CMakeLists.txt

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.13)
project(palview C)
set(CMAKE_C_STANDARD 90)
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
add_executable(palview
src/main.c
src/event.c
src/game.c
src/log.c
src/video.c)
target_link_libraries(palview ${SDL2_LIBRARIES})

24
LICENSE

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

44
Makefile

@ -0,0 +1,44 @@
# A Generic Makefile For Quick and Dirty Projects
#
# This Makefile has been tested using GNU make and bash. Your mileage may vary
# with other make implementations and/or shells.
CFLAGS = $(shell sdl2-config --cflags)
LDFLAGS = $(shell sdl2-config --libs)
CC = cc
MKDIR = mkdir -p
OBJDIR = ./obj
BINDIR = ./bin
ASSETDIR = ./assets
# System-specific Makefile overrides.
-include Makefile.override
# The rest of these settings should are automatically configured. Tweak at your
# own peril! :P
OUTFILE = $(shell basename $(shell pwd))
ALL_C := $(shell find . -type f -name '*.c')
OBJFILES := $(addprefix $(OBJDIR)/,$(ALL_C:%.c=%.o))
all: $(OUTFILE)
$(OBJDIR)/%.o: %.c
$(MKDIR) $(@D)
$(CC) $(CFLAGS) -o $@ -c $<
$(OUTFILE): $(OBJFILES)
$(MKDIR) $(BINDIR)
$(CC) $(LDFLAGS) -o $(BINDIR)/$(OUTFILE) $^
if [ -d $(ASSETDIR) ]; then \
cp $(ASSETDIR)/* $(BINDIR); \
fi
test: $(OUTFILE)
cd $(BINDIR);./$(OUTFILE)
clean:
$(RM) -r $(OBJFILES)
$(RM) $(BINDIR)/$(OUTFILE)

186
README.md

@ -0,0 +1,186 @@
# SDL2 Sandbox
## Introduction
I've made quite a few little experimental projects in SDL. As such, I've
written very similar initialization code multiple times. I decided that it
would be much easier to put some quick boilerplate code together to give me a
base to quickly experiment with new ideas without having to keep reinventing
the wheel.
## Requirements
This project is aimed at people who are programming using a Linux system. A
compiler toolchain, such as gcc with binutils, and an implementation of Make
are required to build the project. Please note that the Make implementation
must support the $(shell) syntax.
If you're running a Linux system with the development tools installed, odds are
this will work without a hitch. If you're using *BSD, you may have to use GNU
make (gmake) for this to build properly.
macOS and Windows users will have to do a bit more work to get this running,
whether it be creating an Xcode/MSVC project, manually compiling SDL, or using
a third-party package manager and/or toolchain (such as mingw64 on Windows or
homebrew on macOS).
SDL 2.0 is required. Later versions may work, but earlier versions will not.
## What's Included?
This project includes a Makefile, a working sample project, and ignore files
for git and Mercurial. It's designed so that you can compile it, watch it work,
then immediately start making changes.
## How to Use This Project
The first step to using this project is to determine which branch suits your
skill level. The `master` branch--the default--is very heavily commented,
intended to help ease beginners into C and SDL2. All major calls and actions
are commented, and the rationale behind each step is explained in as much
detail as is feasible.
The `terse` branch is much cleaner, intended for people who just want to be
able to use this as a base to build other projects.
Since this is intended to be used as a base, it is highly recommended that you
create your own repository (if you choose to use one) rather than keeping the
existing one. For example, if you wanted to create a project called "my_game",
run the following from your terminal emulator of choice:
```
git clone https://github.com/Spectere/sdl2_sandbox.git my_game
cd my_game
rm -rf .git
git init
```
The above command clones the git repository, changes into the project
directory, removes the existing git repository, then creates a new one in its
place. If you prefer to use Mercurial, use `hg init` in place of
`git init`.
If you are using a repository, be sure to delete the ignore file that does
not apply to your VCS of choice. For example, if you're using git, you should
run `rm .hgignore`, while if you're using Mercurial you should run
`rm .gitignore`. If you don't want to use either, both files can be removed.
After your project directory is prepared, type `make` to build the project.
If there are no errors, type `make test` to launch it. You should see a window
pop up with some color shifting. Either press ESC or close the window to exit.
Now, modify `defs.h` to customize the window title text and resolution. The
application loop is contained in `game.c`, and input handling is done in
`event.c`. Happy coding!
## Caveats
While this project does provide a quick and easy way to create an SDL2 project,
it was not intended for large-scale or release-ready projects. Here are a few
potential issues that you can run into.
* The logger is simple, both in ease-of-use and functionality. It will not
support all platforms flawlessly.
* The video settings are hardcoded in `defs.h`.
* No provisions have been made for audio input and output, network support,
or other features. The only things that have been accounted for are video
and basic input.
* Game logic is tied to the framerate, and vsync is leveraged to limit the
framerate. This project does not contain any timing routines.
* The Makefile was written to include all C files in the src/ directory,
and also names the executable after the directory that contains the
project. Additionally, no automatic configuration is supported, and
cross-platform support is not guaranteed.
* This has not been tested with C++ projects.
## Contributing
If you can think of any ways to improve this project, feel free to submit a
pull request. Please note that PRs that push the project too far past its main
goals will most likely be rejected. The goals are as follows:
1. To provide a base for experimentation and prototyping.
2. To provide beginners with a means of easing into SDL2 programming.
It's recommended that contributions provide thorough documentation, as seen in
the master branch, but this is not required.
Also, please take note of the following style guidelines in this section:
### Comments
All source code comments should be C89-style (using `/*` and `*/`), not
C++-style (using `//`). Comments should be made above the applicable line,
though inline comments may be used where appropriate.
A brief description of the file should be included on every source and header
file.
Comments should not exceed 80 columns of width, including whitespace.
### Braces
Open braces should appear at the end of all block statements, including
function definitions.
### Whitespace and Code Width
Code should be indented using tabs. Total width should be counted counted as if
each tab were 4 spaces in width, as that is the default with most modern
graphical IDEs.
Parenthesis should be placed against control statements, function calls, and
function declarations, with no extra spaces. When pointers are declared, be it
in function or variable definitions, the asterisk should be placed against the
name of the pointer.
80 columns of width should be considered a soft cap, as that will allow easy
code examination when using multiple editor panes and a terminal. 120 columns
should be considered a hard cap. Where feasible, a single parameter or equation
should not be broken across multiple lines.
If a function call must span multiple lines, the subsequent lines should be
indented with a single tab.
If a function definition must span multiple lines, the subsequent lines should
be aligned with spaces to one character after the open parenthesis.
If a control statement must span multiple lines, the subsequent lines should be
indented with two tabs. That said, this probably should never occur on a
project with this scope.
### Include Guards
`#ifndef/#define/#endif` should be used for all include guards. At this time,
`#pragma once` should be avoided. The definition format that should be used is
the include filename, in all caps, with underscores surrounding it and
replacing dots. The `#endif` must have the definition stated in an inline
comment.
For example, if you're working on a header file called `foo.h`, it should look
like the following:
```c
/* foo.h
*
* Exposes public functions related to the foo subsystem.
*/
#ifndef _FOO_H_
#define _FOO_H_
void foo_bar();
#endif /* _FOO_H_ */
```
## Licensing
This project is released under the Unlicense, effectively putting it into the
public domain. Feel free to use, modify, and even relicense it to best suit
your needs. No attribution is required.

19
src/defs.h

@ -0,0 +1,19 @@
/* defs.h - Global definitions and constants. */
/* The default caption of the SDL2 window. */
#define PROJECT_NAME "SDL2 Sandbox"
/* The size of the pixmap. */
#define RENDER_WIDTH 322
#define RENDER_HEIGHT 322
/* The SDL window size. */
#define WINDOW_WIDTH 966
#define WINDOW_HEIGHT 966
/* Maximum length of the log format screen, including the severity tag. */
#define LOG_LEN 160
/* How the color box things get displayed. */
#define BOX_SIZE 20
#define BOX_SPACING 2

68
src/event.c

@ -0,0 +1,68 @@
/* event.c - Event handler functions. */
#include <SDL.h>
#include "event.h"
event_result_t event_keyboard(const SDL_Event e) {
SDL_KeyboardEvent kb = e.key;
if(kb.type == SDL_KEYUP) {
switch(kb.keysym.sym) {
case SDLK_ESCAPE:
return EV_QUIT;
}
}
return EV_NONE;
}
event_result_t event_mouse_button(const SDL_Event e) {
SDL_MouseButtonEvent btn = e.button;
return EV_NONE;
}
event_result_t event_mouse_wheel(const SDL_Event e) {
SDL_MouseWheelEvent wheel = e.wheel;
return EV_NONE;
}
event_result_t event_mouse_motion(const SDL_Event e) {
SDL_MouseMotionEvent motion = e.motion;
return EV_NONE;
}
event_result_t event_window(const SDL_Event e) {
SDL_WindowEvent wnd = e.window;
return EV_NONE;
}
/* Main event processor. */
event_result_t event_process() {
SDL_Event e;
SDL_PollEvent(&e);
switch(e.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
return event_keyboard(e);
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
return event_mouse_button(e);
case SDL_MOUSEWHEEL:
return event_mouse_wheel(e);
case SDL_MOUSEMOTION:
return event_mouse_motion(e);
case SDL_WINDOWEVENT:
return event_window(e);
case SDL_QUIT:
return EV_QUIT;
}
return EV_NONE;
}

15
src/event.h

@ -0,0 +1,15 @@
/* event.h - Event handler functions. */
#ifndef _EVENT_H_
#define _EVENT_H_
/* Values that can be sent to the main game loop. */
typedef enum {
EV_NONE,
EV_QUIT
} event_result_t;
/* Main event processor. */
event_result_t event_process();
#endif /* _EVENT_H_ */

101
src/game.c

@ -0,0 +1,101 @@
/* game.c - Main game loop. */
#include <errno.h>
#include <SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "defs.h"
#include "event.h"
#include "video.h"
Uint32 palette[256];
void load_palette(char* palfile, SDL_bool vga) {
struct stat st;
FILE *f;
int i;
stat(palfile, &st);
if(errno) {
printf("unable to stat file: %s\n", strerror(errno));
exit(1);
}
if(st.st_size != 768) {
printf("invalid size (expected 768 byes, got %li bytes)\n", st.st_size);
exit(1);
}
f = fopen(palfile, "rb");
if(f == NULL) {
printf("unable to open file: %s\n", strerror(errno));
exit(1);
}
for(i = 0; i < 256; i++) {
Uint8 r, g, b;
r = (Uint8)fgetc(f);
g = (Uint8)fgetc(f);
b = (Uint8)fgetc(f);
if(!vga)
palette[i] = RGB(r, g, b);
else
palette[i] = RGB(r * 4, g * 4, b * 4);
}
fclose(f);
}
void game_loop(char *palfile, SDL_bool vga) {
event_result_t ev = EV_NONE;
SDL_bool running = SDL_TRUE;
int cx, cy;
load_palette(palfile, vga);
if(video_init(PROJECT_NAME, RENDER_WIDTH, RENDER_HEIGHT, WINDOW_WIDTH,
WINDOW_HEIGHT) != 0) {
printf("error initializing video\n");
exit(1);
}
/* Clear the framebuffer. */
memset(pixels, 0x00, RENDER_WIDTH * RENDER_HEIGHT * 4);
/* Plot things! */
for(cy = 0; cy < 16; cy++) {
for(cx = 0; cx < 16; cx++) {
int i = (cy * 16) + cx;
int c = palette[i];
int sx, sy;
for(sy = BOX_SPACING; sy < BOX_SIZE; sy++) {
for(sx = BOX_SPACING; sx < BOX_SIZE; sx++) {
int x = (BOX_SIZE) * cx + sx;
int y = (BOX_SIZE) * cy + sy;
pixels[POS(x, y)] = c;
}
}
}
}
while(running) {
ev = event_process();
switch(ev) {
case EV_QUIT:
running = SDL_FALSE;
break;
}
video_update();
}
}

10
src/game.h

@ -0,0 +1,10 @@
/* game.h - Main game loop. */
#ifndef _GAME_H_
#define _GAME_H_
#include <SDL_types.h>
void game_loop(char* palfile, SDL_bool vga);
#endif /* _GAME_H_ */

52
src/log.c

@ -0,0 +1,52 @@
/* log.c - Application logging functionality. */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "defs.h"
#include "log.h"
void lprint(log_level_t level, const char *format, ...) {
char output_format[LOG_LEN];
va_list args;
va_start(args, format);
if(strlen(format) + 10 > LOG_LEN) {
fprintf(stderr, "[!!!] Log string is too long!\n");
vfprintf(stdout, format, args);
fprintf(stdout, "\n");
va_end(args);
return;
}
switch(level) {
case DEBUG:
strcpy(output_format, "[DEBUG] ");
break;
case INFO:
strcpy(output_format, "[INFO] ");
break;
case WARN:
strcpy(output_format, "[WARN] ");
break;
case ERROR:
strcpy(output_format, "[ERROR] ");
break;
default:
/* If no valid severity was passed, initialize the string. */
strcpy(output_format, "");
break;
}
strcat(output_format, format);
strcat(output_format, "\n");
if(level == ERROR)
vfprintf(stderr, output_format, args);
else
vfprintf(stdout, output_format, args);
va_end(args);
}

16
src/log.h

@ -0,0 +1,16 @@
/* log.h - Application logging functionality. */
#ifndef _LOG_H_
#define _LOG_H_
/* Severity levels used by lprint(). */
typedef enum {
DEBUG,
INFO,
WARN,
ERROR
} log_level_t;
void lprint(log_level_t level, const char *format, ...);
#endif /* _LOG_H_ */

31
src/main.c

@ -0,0 +1,31 @@
/* main.c - Program entry point. */
#include <stdio.h>
#include "defs.h"
#include "game.h"
#include "video.h"
int main(int argc, char **argv) {
SDL_bool vga = SDL_FALSE;
if(argc < 2) {
printf("\n usage: palview (palette file) [vga]\n\n");
printf(" vga should be 1 if the palette is a VGA palette (color range: 0x00-0x3F,\n");
printf(" otherwise it should be 0. If not specified, it will default to 0.\n\n");
return 1;
}
if(argc > 2) {
printf("vga: %s\n", argv[2]);
if(strncmp(argv[2], "1", 1) == 0)
vga = SDL_TRUE;
}
game_loop(argv[1], vga);
video_close();
SDL_Quit();
return 0;
}

80
src/video.c

@ -0,0 +1,80 @@
/* video.c - Initializes and updates the window. */
#include <stdlib.h>
#include "log.h"
#include "video.h"
int tex_width, tex_height, wnd_width, wnd_height;
Uint32 *pixels;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
void video_close() {
lprint(INFO, "Closing video subsystem");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
int video_init(const char *title, int render_width, int render_height,
int window_width, int window_height) {
tex_width = render_width;
tex_height = render_height;
wnd_width = window_width;
wnd_height = window_height;
SDL_Init(SDL_INIT_VIDEO);
lprint(INFO, "Initializing video (%ix%i surface, %ix%i window)...",
tex_width, tex_height, wnd_width, wnd_height);
/* Create an SDL window. */
lprint(DEBUG, "Creating window");
window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, window_width, window_height, 0);
if(window == NULL) {
lprint(ERROR, "Error creating window: %s", SDL_GetError());
return 1;
}
/* Create the renderer. */
lprint(DEBUG, "Creating hardware accelerated renderer");
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if(renderer == NULL) {
/* Attempt to fall back to software rendering. */
lprint(WARN, "Failed to create hardware accelerated renderer: %s", SDL_GetError());
lprint(WARN, "Attempting to create a software renderer");
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE | SDL_RENDERER_PRESENTVSYNC);
if(renderer == NULL) {
lprint(ERROR, "Error creating renderer: %s", SDL_GetError());
return 1;
}
}
/* Create an ARGB8888 texture. */
lprint(DEBUG, "Creating texture");
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, tex_width, tex_height);
if(texture == NULL) {
lprint(ERROR, "Error creating texture: %s", SDL_GetError());
return 1;
}
pixels = malloc(sizeof(Uint32) * tex_width * tex_height);
lprint(INFO, "Video subsystem initialized successfully");
return 0;
}
void video_update() {
SDL_UpdateTexture(texture, NULL, pixels, tex_width * sizeof(Uint32));
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}

19
src/video.h

@ -0,0 +1,19 @@
/* video.h - Initializes and updates the window. */
#ifndef _VIDEO_H_
#define _VIDEO_H_
#include <SDL.h>
#include "defs.h"
#define RGB(r, g, b) (0xFF000000 | (r << 16) | (g << 8) | b)
#define POS(x, y) ((RENDER_WIDTH * y) + x)
extern Uint32 *pixels;
void video_close();
int video_init(const char *title, int render_width, int render_height,
int window_width, int window_height);
void video_update();
#endif /* _VIDEO_H_ */

23
sublime/sdl2-sandbox.sublime-project vendored

@ -0,0 +1,23 @@
{
"folders":
[
{
"name": "Project Files",
"path": "..",
"file_include_patterns": [
"Makefile",
"LICENSE",
"README.md",
".gitignore",
".hgignore"
],
"folder_exclude_patterns": [
"*"
]
},
{
"name": "Source",
"path": "../src"
}
]
}
Loading…
Cancel
Save