huge v4k update

main
Dominik Madarász 2023-11-15 20:14:14 +01:00
parent dbef195cd2
commit 5aa83a524a
81 changed files with 8506 additions and 4670 deletions

View File

@ -482,6 +482,7 @@ if "%1"=="tidy" (
del *.mp4 > nul 2> nul
del *.def > nul 2> nul
del *.dll > nul 2> nul
del *.csv > nul 2> nul
del 3rd_*.* > nul 2> nul
del v4k_*.* > nul 2> nul
del v4k.html > nul 2> nul

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@
#define ZIP_H
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
typedef struct zip zip;
@ -550,7 +551,7 @@ bool zip_append_file_timeinfo(zip *z, const char *entryname, const char *comment
// @fixme: calc whole crc contents
uint32_t crc = 0;
unsigned char buf[1<<15];
unsigned char buf[4096];
while(!feof(in) && !ferror(in)) crc = zip__crc32(crc, buf, fread(buf, 1, sizeof(buf), in));
if(ferror(in)) return ERR(false, "Error while calculating CRC, skipping store.");

View File

@ -0,0 +1,140 @@
// base64 de/encoder. Based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN).
// - rlyeh, public domain
#ifndef BASE64_H
#define BASE64_H
unsigned base64_bounds(unsigned size);
char* base64_encode(const void *inp, unsigned inlen); // free() after use
char* base64_decode(const char *inp, unsigned inlen); // array_free() after use
#endif
#ifdef BASE64_C
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
unsigned base64_bounds(unsigned size) {
return BASE64_ENCODE_OUT_SIZE(size);
}
char* base64_encode(const void *inp, unsigned inlen) { // free() after use
unsigned outlen = base64_bounds(inlen);
char *out_ = malloc(outlen);
out_[outlen] = 0;
uint_least32_t v;
unsigned ii, io, rem;
char *out = (char *)out_;
const unsigned char *in = (const unsigned char *)inp;
const uint8_t base64enc_tab[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for(io = 0, ii = 0, v = 0, rem = 0; ii < inlen; ii ++) {
unsigned char ch;
ch = in[ii];
v = (v << 8) | ch;
rem += 8;
while (rem >= 6) {
rem -= 6;
if (io >= outlen)
return (free(out_), 0); /* truncation is failure */
out[io ++] = base64enc_tab[(v >> rem) & 63];
}
}
if (rem) {
v <<= (6 - rem);
if (io >= outlen)
return (free(out_), 0); /* truncation is failure */
out[io ++] = base64enc_tab[v & 63];
}
while(io&3) {
if(io>=outlen) return (free(out_), 0); /* truncation is failure */
out[io++]='=';
}
if(io>=outlen) return (free(out_), 0); /* no room for null terminator */
out[io]=0;
return out_;
}
#ifdef array_resize
array(char) base64_decode(const char *inp, unsigned inlen) { // array_free() after use
#if 0
unsigned long outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen+1);
if( base64_decodex((const unsigned char *)inp, (unsigned long)inlen, (unsigned char *)out_, &outlen) != CRYPT_OK ) {
array_free(out_);
return 0;
}
array_resize(out_, outlen);
out_[outlen] = 0;
return out_;
#else
unsigned outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen);
// based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN)
uint_least32_t v;
unsigned ii, io, rem;
char *out = (char *)out_;
const unsigned char *in = (const unsigned char *)inp;
const uint8_t base64dec_tab[256]= {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255 };
for (io = 0, ii = 0,v = 0, rem = 0; ii < inlen; ii ++) {
unsigned char ch;
if (isspace(in[ii]))
continue;
if ((in[ii]=='=') || (!in[ii]))
break; /* stop at = or null character*/
ch = base64dec_tab[(unsigned char)in[ii]];
if (ch == 255)
break; /* stop at a parse error */
v = (v<<6) | ch;
rem += 6;
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
}
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
return (array_resize(out_, io), out_);
#endif
}
#endif // array_resize
#endif // BASE64_C

View File

@ -8955,12 +8955,7 @@ static bool LzmaDec_Init(CLzmaDec *p, const uint8_t *raw_props)
// glue.c
static
#ifdef _MSC_VER
__declspec(thread)
#else
__thread
#endif
static __thread
struct {
uint8_t *begin, *seek, *end;
}
@ -10300,9 +10295,7 @@ static inline uint32_t DecodeMod(const uint8_t** p) {
// LZ77
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen) {
ULZ_WORKMEM *u =(ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen, ULZ_WORKMEM *u) {
for (int i=0; i<ULZ_HASH_SIZE; ++i)
u->HashTable[i]=ULZ_NIL;
@ -10384,16 +10377,14 @@ static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outle
op+=run;
}
ULZ_REALLOC(u, 0);
return op-out;
}
static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, int level) {
static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, int level, ULZ_WORKMEM *u) {
if (level<1 || level>9)
return 0;
const int max_chain=(level<9)?1<<level:1<<13;
ULZ_WORKMEM *u = (ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
for (int i=0; i<ULZ_HASH_SIZE; ++i)
u->HashTable[i]=ULZ_NIL;
@ -10518,7 +10509,6 @@ static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, i
op+=run;
}
ULZ_REALLOC(u, 0);
return op-out;
}
@ -10575,9 +10565,11 @@ static int UlzDecompress(const uint8_t* in, int inlen, uint8_t* out, int outlen)
}
unsigned ulz_encode(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags) {
static __thread ULZ_WORKMEM u;
int level = flags > 9 ? 9 : flags; // [0..(6)..9]
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level)
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen);
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level, &u)
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, &u);
return (unsigned)rc;
}
unsigned ulz_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
@ -10713,7 +10705,7 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
double enctime = 0;
if( logfile ) tm = clock();
{
for( uint32_t inlen; (inlen=fread(inbuf, 1, BS_BYTES, in)) > 0 ; ) {
for( uint32_t inlen; (inlen=BS_BYTES * fread(inbuf, BS_BYTES, 1, in)) > 0 ; ) {
uint32_t outlen[2] = {0};
best = clist[0];
@ -10748,15 +10740,15 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
if( compr ) {
uint8_t packer = (compr << 4) | flags;
// store block length + compressor + compr data
if( fwrite(&outlen[0], 1, 4, out) != 4 ) goto fail;
if( fwrite(&outlen[0], 4, 1, out) != 1 ) goto fail;
if( fwrite(&packer, 1, 1, out) != 1 ) goto fail;
if( fwrite(outbuf[0], 1, outlen[0], out) != outlen[0] ) goto fail;
if( fwrite(outbuf[0], outlen[0], 1, out) != 1 ) goto fail;
} else {
uint8_t packer = 0;
// store block length + no-compressor + raw data
if( fwrite(&inlen, 1, 4, out) != 4 ) goto fail;
if( fwrite(&inlen, 4, 1, out) != 1 ) goto fail;
if( fwrite(&packer, 1, 1, out) != 1 ) goto fail;
if( fwrite(inbuf, 1, inlen, out) != inlen ) goto fail;
if( fwrite(inbuf, inlen, 1, out) != 1 ) goto fail;
}
total_in += inlen;
@ -10790,8 +10782,8 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
uint8_t block8; if( fread(&block8, 1,1, in ) < 1 ) return 0;
uint8_t excess8; if( fread(&excess8, 1,1, in ) < 1 ) return 0;
uint8_t block8; if( fread(&block8, 1,1, in ) != 1 ) return 0;
uint8_t excess8; if( fread(&excess8, 1,1, in ) != 1 ) return 0;
uint64_t BLOCK_SIZE = 1ull << block8;
uint64_t EXCESS = 1ull << excess8;
@ -10803,15 +10795,15 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
double dectime = 0;
if(logfile) tm = clock();
{
for(uint32_t inlen=0, loop=0;fread(&inlen, 1, sizeof(inlen), in)>0;++loop) {
for(uint32_t inlen=0, loop=0;fread(&inlen, sizeof(inlen), 1, in) == 1;++loop) {
if (inlen>(BLOCK_SIZE+EXCESS)) goto fail;
uint8_t packer;
if( fread(&packer, 1,sizeof(packer), in) <= 0 ) goto fail;
if( fread(&packer, sizeof(packer),1, in) != 1 ) goto fail;
if(packer) {
// read compressed
if (fread(inbuf, 1, inlen, in)!=inlen) goto fail;
if (fread(inbuf, inlen,1, in)!=1) goto fail;
// decompress
uint8_t compressor = packer >> 4;
@ -10819,11 +10811,11 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
if (!outlen) goto fail;
} else {
// read raw
if (fread(outbuf, 1, inlen, in)!=inlen) goto fail;
if (fread(outbuf, inlen,1, in)!=1) goto fail;
outlen=inlen;
}
if (fwrite(outbuf, 1, outlen, out) != outlen) {
if (fwrite(outbuf, outlen, 1, out) != 1) {
perror("fwrite() failed");
goto fail;
}

View File

@ -17378,10 +17378,10 @@ extern const ltc_math_descriptor gmp_desc;
/* ---- LTC_BASE64 Routines ---- */
#ifdef LTC_BASE64
int base64_encode(const unsigned char *in, unsigned long len,
int base64_encodex(const unsigned char *in, unsigned long len, //< @r-lyeh +x
unsigned char *out, unsigned long *outlen);
int base64_decode(const unsigned char *in, unsigned long len,
int base64_decodex(const unsigned char *in, unsigned long len, //< @r-lyeh +x
unsigned char *out, unsigned long *outlen);
#endif
@ -30099,7 +30099,7 @@ static int _base64_decode_internal(const unsigned char *in, unsigned long inlen
@param outlen [in/out] The max size and resulting size of the decoded data
@return CRYPT_OK if successful
*/
int base64_decode(const unsigned char *in, unsigned long inlen,
int base64_decodex(const unsigned char *in, unsigned long inlen,
unsigned char *out, unsigned long *outlen)
{
return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed);

View File

@ -15,6 +15,7 @@
// 3rd party libs
#define ARCHIVE_C // archive.c
#define BASE64_C // base64.c
#define COMPRESS_C // compress.c
#define ENET_IMPLEMENTATION // enet
#define GJK_C // gjk
@ -182,6 +183,7 @@ static char *ui_filter = 0;
else if(!strcmp(id, "idle") && nargs ==1) push(ev, input_idle(pop(ev)));
{{FILE:3rd_eval.h}}
{{FILE:3rd_luadebugger.h}}
{{FILE:3rd_base64.h}}
//#define SQLITE_OMIT_LOAD_EXTENSION
//#define SQLITE_CORE 1
//#define SQLITE_DEBUG 1

View File

@ -23,12 +23,13 @@ typedef struct cook_subscript_t {
char *script;
char *outname;
int compress_level;
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
} cook_subscript_t;
typedef struct cook_script_t {
cook_subscript_t cs[8];
int num_passes;
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
} cook_script_t;
static
@ -42,6 +43,7 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
// - if no script is going to be generated, output is in fact input file.
// - no compression is going to be required.
cook_subscript_t cs = { 0 };
cs.gen_ns -= time_ns();
// reuse script heap from last call if possible (optimization)
static __thread char *script = 0;
@ -54,6 +56,7 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
map_clear(symbols);
map_clear(groups);
map_find_or_add(symbols, "INFILE", STRDUP(infile));
map_find_or_add(symbols, "INPUT", STRDUP(infile));
map_find_or_add(symbols, "PRETTY", STRDUP(infile + ART_SKIP_ROOT)); // pretty (truncated) input (C:/prj/V4K/art/file.wav -> file.wav)
@ -105,10 +108,15 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
}
lines[i] = line = nl;
#if 0
static thread_mutex_t lock, *init = 0; if(!init) thread_mutex_init(init = &lock);
thread_mutex_lock( &lock );
system(line); // strcatf(&script, "%s\n", line);
thread_mutex_unlock( &lock );
#else
// append line
strcatf(&script, "%s\n", line);
#endif
continue;
}
@ -256,15 +264,15 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
}
}
char *compression = 0;
for each_map(groups, char*, key, char*, val) {
if( isdigit(key[0]) ) {
for each_map_ptr_sorted(groups, char*, key, char*, val) { // sorted iteration, so hopefully '0' no compression gets evaluated first
if( !compression && isdigit((*key)[0]) ) {
char *comma = va(",%s,", ext);
if( !strcmpi(val,ext) || strbegi(val, comma+1) || strstri(val, comma) || strendi(val, va(",%s", ext))) {
compression = key;
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
compression = (*key);
}
comma = va(",%s,", belongs_to);
if( !strcmpi(val,ext) || strbegi(val, comma+1) || strstri(val, comma) || strendi(val, va(",%s", ext))) {
compression = key;
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
compression = (*key);
}
}
}
@ -324,6 +332,7 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
}
cs.outname = cs.outname ? cs.outname : (char*)infile;
cs.gen_ns += time_ns();
ASSERT(mcs.num_passes < countof(mcs.cs));
mcs.cs[mcs.num_passes++] = cs;
@ -455,6 +464,11 @@ int cook(void *userdata) {
volatile int *progress = &job->progress;
*progress = 0;
// preload a few large binaries
// dll("tools/furnace.exe", 0);
// dll("tools/assimp-vc143-mt.dll", 0);
// dll("tools/ffmpeg.exe", 0);
// scan disk from fs_now snapshot
array(struct fs) filtered = zipscan_filter(job->threadid, job->numthreads);
//printf("Scanned: %d items found\n", array_count(now));
@ -493,6 +507,13 @@ int cook(void *userdata) {
zip_append_file/*_timeinfo*/(z, deleted[i], comment, in, 0/*, tm_now*/);
fclose(in);
}
// generate cook metrics. you usually do `game.exe --cook-stats && (type *.csv | sort /R > cook.csv)`
static __thread FILE *statsfile = 0;
if(flag("--cook-stats"))
fseek(statsfile = fopen(va("cook%d.csv",job->threadid), "a+t"), 0L, SEEK_END);
if(statsfile && ftell(statsfile) == 0) fprintf(statsfile,"%10s,%10s,%10s,%10s,%10s, %s\n","+total_ms","gen_ms","exe_ms","zip_ms","pass","file");
// added or changed files
for( int i = 0, end = array_count(uncooked); i < end && !cook_cancelling; ++i ) {
*progress = ((i+1) == end ? 90 : (i * 90) / end); // (i+i>0) * 100.f / end;
@ -519,43 +540,46 @@ int cook(void *userdata) {
}
}
// invoke cooking script and recap status
// invoke cooking script
mcs.cs[pass].exe_ns -= time_ns();
// invoke cooking script
const char *rc_output = app_exec(cs.script);
// recap status
int rc = atoi(rc_output);
int outlen = file_size(cs.outfile);
int failed = cs.script[0] ? rc || !outlen : 0;
// int outlen = file_size(cs.outfile);
int failed = rc; // cs.script[0] ? rc || !outlen : 0;
// print errors
if( failed ) {
PRINTF("Import failed: %s while executing:\n%s\nReturned:\n%s\n", cs.outname, cs.script, rc_output);
continue;
}
// special char (multi-pass cook). newly generated file: refresh values
if( pass > 0 ) { // (multi-pass cook)
// newly generated file: refresh values
// ensure newly created files by cook are also present on repo/disc for further cook passes
if( pass > 0 ) { // && strchr(cs.outname, '@') ) { // pass>0 is a small optimization // special char (multi-pass cooks)
file_delete(cs.outname);
file_move(cs.outfile, cs.outname);
inlen = file_size(infile = cs.outfile = cs.outname);
}
mcs.cs[pass].exe_ns += time_ns();
// process only if included. may include optional compression.
mcs.cs[pass].zip_ns -= time_ns();
if( cs.compress_level >= 0 ) {
FILE *in = fopen(cs.outfile, "rb");
#if 0
struct stat st; stat(infile, &st);
struct tm *timeinfo = localtime(&st.st_mtime);
ASSERT(timeinfo);
#endif
FILE *in = fopen(cs.outfile ? cs.outfile : infile, "rb");
if(!in) in = fopen(infile, "rb");
char *comment = va("%d", inlen);
if( !zip_append_file/*_timeinfo*/(z, infile, comment, in, cs.compress_level/*, timeinfo*/) ) {
if( !zip_append_file(z, infile, comment, in, cs.compress_level) ) {
PANIC("failed to add processed file into %s: %s(%s)", zipfile, cs.outname, infile);
}
fclose(in);
}
mcs.cs[pass].zip_ns += time_ns();
// stats per subscript
mcs.cs[pass].pass_ns = mcs.cs[pass].gen_ns + mcs.cs[pass].exe_ns + mcs.cs[pass].zip_ns;
if(statsfile) fprintf(statsfile, "%10.f,%10.f,%10.f,%10.f,%10d, \"%s\"\n", mcs.cs[pass].pass_ns/1e6, mcs.cs[pass].gen_ns/1e6, mcs.cs[pass].exe_ns/1e6, mcs.cs[pass].zip_ns/1e6, pass+1, infile);
}
}
@ -584,13 +608,13 @@ int cook_async( void *userdata ) {
// tcc: only a single running thread shall pass, because of racing shared state due to missing thread_local support at compiler level
ifdef(tcc, thread_mutex_lock( job->lock ));
ifdef(osx, thread_mutex_lock( job->lock ));
ifdef(osx, thread_mutex_lock( job->lock )); // @todo: remove silicon mac M1 hack
int ret = cook(userdata);
// tcc: only a single running thread shall pass, because of racing shared state due to missing thread_local support at compiler level
ifdef(osx, thread_mutex_unlock( job->lock )); // @todo: remove silicon mac M1 hack
ifdef(tcc, thread_mutex_unlock( job->lock ));
ifdef(osx, thread_mutex_unlock( job->lock ));
thread_exit( ret );
return ret;
@ -669,6 +693,14 @@ bool cook_start( const char *cook_ini, const char *masks, int flags ) {
EDITOR = out; // @leak
assert( EDITOR[strlen(EDITOR) - 1] == '/' );
}
// small optimization for upcoming parser: remove whole comments from file
array(char*) lines = strsplit(rules, "\r\n");
for( int i = 0; i < array_count(lines); ) {
if( lines[i][0] == ';' ) array_erase_slow(lines, i);
else ++i;
}
rules = STRDUP( strjoin(lines, "\n") );
}
if( !masks ) {
@ -715,7 +747,7 @@ bool cook_start( const char *cook_ini, const char *masks, int flags ) {
if( strend(fname, ".obj") ) {
char header[4] = {0};
for( FILE *in = fopen(fname, "rb"); in; fclose(in), in = NULL) {
fread(header, 1, 2, in);
fread(header, 2, 1, in);
}
if( !memcmp(header, "\x64\x86", 2) ) continue;
if( !memcmp(header, "\x00\x00", 2) ) continue;

View File

@ -1,77 +1,4 @@
static
array(char) base64__decode(const char *in_, unsigned inlen) {
// from libtomcrypt
#define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4)
#define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3)
#if 1
unsigned long outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen);
if( base64_decode((const unsigned char *)in_, (unsigned long)inlen, (unsigned char *)out_, &outlen) != CRYPT_OK ) {
array_free(out_);
return 0;
}
array_resize(out_, outlen);
return out_;
#else
unsigned outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen);
// based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN)
uint_least32_t v;
unsigned ii, io, rem;
char *out = (char *)out_;
const unsigned char *in = (const unsigned char *)in_;
const uint8_t base64dec_tab[256]= {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255, 62,255,255,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255, 63,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
};
for (io = 0, ii = 0,v = 0, rem = 0; ii < inlen; ii ++) {
unsigned char ch;
if (isspace(in[ii]))
continue;
if ((in[ii]=='=') || (!in[ii]))
break; /* stop at = or null character*/
ch = base64dec_tab[(unsigned char)in[ii]];
if (ch == 255)
break; /* stop at a parse error */
v = (v<<6) | ch;
rem += 6;
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
}
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
return (array_resize(out_, io), out_);
#endif
}
static array(json5) roots;
static array(char*) sources;
@ -244,7 +171,7 @@ array(char) (xml_blob)(char *key) { // base64 blob
if( !node ) return 0;
if( !strchr(key, '$') ) return 0;
const char *data = (const char*)node;
array(char) out = base64__decode(data, strlen(data)); // either array of chars (ok) or null (error)
array(char) out = base64_decode(data, strlen(data)); // either array of chars (ok) or null (error)
return out;
}

View File

@ -318,7 +318,7 @@ void* file_sha1(const char *file) { // 20bytes
sha1_init(&hs);
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = fread(buf, 1, sizeof(buf), fp)) > 0; ) {
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
sha1_process(&hs, (const unsigned char *)buf, inlen);
}
}
@ -332,7 +332,7 @@ void* file_md5(const char *file) { // 16bytes
md5_init(&hs);
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = fread(buf, 1, sizeof(buf), fp)) > 0; ) {
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
md5_process(&hs, (const unsigned char *)buf, inlen);
}
}
@ -345,7 +345,7 @@ void* file_crc32(const char *file) { // 4bytes
unsigned crc = 0;
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = fread(buf, 1, sizeof(buf), fp)) > 0; ) {
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
crc = zip__crc32(crc, buf, inlen); // unsigned int stbiw__crc32(unsigned char *buffer, int len)
}
}
@ -615,7 +615,7 @@ void vfs_reload() {
#define ARK_SWAP32(x) (x)
#define ARK_SWAP64(x) (x)
#define ARK_REALLOC REALLOC
static uint64_t ark_fget64( FILE *in ) { uint64_t v; fread( &v, 1, 8, in ); return ARK_SWAP64(v); }
static uint64_t ark_fget64( FILE *in ) { uint64_t v; fread( &v, 8, 1, in ); return ARK_SWAP64(v); }
void ark_list( const char *infile, zip **z ) {
for( FILE *in = fopen(infile, "rb"); in; fclose(in), in = 0 )
while(!feof(in)) {
@ -789,13 +789,13 @@ char* vfs_load(const char *pathfile, int *size_out) { // @todo: fix leaks, vfs_u
while( pathfile[0] == '.' && (pathfile[1] == '/' || pathfile[1] == '\\') ) pathfile += 2;
// if (pathfile[0] == '/' || pathfile[1] == ':') return file_load(pathfile, size_out); // @fixme: handle current cooked /home/V4K or C:/V4K path cases within zipfiles
if( size_out ) *size_out = 0;
if( strend(pathfile, "/") ) return 0; // it's a dir
static __thread map(char*,int) misses = 0, *init = 0; if(!init) init = misses, map_init(misses, less_str, hash_str);
int *found = map_find_or_add_allocated_key(misses, STRDUP(pathfile), -1); // [-1]non-init,[false]could not cook,[true]cooked
if( found && *found == 0 ) {
if( size_out ) *size_out = 0;
if( strend(pathfile, "/") ) return 0; // it's a dir
static __thread map(char*,int) misses = 0, *init = 0; if(!init) init = misses, map_init(misses, less_str, hash_str);
int *found = map_find_or_add_allocated_key(misses, STRDUP(pathfile), -1); // [-1]non-init,[false]could not cook,[true]cooked
if( found && *found == 0 ) {
return 0;
}
}
//{
// exclude garbage from material names
@ -841,7 +841,7 @@ char* vfs_load(const char *pathfile, int *size_out) { // @todo: fix leaks, vfs_u
}
// search (cache)
if( !ptr && ! is(osx) ) {
if( !ptr && !is(osx) ) { // @todo: remove silicon mac M1 hack
ptr = cache_lookup(lookup_id, &size);
}
@ -884,7 +884,7 @@ char* vfs_load(const char *pathfile, int *size_out) { // @todo: fix leaks, vfs_u
char *cmd = va("%scook" ifdef(osx,".osx",ifdef(linux,".linux",".exe"))" %s %s --cook-ini=%s --cook-additive --cook-jobs=1 --quiet", TOOLS, group1, group2, COOK_INI);
// cook groups
int rc = system(cmd);
int rc = atoi(app_exec(cmd));
if(rc < 0) PANIC("cannot invoke `%scook` (return code %d)", TOOLS, rc);
vfs_reload(); // @todo: optimize me. it is waaay inefficent to reload the whole VFS layout after cooking a single asset
@ -1126,7 +1126,9 @@ ini_t ini_from_mem(const char *data) {
}
ini_t ini(const char *filename) {
return ini_from_mem(file_read(filename));
char *kv = file_read(filename);
if(!kv) kv = vfs_read(filename);
return ini_from_mem(kv);
}
bool ini_write(const char *filename, const char *section, const char *key, const char *value) {

View File

@ -1667,7 +1667,6 @@ void font_scales(const char *tag, float h1, float h2, float h3, float h4, float
// 1. Calculate and save a bunch of useful variables and put them in the global font variable.
void font_face_from_mem(const char *tag, const void *ttf_data, unsigned ttf_len, float font_size, unsigned flags) {
unsigned index = *tag - FONT_FACE1[0];
if( index >= 8 ) return;
if( font_size <= 0 || font_size > 72 ) return;
if( !ttf_data || !ttf_len ) return;

View File

@ -753,7 +753,7 @@ int ui_gamepad(int gamepad_id) {
input_use(0);
return 0;
}
}
int ui_gamepads() {
for( int i = 0; i < 4; ++i ) ui_gamepad(i);

View File

@ -853,7 +853,7 @@ void printi_( int *m, int ii, int jj ) {
}
void print_( float *m, int ii, int jj ) {
for( int j = 0; j < jj; ++j ) {
for( int i = 0; i < ii; ++i ) printf("%8.3f ", *m++);
for( int i = 0; i < ii; ++i ) printf("%8.3f", *m++);
puts("");
}
// puts("---");

View File

@ -215,7 +215,7 @@ void *obj_free(void *o) {
FREE(o);
}
return 0;
}
}
return o; // cannot destroy: object is still referenced
}
@ -463,21 +463,23 @@ void test_obj_scene() {
static map(int,int) oms;
static thread_mutex_t *oms_lock;
void *obj_setmeta(void *o, const char *key, const char *value) {
void *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0);
if(!*q && !value[0]) {} else *q = intern(value);
return quark(*q), o;
quark(*q), ret = o;
}
return 0; // unreachable
return ret;
}
const char* obj_meta(const void *o, const char *key) {
const char *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0);
return quark(*q);
ret = quark(*q);
}
return 0; // unreachable
return ret;
}
void *obj_setname(void *o, const char *name) {
@ -637,6 +639,8 @@ const char *p2s(const char *type, void *p) {
else if( !strcmp(type, "vec2") ) return ftoa2(*(vec2*)p);
else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p);
else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)p);
else if( !strcmp(type, "rgb") ) return rgbatoa(*(unsigned*)p);
else if( !strcmp(type, "rgba") ) return rgbatoa(*(unsigned*)p);
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p);
// @todo: if strchr('*') assume obj, if reflected save guid: obj_id();
return tty_color(YELLOW), printf("p2s: cannot serialize `%s` type\n", type), tty_color(0), "";
@ -653,6 +657,8 @@ bool s2p(void *P, const char *type, const char *str) {
else if( !strcmp(type, "vec2") ) return !!memcpy(P, (v2 = atof2(str), &v2), sizeof(v2));
else if( !strcmp(type, "vec3") ) return !!memcpy(P, (v3 = atof3(str), &v3), sizeof(v3));
else if( !strcmp(type, "vec4") ) return !!memcpy(P, (v4 = atof4(str), &v4), sizeof(v4));
else if( !strcmp(type, "rgb") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
else if( !strcmp(type, "rgba") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
else if( !strcmp(type, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p));
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) {
char substring[128] = {0};
@ -907,7 +913,7 @@ void entity_register() {
do_once {
STRUCT(entity, uintptr_t, cflags);
obj_extend(entity, save);
}
}
}
AUTORUN{

View File

@ -342,4 +342,3 @@ typedef enum OBJTYPE_BUILTINS {
OBJTYPE_vec2i = 9,
OBJTYPE_vec3i = 10,
} OBJTYPE_BUILTINS;

View File

@ -626,7 +626,7 @@ static bool rd(void *buf, size_t len, size_t swap) { // return false any error a
bool ret;
if( in.fp ) {
assert( !ferror(in.fp) && "invalid file handle (reader)" );
ret = len == fread((char*)buf, 1, len, in.fp);
ret = 1 == fread((char*)buf, len, 1, in.fp);
} else {
assert( in.membuf && "invalid memory buffer (reader)");
assert( (in.offset + len <= in.memsize) && "memory overflow! (reader)");
@ -2304,7 +2304,7 @@ void entropy( void *buf, unsigned n ) {
FILE *fp = fopen( "/dev/urandom", "r" );
if( !fp ) assert(!"/dev/urandom open failed");
size_t read = fread( buf, 1, n, fp );
size_t read = n * fread( buf, n, 1, fp );
assert( read == n && "/dev/urandom read failed" );
fclose( fp );
}

View File

@ -386,3 +386,4 @@ API int saveb(unsigned char *buf, const char *format, ...);
API int loadf(FILE *file, const char *format, ...);
API int loadb(const unsigned char *buf, const char *format, ...);

View File

@ -11,7 +11,6 @@ void (ui_profiler)() {
profile_setstat("Render.num_fps", fps);
enum { COUNT = 300 };
static float values[COUNT] = {0}; static int offset = 0;
values[offset=(offset+1)%COUNT] = fps;

View File

@ -10,7 +10,7 @@ static map(unsigned, array(reflect_t)) members;
void reflect_init() {
if(!reflects) map_init_int(reflects);
if(!members) map_init_int(members);
}
}
AUTORUN {
reflect_init();
}
@ -22,7 +22,7 @@ const char* symbol_naked(const char *s) {
if(!strstr(s, " *") ) return s;
char *copy = va("%s", s);
do strswap(copy," *","*"); while( strstr(copy, " *") ); // char * -> char*
return (const char *)copy;
return (const char*)copy;
}
void type_inscribe(const char *TY,unsigned TYsz,const char *infos) {
@ -142,7 +142,7 @@ int ui_reflect(const char *filter) {
if( strmatchi(R->name, filter)) {
ui_reflect_(R, filter, masks[i]);
}
}
}
if( enabled ) ui_enable();
return 0;
@ -183,9 +183,9 @@ AUTOTEST {
//printf("+%s vec3.%s (+%x) // %s\n", R->type, R->name, R->member_offset, R->info);
}
// reflect_print("puts");
//reflect_print("puts");
//reflect_print("TEXTURE_RGBA");
//reflect_print("vec3");
// reflect_dump("*");
//reflect_dump("*");
}

View File

@ -96,7 +96,7 @@ unsigned shader_geom(const char *gs, const char *vs, const char *fs, const char
PRINTF(/*"!"*/"Compiling shader\n");
char *glsl_defines = "";
if (defines) {
if( defines ) {
for each_substring(defines, ",", def) {
glsl_defines = va("%s#define %s\n", glsl_defines, def);
}
@ -567,6 +567,27 @@ unsigned bgraf(float b, float g, float r, float a) {
return rgba(r * 255, g * 255, b * 255, a * 255);
}
unsigned atorgba(const char *s) {
if( s[0] != '#' ) return 0;
unsigned r = 0, g = 0, b = 0, a = 255;
int slen = strspn(s+1, "0123456789abcdefABCDEF");
if( slen > 8 ) slen = 8;
/**/ if( slen == 6 ) sscanf(s+1, "%2x%2x%2x", &r,&g,&b);
else if( slen == 8 ) sscanf(s+1, "%2x%2x%2x%2x", &r,&g,&b,&a);
else if( slen == 3 ) sscanf(s+1, "%1x%1x%1x", &r,&g,&b ), r=r<<4|r,g=g<<4|g,b=b<<4|b;
else if( slen == 4 ) sscanf(s+1, "%1x%1x%1x%1x", &r,&g,&b,&a), r=r<<4|r,g=g<<4|g,b=b<<4|b,a=a<<4|a;
return rgba(r,g,b,a);
}
char *rgbatoa(unsigned rgba) {
unsigned a = rgba >> 24;
unsigned b =(rgba >> 16) & 255;
unsigned g =(rgba >> 8) & 255;
unsigned r = rgba & 255;
char *s = va("# ");
sprintf(s+1, "%02x%02x%02x%02x", r,g,b,a);
return s;
}
// -----------------------------------------------------------------------------
// images
@ -1453,16 +1474,19 @@ void sprite( texture_t texture, float position[3], float rotation, uint32_t colo
sprite_sheet( texture, spritesheet, position, rotation, offset, scale, 0, color, false );
}
// rect(x,y,w,h) is [0..1] normalized, z-index, pos(x,y,scale), rotation (degrees), color (rgba)
void sprite_rect( texture_t t, vec4 rect, float zindex, vec3 pos, float tilt_deg, unsigned tint_rgba) {
// @todo: no need to queue if alpha or scale are zero
// rect(x,y,w,h) is [0..1] normalized, z-index, pos(x,y,scalex,scaley), rotation (degrees), color (rgba)
void sprite_rect( texture_t t, vec4 rect, float zindex, vec4 pos, float tilt_deg, unsigned tint_rgba) {
// do not queue if either alpha or scale is zero
if( 0 == (pos.z * pos.w * ((tint_rgba>>24) & 255)) ) return;
sprite_t s = {0};
s.x = rect.x, s.y = rect.y, s.w = rect.z, s.h = rect.w;
s.cellw = s.w * t.w, s.cellh = s.h * t.h;
s.px = pos.x, s.py = pos.y, s.pz = zindex;
s.sx = s.sy = pos.z;
s.sx = pos.z, s.sy = pos.w;
s.x = rect.x, s.y = rect.y, s.w = rect.z, s.h = rect.w;
s.cellw = s.w * s.sx * t.w, s.cellh = s.h * s.sy * t.h;
s.rgba = tint_rgba;
s.ox = 0/*ox*/ * s.sx;
s.oy = 0/*oy*/ * s.sy;
@ -1808,7 +1832,7 @@ tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols
return t;
}
int tileset_ui( tileset_t t ) {
int ui_tileset( tileset_t t ) {
ui_subimage(va("Selection #%d (%d,%d)", t.selected, t.selected % t.cols, t.selected / t.cols), t.tex.id, t.tex.w, t.tex.h, (t.selected % t.cols) * t.tile_w, (t.selected / t.cols) * t.tile_h, t.tile_w, t.tile_h);
int choice;
if( (choice = ui_image(0, t.tex.id, t.tex.w,t.tex.h)) ) {
@ -1922,7 +1946,7 @@ void tiled_render(tiled_t tmx, vec3 pos) {
}
}
void tiled_ui(tiled_t *t) {
void ui_tiled(tiled_t *t) {
ui_label2("Loaded map", t->map_name ? t->map_name : "(none)");
ui_label2("Map dimensions", va("%dx%d", t->w, t->h));
ui_label2("Tile dimensions", va("%dx%d", t->tilew, t->tileh));
@ -1939,7 +1963,7 @@ void tiled_ui(tiled_t *t) {
if( ui_collapse(va("Sets: %d", array_count(t->layers)), va("%p",t))) {
for( int i = 0; i < array_count(t->layers); ++i ) {
if( ui_collapse(va("%d", i+1), va("%p%d",t,i)) ) {
t->sets[i].selected = tileset_ui( t->sets[i] );
t->sets[i].selected = ui_tileset( t->sets[i] );
ui_collapse_end();
}
}
@ -2366,7 +2390,7 @@ void spine_render(spine_t *p, vec3 offset, unsigned flags) {
offsy = dir.y * r->sy;
}
sprite_rect(p->texture, rect, zindex, add3(vec3(target.x,target.y,1),vec3(offsx,offsy,0)), tilt, tint);
sprite_rect(p->texture, rect, zindex, add4(vec4(target.x,target.y,1,1),vec4(offsx,offsy,0,0)), tilt, tint);
}
}
@ -2414,7 +2438,7 @@ void spine_animate(spine_t *p, float delta) {
spine_animate_(p, &p->time, &p->maxtime, delta);
}
void spine_ui(spine_t *p) {
void ui_spine(spine_t *p) {
if( ui_collapse(va("Anims: %d", array_count(p->anims)), va("%p-a", p))) {
for each_array_ptr(p->anims, spine_anim_t, q) {
if(ui_slider2("", &p->time, va("%.2f/%.0f %.2f%%", p->time, p->maxtime, p->time * 100.f))) {
@ -2491,7 +2515,7 @@ void spine_ui(spine_t *p) {
sprite_rect(p->texture,
// rect: vec4(r->x*1.0/p->texture.w,r->y*1.0/p->texture.h,(r->x+r->w)*1.0/p->texture.w,(r->y+r->h)*1.0/p->texture.h),
ptr4(&r->x), // atlas
0, vec3(0,0,0), r->deg + tilt, tint);
0, vec4(0,0,1,1), r->deg + tilt, tint);
sprite_flush();
camera_get_active()->position = vec3(+window_width()/3,window_height()/2.25,2);
}
@ -4272,7 +4296,7 @@ bool model_load_textures(iqm_t *q, const struct iqmheader *hdr, model_t *model)
if( material_embedded_texture ) {
*material_embedded_texture = '\0';
material_embedded_texture += 5;
array(char) embedded_texture = base64__decode(material_embedded_texture, strlen(material_embedded_texture));
array(char) embedded_texture = base64_decode(material_embedded_texture, strlen(material_embedded_texture));
//printf("%s %d\n", material_embedded_texture, array_count(embedded_texture));
//hexdump(embedded_texture, array_count(embedded_texture));
*out = texture_compressed_from_mem( embedded_texture, array_count(embedded_texture), 0 ).id;
@ -4398,7 +4422,7 @@ model_t model_from_mem(const void *mem, int len, int flags) {
"att_position,att_texcoord,att_normal,att_tangent,att_instanced_matrix,,,,att_indexes,att_weights,att_vertexindex,att_color,att_bitangent","fragColor",
va("SHADING_PHONG,%s", (flags&MODEL_RIMLIGHT)?"RIM":""));
// }
ASSERT(shaderprog > 0);
// ASSERT(shaderprog > 0);
iqm_t *q = CALLOC(1, sizeof(iqm_t));
m.program = shaderprog;

View File

@ -37,6 +37,9 @@ API unsigned alpha( unsigned rgba );
#define BLUE RGBX(0xB55A06,255)
API unsigned atorgba(const char *s);
API char * rgbatoa(unsigned rgba);
// -----------------------------------------------------------------------------
// images
@ -121,6 +124,7 @@ typedef struct texture_t {
char* filename;
bool transparent;
unsigned fbo; // for texture recording
union { unsigned userdata, delay; };
} texture_t;
API texture_t texture_compressed(const char *filename, unsigned flags);
@ -185,8 +189,8 @@ API void fullscreen_quad_ycbcr_flipped( texture_t texture_YCbCr[3], float gamma
// texture id, position(x,y,depth sort), tint color, rotation angle
API void sprite( texture_t texture, float position[3], float rotation /*0*/, uint32_t color /*~0u*/);
// texture id, rect(x,y,w,h) is [0..1] normalized, z-index, pos(xy,scale), rotation (degrees), color (rgba)
API void sprite_rect( texture_t t, vec4 rect, float zindex, vec3 pos, float tilt_deg, unsigned tint_rgba);
// texture id, rect(x,y,w,h) is [0..1] normalized, z-index, pos(xy,scale.xy), rotation (degrees), color (rgba)
API void sprite_rect( texture_t t, vec4 rect, float zindex, vec4 pos, float tilt_deg, unsigned tint_rgba);
// texture id, sheet(frameNumber,X,Y) (frame in a X*Y spritesheet), position(x,y,depth sort), rotation angle, offset(x,y), scale(x,y), is_additive, tint color
API void sprite_sheet( texture_t texture, float sheet[3], float position[3], float rotation, float offset[2], float scale[2], int is_additive, uint32_t rgba, int resolution_independant);
@ -204,7 +208,8 @@ typedef struct tileset_t {
} tileset_t;
API tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols, unsigned rows);
API int tileset_ui( tileset_t t );
API int ui_tileset( tileset_t t );
typedef struct tilemap_t {
int blank_chr; // transparent tile
@ -239,7 +244,8 @@ typedef struct tiled_t {
API tiled_t tiled(const char *file_tmx);
API void tiled_render(tiled_t tmx, vec3 pos);
API void tiled_ui(tiled_t *t);
API void ui_tiled(tiled_t *t);
// -----------------------------------------------------------------------------
// spines
@ -250,7 +256,8 @@ API spine_t*spine(const char *file_json, const char *file_atlas, unsigned flags)
API void spine_skin(spine_t *p, unsigned skin);
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
API void spine_animate(spine_t *p, float delta);
API void spine_ui(spine_t *p);
API void ui_spine(spine_t *p);
// -----------------------------------------------------------------------------
// cubemaps

View File

@ -261,11 +261,12 @@ const char *strlerp(unsigned numpairs, const char **pairs, const char *str) { //
}
array(char*) strsplit(const char *str, const char *separators) {
enum { SLOTS = 32 };
static __thread int slot = 0;
static __thread char *buf[16] = {0};
static __thread array(char*) list[16] = {0};
static __thread char *buf[SLOTS] = {0};
static __thread array(char*) list[SLOTS] = {0};
slot = (slot+1) % 16;
slot = (slot+1) % SLOTS;
array_resize(list[slot], 0);
*(buf[slot] = REALLOC(buf[slot], strlen(str)*2+1)) = '\0'; // *2 to backup pathological case where input str is only separators && include == 1
@ -297,10 +298,11 @@ array(char*) strsplit(const char *str, const char *separators) {
return list[slot];
}
char* strjoin(array(char*) list, const char *separator) {
enum { SLOTS = 16 };
static __thread int slot = 0;
static __thread char* mems[16] = {0};
static __thread char* mems[SLOTS] = {0};
slot = (slot+1) % 16;
slot = (slot+1) % SLOTS;
int num_list = array_count(list);
int len = 0, inc = 0, seplen = strlen(separator);

View File

@ -90,28 +90,72 @@ const char *app_cache() {
const char * app_exec( const char *cmd ) {
static __thread char output[4096+16] = {0};
char *buf = output + 16; buf[0] = 0; // memset(buf, 0, 4096);
if( !cmd[0] ) return "0 ";
cmd = file_normalize(cmd);
int rc = -1;
char *buf = output + 16; buf[0] = 0; // memset(buf, 0, 4096);
// pick the fastest code path per platform
#if is(osx)
for( FILE *fp = popen( cmd, "r" ); fp; rc = pclose(fp), fp = 0) {
while( fgets(buf, 4096 - 1, fp) ) {
// while( fgets(buf, 4096 - 1, fp) ) {}
}
// if( rc != 0 ) {
// char *r = strrchr(buf, '\r'); if(r) *r = 0;
// char *n = strrchr(buf, '\n'); if(n) *n = 0;
// }
#elif is(win32)
STARTUPINFOA si = {0}; si.cb = sizeof(si);
PROCESS_INFORMATION pi = {0};
snprintf(output+16, 4096, "cmd /c \"%s\"", cmd);
int prio = //strstr(cmd, "ffmpeg") || strstr(cmd, "furnace") || strstr(cmd, "ass2iqe") ?
REALTIME_PRIORITY_CLASS; //: 0;
//prio |= DETACHED_PROCESS;
//si.dwFlags = STARTF_USESTDHANDLES;
if( CreateProcessA(
NULL, output+16, // cmdline
NULL,
NULL,
FALSE, // FALSE: dont inherit handles
prio /*CREATE_DEFAULT_ERROR_MODE|CREATE_NO_WINDOW*/, // 0|HIGH_PRIORITY_CLASS
NULL, // "", // NULL would inherit env
NULL, // current dir
&si, &pi) )
{
// Wait for process
DWORD dwExitCode2 = WaitForSingleObject(pi.hProcess, INFINITE);
DWORD dwExitCode; GetExitCodeProcess(pi.hProcess, &dwExitCode);
rc = dwExitCode;
}
if( rc != 0 ) {
char *r = strrchr(buf, '\r'); if(r) *r = 0;
char *n = strrchr(buf, '\n'); if(n) *n = 0;
else
{
// CreateProcess() failed
rc = GetLastError();
}
#else
rc = system(cmd);
#endif
return snprintf(output, 16, "%-15d", rc), buf[-1] = ' ', output;
}
int app_spawn( const char *cmd ) {
if( !cmd[0] ) return -1;
if( !cmd[0] ) return false;
cmd = file_normalize(cmd);
return system(cmd);
#if _WIN32
bool ok = WinExec(va("cmd /c \"%s\"", cmd), SW_HIDE) > 31;
#else
bool ok = system(va("%s &", cmd)) == 0;
#endif
return ok;
}
#if is(osx)
@ -694,7 +738,7 @@ void alert(const char *message) { // @todo: move to app_, besides die()
#endif
window_visible(true);
}
}
void breakpoint() {
debugbreak();
@ -807,9 +851,9 @@ void app_crash() {
*p = 42;
}
void app_beep() {
ifdef(win32, system("rundll32 user32.dll,MessageBeep"); return; );
ifdef(linux, system("paplay /usr/share/sounds/freedesktop/stereo/message.oga"); return; );
ifdef(osx, system("tput bel"); return; );
ifdef(win32, app_spawn("rundll32 user32.dll,MessageBeep"); return; );
ifdef(linux, app_spawn("paplay /usr/share/sounds/freedesktop/stereo/message.oga"); return; );
ifdef(osx, app_spawn("tput bel"); return; );
//fallback:
fputc('\x7', stdout);
@ -864,7 +908,7 @@ bool app_open_folder(const char *file) {
#else
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
#endif
return system(buf) == 0;
return app_spawn(buf);
}
static
@ -877,7 +921,7 @@ bool app_open_file(const char *file) {
#else
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
#endif
return system(buf) == 0;
return app_spawn(buf);
}
static

View File

@ -59,13 +59,13 @@ API void trap_on_quit(int signal); // helper util
API void trap_on_abort(int signal); // helper util
API void trap_on_debug(int signal); // helper util
#define PANIC(...) PANIC(va(__VA_ARGS__), strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__) // die() ?
#define PANIC(...) PANIC(va(""__VA_ARGS__), __FILE__, __LINE__) // die() ?
API int (PANIC)(const char *error, const char *file, int line);
#define PRINTF(...) PRINTF(va(__VA_ARGS__), 1[#__VA_ARGS__] == '!' ? callstack(+48) : "", strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__, __FUNCTION__)
#define PRINTF(...) PRINTF(va(""__VA_ARGS__), 1[""#__VA_ARGS__] == '!' ? callstack(+48) : "", __FILE__, __LINE__, __FUNCTION__)
API int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
#define test(expr) test(strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__,__LINE__,#expr,!!(expr))
#define test(expr) test(__FILE__,__LINE__,#expr,!!(expr))
API int (test)(const char *file, int line, const char *expr, bool result);
#if ENABLE_AUTOTESTS

View File

@ -103,8 +103,8 @@ static uint64_t nanotimer(uint64_t *out_freq) {
}
uint64_t time_ns() {
static uint64_t epoch = 0;
static uint64_t freq = 0;
static __thread uint64_t epoch = 0;
static __thread uint64_t freq = 0;
if( !freq ) {
epoch = nanotimer(&freq);
}

View File

@ -107,10 +107,11 @@ static int ui_using_v2_menubar = 0;
nk_menu_close(ui_ctx); \
nk_menu_end(ui_ctx); \
}}
#define UI_MENU_ALIGN_RIGHT(px) { \
#define UI_MENU_ALIGN_RIGHT(px, ...) { \
int hspace = total_space.w - span - (px) - 1.5 * ITEM_WIDTH; \
nk_layout_row_push(ui_ctx, hspace); span += hspace; \
if (nk_menu_begin_label(ui_ctx, (title), align = NK_TEXT_RIGHT, nk_vec2(1,1))) { \
__VA_ARGS__; \
nk_menu_close(ui_ctx); \
nk_menu_end(ui_ctx); \
}}
@ -1702,20 +1703,20 @@ int ui_label(const char *label) {
}
static int nk_label_(struct nk_context *ui_ctx, const char *text_, int align2 ) {
const struct nk_input *input = &ui_ctx->input;
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
if( is_hovering ) {
const struct nk_input *input = &ui_ctx->input;
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
if( is_hovering ) {
struct nk_rect winbounds = nk_window_get_bounds(ui_ctx);
is_hovering &= nk_input_is_mouse_hovering_rect(input, winbounds);
is_hovering &= nk_window_has_focus(ui_ctx);
}
}
nk_label(ui_ctx, text_, align2);
// this is an ugly hack to detect which icon (within a label) we're clicking on.
// @todo: figure out a better way to detect this... would it be better to have a ui_label_toolbar(lbl,bar) helper function instead?
ui_label_icon_clicked_R.x = is_hovering ? ( (int)((input->mouse.pos.x - bounds.x) - (align2 == NK_TEXT_RIGHT ? bounds.w : 0) ) * nk_input_is_mouse_released(input, NK_BUTTON_LEFT)) : 0;
// this is an ugly hack to detect which icon (within a label) we're clicking on.
// @todo: figure out a better way to detect this... would it be better to have a ui_label_toolbar(lbl,bar) helper function instead?
ui_label_icon_clicked_R.x = is_hovering ? ( (int)((input->mouse.pos.x - bounds.x) - (align2 == NK_TEXT_RIGHT ? bounds.w : 0) ) * nk_input_is_mouse_released(input, NK_BUTTON_LEFT)) : 0;
return ui_label_icon_clicked_R.x;
}
@ -1892,24 +1893,60 @@ int ui_toggle(const char *label, bool *value) {
return rc ? (*value ^= 1), rc : rc;
}
int ui_color4f(const char *label, float *color4) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
float c[4] = { color4[0]*255, color4[1]*255, color4[2]*255, color4[3]*255 };
int ret = ui_color4(label, c);
for( int i = 0; i < 4; ++i ) color4[i] = c[i] / 255.0f;
return ret;
}
static enum color_mode {COL_RGB, COL_HSV} ui_color_mode = COL_RGB;
int ui_color4(const char *label, float *color4) {
int ui_color4f(const char *label, float *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { color4[0]*ui_alpha/255, color4[1]*ui_alpha/255, color4[2]*ui_alpha/255, color4[3]/255 }, before = after;
struct nk_colorf after = { color[0]*ui_alpha, color[1]*ui_alpha, color[2]*ui_alpha, color[3] }, before = after;
struct nk_colorf clamped = { clampf(color[0],0,1), clampf(color[1],0,1), clampf(color[2],0,1), clampf(color[3],0,1) };
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(clamped), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", -FLT_MAX, after.r, FLT_MAX, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", -FLT_MAX, after.g, FLT_MAX, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", -FLT_MAX, after.b, FLT_MAX, 0.01f,0.005f);
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", -FLT_MAX, hsva[0], FLT_MAX, 0.01f,0.005f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", -FLT_MAX, hsva[1], FLT_MAX, 0.01f,0.005f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", -FLT_MAX, hsva[2], FLT_MAX, 0.01f,0.005f);
after = nk_hsva_colorfv(hsva);
}
nk_label(ui_ctx, va("#%02X%02X%02X", (unsigned)clampf(after.r*255,0,255), (unsigned)clampf(after.g*255,0,255), (unsigned)clampf(after.b*255,0,255)), NK_TEXT_CENTERED);
color[0] = after.r;
color[1] = after.g;
color[2] = after.b;
color[3] = after.a;
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color4(const char *label, unsigned *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
unsigned a = *color >> 24;
unsigned b =(*color >> 16)&255;
unsigned g =(*color >> 8)&255;
unsigned r = *color & 255;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { r*ui_alpha/255, g*ui_alpha/255, b*ui_alpha/255, a*ui_alpha/255 }, before = after;
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGBA);
@ -1920,44 +1957,83 @@ int ui_color4(const char *label, float *color4) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", 0, after.r, 1.0f, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", 0, after.g, 1.0f, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", 0, after.b, 1.0f, 0.01f,0.005f);
after.a = nk_propertyf(ui_ctx, "#A:", 0, after.a, 1.0f, 0.01f,0.005f);
after.r = nk_propertyi(ui_ctx, "#R:", 0, after.r * 255, 255, 1,1) / 255.f;
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
after.a = nk_propertyi(ui_ctx, "#A:", 0, after.a * 255, 255, 1,1) / 255.f;
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", 0, hsva[0], 1.0f, 0.01f,0.05f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", 0, hsva[1], 1.0f, 0.01f,0.05f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", 0, hsva[2], 1.0f, 0.01f,0.05f);
hsva[3] = nk_propertyf(ui_ctx, "#A:", 0, hsva[3], 1.0f, 0.01f,0.05f);
hsva[0] = nk_propertyi(ui_ctx, "#H:", 0, hsva[0] * 255, 255, 1,1) / 255.f;
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
hsva[3] = nk_propertyi(ui_ctx, "#A:", 0, hsva[3] * 255, 255, 1,1) / 255.f;
after = nk_hsva_colorfv(hsva);
}
r = after.r * 255;
g = after.g * 255;
b = after.b * 255;
a = after.a * 255;
*color = rgba(r,g,b,a);
color4[0] = after.r * 255;
color4[1] = after.g * 255;
color4[2] = after.b * 255;
color4[3] = after.a * 255;
nk_label(ui_ctx, va("#%02X%02X%02X%02X", r, g, b, a), NK_TEXT_CENTERED);
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color3f(const char *label, float *color3) {
float c[3] = { color3[0]*255, color3[1]*255, color3[2]*255 };
int ret = ui_color3(label, c);
for( int i = 0; i < 3; ++i ) color3[i] = c[i] / 255.0f;
return ret;
}
int ui_color3(const char *label, float *color3) {
int ui_color3f(const char *label, float *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { color3[0]*ui_alpha/255, color3[1]*ui_alpha/255, color3[2]*ui_alpha/255, 1 }, before = after;
struct nk_colorf after = { color[0]*ui_alpha, color[1]*ui_alpha, color[2]*ui_alpha, ui_alpha }, before = after;
struct nk_colorf clamped = { clampf(color[0],0,1), clampf(color[1],0,1), clampf(color[2],0,1), 1 };
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(clamped), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", -FLT_MAX, after.r, FLT_MAX, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", -FLT_MAX, after.g, FLT_MAX, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", -FLT_MAX, after.b, FLT_MAX, 0.01f,0.005f);
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", -FLT_MAX, hsva[0], FLT_MAX, 0.01f,0.005f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", -FLT_MAX, hsva[1], FLT_MAX, 0.01f,0.005f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", -FLT_MAX, hsva[2], FLT_MAX, 0.01f,0.005f);
after = nk_hsva_colorfv(hsva);
}
nk_label(ui_ctx, va("#%02X%02X%02X", (unsigned)clampf(after.r*255,0,255), (unsigned)clampf(after.g*255,0,255), (unsigned)clampf(after.b*255,0,255)), NK_TEXT_CENTERED);
color[0] = after.r;
color[1] = after.g;
color[2] = after.b;
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color3(const char *label, unsigned *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
unsigned a = *color >> 24;
unsigned b =(*color >> 16)&255;
unsigned g =(*color >> 8)&255;
unsigned r = *color & 255;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { r*ui_alpha, g*ui_alpha, b*ui_alpha, 1 }, before = after;
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
@ -1968,21 +2044,23 @@ int ui_color3(const char *label, float *color3) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", 0, after.r, 1.0f, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", 0, after.g, 1.0f, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", 0, after.b, 1.0f, 0.01f,0.005f);
after.r = nk_propertyi(ui_ctx, "#R:", 0, after.r * 255, 255, 1,1) / 255.f;
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", 0, hsva[0], 1.0f, 0.01f,0.05f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", 0, hsva[1], 1.0f, 0.01f,0.05f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", 0, hsva[2], 1.0f, 0.01f,0.05f);
hsva[0] = nk_propertyi(ui_ctx, "#H:", 0, hsva[0] * 255, 255, 1,1) / 255.f;
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
after = nk_hsva_colorfv(hsva);
}
r = after.r * 255;
g = after.g * 255;
b = after.b * 255;
*color = rgba(r,g,b,a);
color3[0] = after.r * 255;
color3[1] = after.g * 255;
color3[2] = after.b * 255;
nk_label(ui_ctx, va("#%02X%02X%02X", r, g, b), NK_TEXT_CENTERED);
nk_combo_end(ui_ctx);
}
@ -2055,6 +2133,8 @@ int ui_bool(const char *label, bool *enabled ) {
return chg;
}
static int ui_num_signs = 0;
int ui_int(const char *label, int *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
@ -2076,6 +2156,45 @@ int ui_unsigned(const char *label, unsigned *v) {
*v = (unsigned)nk_propertyd(ui_ctx, "#", 0, *v, UINT_MAX, 1,1);
return prev != *v;
}
int ui_unsigned2(const char *label, unsigned *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+2u %+2u", v[0], v[1]) :
va("%2u, %2u", v[0], v[1]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
unsigned prev0 = v[0]; nk_property_int(ui_ctx, "#X:", 0, &v[0], INT_MAX, 1,0.5f);
unsigned prev1 = v[1]; nk_property_int(ui_ctx, "#Y:", 0, &v[1], INT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1];
}
return 0;
}
int ui_unsigned3(const char *label, unsigned *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+2u %+2u %+2u", v[0], v[1], v[2]) :
va("%2u, %2u, %2u", v[0], v[1], v[2]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
unsigned prev0 = v[0]; nk_property_int(ui_ctx, "#X:", 0, &v[0], INT_MAX, 1,0.5f);
unsigned prev1 = v[1]; nk_property_int(ui_ctx, "#Y:", 0, &v[1], INT_MAX, 1,0.5f);
unsigned prev2 = v[2]; nk_property_int(ui_ctx, "#Z:", 0, &v[2], INT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1] || prev2 != v[2];
}
return 0;
}
int ui_short(const char *label, short *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
@ -2116,16 +2235,14 @@ int ui_clampf(const char *label, float *v, float minf, float maxf) {
return prev != v[0];
}
static bool ui_float_sign = 0;
int ui_float2(const char *label, float *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_float_sign ?
--ui_float_sign, va("%+.3f %+.3f", v[0], v[1]) :
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.3f %+.3f", v[0], v[1]) :
va("%.3f, %.3f", v[0], v[1]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
@ -2144,8 +2261,8 @@ int ui_float3(const char *label, float *v) {
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_float_sign ?
--ui_float_sign, va("%+.2f %+.2f %+.2f", v[0], v[1], v[2]) :
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.2f %+.2f %+.2f", v[0], v[1], v[2]) :
va("%.2f, %.2f, %.2f", v[0], v[1], v[2]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
@ -2165,8 +2282,8 @@ int ui_float4(const char *label, float *v) {
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_float_sign ?
--ui_float_sign, va("%+.2f %+.2f %+.2f %+.2f", v[0], v[1], v[2], v[3]) :
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.2f %+.2f %+.2f %+.2f", v[0], v[1], v[2], v[3]) :
va("%.2f,%.2f,%.2f,%.2f", v[0], v[1], v[2], v[3]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
@ -2185,7 +2302,7 @@ int ui_float4(const char *label, float *v) {
int ui_mat33(const char *label, float M[9]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_float_sign = 3;
ui_num_signs = 3;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float3(NULL, M);
@ -2196,7 +2313,7 @@ int ui_mat33(const char *label, float M[9]) {
int ui_mat34(const char *label, float M[12]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_float_sign = 3;
ui_num_signs = 3;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float4(NULL, M);
@ -2207,7 +2324,7 @@ int ui_mat34(const char *label, float M[12]) {
int ui_mat44(const char *label, float M[16]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_float_sign = 4;
ui_num_signs = 4;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float4(NULL, M);

View File

@ -29,11 +29,13 @@ API int ui_mat44(const char *label, float mat44[16]);
API int ui_double(const char *label, double *value);
API int ui_buffer(const char *label, char *buffer, int buflen);
API int ui_string(const char *label, char **string);
API int ui_color3(const char *label, float *color3); //[0..255]
API int ui_color3f(const char *label, float *color3); //[0..1]
API int ui_color4(const char *label, float *color4); //[0..255]
API int ui_color4f(const char *label, float *color4); //[0..1]
API int ui_color3(const char *label, unsigned *color); //[0..255]
API int ui_color3f(const char *label, float color[3]); //[0..1]
API int ui_color4(const char *label, unsigned *color); //[0..255]
API int ui_color4f(const char *label, float color[4]); //[0..1]
API int ui_unsigned(const char *label, unsigned *value);
API int ui_unsigned2(const char *label, unsigned *value);
API int ui_unsigned3(const char *label, unsigned *value);
API int ui_button(const char *label);
API int ui_button_transparent(const char *label);
API int ui_buttons(int buttons, /*labels*/...);

View File

@ -168,7 +168,7 @@ void window_drop_callback(GLFWwindow* window, int count, const char** paths) {
void window_hints(unsigned flags) {
#ifdef __APPLE__
//glfwInitHint( GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE );
glfwWindowHint( GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE );
glfwWindowHint( GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE ); // @todo: remove silicon mac M1 hack
//glfwWindowHint( GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_FALSE );
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
#endif
@ -317,6 +317,9 @@ bool window_create_from_handle(void *handle, float scale, unsigned flags) {
//glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // always on top
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
}
if( flags & WINDOW_BORDERLESS ) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
#endif
// windowed
float ratio = (float)winWidth / (winHeight + !winHeight);
@ -387,7 +390,7 @@ bool window_create_from_handle(void *handle, float scale, unsigned flags) {
PRINTF("GPU OpenGL: %d.%d\n", GLAD_VERSION_MAJOR(gl_version), GLAD_VERSION_MINOR(gl_version));
if( FLAGS_TRANSPARENT ) { // @transparent
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE);
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE); // @todo: is decorated an attrib or a hint?
if( scale >= 1 ) glfwMaximizeWindow(window);
}
#endif
@ -825,7 +828,7 @@ int window_record(const char *outfile_mp4) {
vec2 window_dpi() {
vec2 dpi = vec2(1,1);
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
#if !is(ems) && !is(osx) // @todo: remove silicon mac M1 hack
glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &dpi.x, &dpi.y);
#endif
return dpi;

View File

@ -17,6 +17,7 @@ enum WINDOW_FLAGS {
WINDOW_ASPECT = 0x100, // keep aspect
WINDOW_FIXED = 0x200, // disable resizing
WINDOW_TRANSPARENT = 0x400,
WINDOW_BORDERLESS = 0x800,
WINDOW_VSYNC = 0,
WINDOW_VSYNC_ADAPTIVE = 0x1000,

View File

@ -13943,6 +13943,7 @@ int gladLoadGL( GLADloadfunc load) {
// 3rd party libs
#define ARCHIVE_C // archive.c
#define BASE64_C // base64.c
#define COMPRESS_C // compress.c
#define ENET_IMPLEMENTATION // enet
#define GJK_C // gjk
@ -232706,12 +232707,7 @@ static bool LzmaDec_Init(CLzmaDec *p, const uint8_t *raw_props)
// glue.c
static
#ifdef _MSC_VER
__declspec(thread)
#else
__thread
#endif
static __thread
struct {
uint8_t *begin, *seek, *end;
}
@ -234051,9 +234047,7 @@ static inline uint32_t DecodeMod(const uint8_t** p) {
// LZ77
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen) {
ULZ_WORKMEM *u =(ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen, ULZ_WORKMEM *u) {
for (int i=0; i<ULZ_HASH_SIZE; ++i)
u->HashTable[i]=ULZ_NIL;
@ -234135,16 +234129,14 @@ static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outle
op+=run;
}
ULZ_REALLOC(u, 0);
return op-out;
}
static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, int level) {
static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, int level, ULZ_WORKMEM *u) {
if (level<1 || level>9)
return 0;
const int max_chain=(level<9)?1<<level:1<<13;
ULZ_WORKMEM *u = (ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
for (int i=0; i<ULZ_HASH_SIZE; ++i)
u->HashTable[i]=ULZ_NIL;
@ -234269,7 +234261,6 @@ static int UlzCompress(const uint8_t* in, int inlen, uint8_t* out, int outlen, i
op+=run;
}
ULZ_REALLOC(u, 0);
return op-out;
}
@ -234326,9 +234317,11 @@ static int UlzDecompress(const uint8_t* in, int inlen, uint8_t* out, int outlen)
}
unsigned ulz_encode(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags) {
static __thread ULZ_WORKMEM u;
int level = flags > 9 ? 9 : flags; // [0..(6)..9]
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level)
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen);
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level, &u)
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, &u);
return (unsigned)rc;
}
unsigned ulz_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
@ -234464,7 +234457,7 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
double enctime = 0;
if( logfile ) tm = clock();
{
for( uint32_t inlen; (inlen=fread(inbuf, 1, BS_BYTES, in)) > 0 ; ) {
for( uint32_t inlen; (inlen=BS_BYTES * fread(inbuf, BS_BYTES, 1, in)) > 0 ; ) {
uint32_t outlen[2] = {0};
best = clist[0];
@ -234499,15 +234492,15 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
if( compr ) {
uint8_t packer = (compr << 4) | flags;
// store block length + compressor + compr data
if( fwrite(&outlen[0], 1, 4, out) != 4 ) goto fail;
if( fwrite(&outlen[0], 4, 1, out) != 1 ) goto fail;
if( fwrite(&packer, 1, 1, out) != 1 ) goto fail;
if( fwrite(outbuf[0], 1, outlen[0], out) != outlen[0] ) goto fail;
if( fwrite(outbuf[0], outlen[0], 1, out) != 1 ) goto fail;
} else {
uint8_t packer = 0;
// store block length + no-compressor + raw data
if( fwrite(&inlen, 1, 4, out) != 4 ) goto fail;
if( fwrite(&inlen, 4, 1, out) != 1 ) goto fail;
if( fwrite(&packer, 1, 1, out) != 1 ) goto fail;
if( fwrite(inbuf, 1, inlen, out) != inlen ) goto fail;
if( fwrite(inbuf, inlen, 1, out) != 1 ) goto fail;
}
total_in += inlen;
@ -234541,8 +234534,8 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
uint8_t block8; if( fread(&block8, 1,1, in ) < 1 ) return 0;
uint8_t excess8; if( fread(&excess8, 1,1, in ) < 1 ) return 0;
uint8_t block8; if( fread(&block8, 1,1, in ) != 1 ) return 0;
uint8_t excess8; if( fread(&excess8, 1,1, in ) != 1 ) return 0;
uint64_t BLOCK_SIZE = 1ull << block8;
uint64_t EXCESS = 1ull << excess8;
@ -234554,15 +234547,15 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
double dectime = 0;
if(logfile) tm = clock();
{
for(uint32_t inlen=0, loop=0;fread(&inlen, 1, sizeof(inlen), in)>0;++loop) {
for(uint32_t inlen=0, loop=0;fread(&inlen, sizeof(inlen), 1, in) == 1;++loop) {
if (inlen>(BLOCK_SIZE+EXCESS)) goto fail;
uint8_t packer;
if( fread(&packer, 1,sizeof(packer), in) <= 0 ) goto fail;
if( fread(&packer, sizeof(packer),1, in) != 1 ) goto fail;
if(packer) {
// read compressed
if (fread(inbuf, 1, inlen, in)!=inlen) goto fail;
if (fread(inbuf, inlen,1, in)!=1) goto fail;
// decompress
uint8_t compressor = packer >> 4;
@ -234570,11 +234563,11 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
if (!outlen) goto fail;
} else {
// read raw
if (fread(outbuf, 1, inlen, in)!=inlen) goto fail;
if (fread(outbuf, inlen,1, in)!=1) goto fail;
outlen=inlen;
}
if (fwrite(outbuf, 1, outlen, out) != outlen) {
if (fwrite(outbuf, outlen, 1, out) != 1) {
perror("fwrite() failed");
goto fail;
}
@ -234627,6 +234620,7 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
#define ZIP_H
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
typedef struct zip zip;
@ -235150,7 +235144,7 @@ bool zip_append_file_timeinfo(zip *z, const char *entryname, const char *comment
// @fixme: calc whole crc contents
uint32_t crc = 0;
unsigned char buf[1<<15];
unsigned char buf[4096];
while(!feof(in) && !ferror(in)) crc = zip__crc32(crc, buf, fread(buf, 1, sizeof(buf), in));
if(ferror(in)) return ERR(false, "Error while calculating CRC, skipping store.");
@ -259869,10 +259863,10 @@ extern const ltc_math_descriptor gmp_desc;
/* ---- LTC_BASE64 Routines ---- */
#ifdef LTC_BASE64
int base64_encode(const unsigned char *in, unsigned long len,
int base64_encodex(const unsigned char *in, unsigned long len, //< @r-lyeh +x
unsigned char *out, unsigned long *outlen);
int base64_decode(const unsigned char *in, unsigned long len,
int base64_decodex(const unsigned char *in, unsigned long len, //< @r-lyeh +x
unsigned char *out, unsigned long *outlen);
#endif
@ -272590,7 +272584,7 @@ static int _base64_decode_internal(const unsigned char *in, unsigned long inlen
@param outlen [in/out] The max size and resulting size of the decoded data
@return CRYPT_OK if successful
*/
int base64_decode(const unsigned char *in, unsigned long inlen,
int base64_decodex(const unsigned char *in, unsigned long inlen,
unsigned char *out, unsigned long *outlen)
{
return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed);
@ -315369,6 +315363,148 @@ int dbg_pcall(lua_State *lua, int nargs, int nresults, int msgh){
return err;
}
#line 0
#line 1 "engine/split/3rd_base64.h"
// base64 de/encoder. Based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN).
// - rlyeh, public domain
#ifndef BASE64_H
#define BASE64_H
unsigned base64_bounds(unsigned size);
char* base64_encode(const void *inp, unsigned inlen); // free() after use
char* base64_decode(const char *inp, unsigned inlen); // array_free() after use
#endif
#ifdef BASE64_C
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
unsigned base64_bounds(unsigned size) {
return BASE64_ENCODE_OUT_SIZE(size);
}
char* base64_encode(const void *inp, unsigned inlen) { // free() after use
unsigned outlen = base64_bounds(inlen);
char *out_ = malloc(outlen);
out_[outlen] = 0;
uint_least32_t v;
unsigned ii, io, rem;
char *out = (char *)out_;
const unsigned char *in = (const unsigned char *)inp;
const uint8_t base64enc_tab[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for(io = 0, ii = 0, v = 0, rem = 0; ii < inlen; ii ++) {
unsigned char ch;
ch = in[ii];
v = (v << 8) | ch;
rem += 8;
while (rem >= 6) {
rem -= 6;
if (io >= outlen)
return (free(out_), 0); /* truncation is failure */
out[io ++] = base64enc_tab[(v >> rem) & 63];
}
}
if (rem) {
v <<= (6 - rem);
if (io >= outlen)
return (free(out_), 0); /* truncation is failure */
out[io ++] = base64enc_tab[v & 63];
}
while(io&3) {
if(io>=outlen) return (free(out_), 0); /* truncation is failure */
out[io++]='=';
}
if(io>=outlen) return (free(out_), 0); /* no room for null terminator */
out[io]=0;
return out_;
}
#ifdef array_resize
array(char) base64_decode(const char *inp, unsigned inlen) { // array_free() after use
#if 0
unsigned long outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen+1);
if( base64_decodex((const unsigned char *)inp, (unsigned long)inlen, (unsigned char *)out_, &outlen) != CRYPT_OK ) {
array_free(out_);
return 0;
}
array_resize(out_, outlen);
out_[outlen] = 0;
return out_;
#else
unsigned outlen = BASE64_DECODE_OUT_SIZE(inlen);
array(char) out_ = 0; array_resize(out_, outlen);
// based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN)
uint_least32_t v;
unsigned ii, io, rem;
char *out = (char *)out_;
const unsigned char *in = (const unsigned char *)inp;
const uint8_t base64dec_tab[256]= {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255 };
for (io = 0, ii = 0,v = 0, rem = 0; ii < inlen; ii ++) {
unsigned char ch;
if (isspace(in[ii]))
continue;
if ((in[ii]=='=') || (!in[ii]))
break; /* stop at = or null character*/
ch = base64dec_tab[(unsigned char)in[ii]];
if (ch == 255)
break; /* stop at a parse error */
v = (v<<6) | ch;
rem += 6;
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
}
if (rem >= 8) {
rem -= 8;
if (io >= outlen)
return (array_free(out_), NULL); /* truncation is failure */
out[io ++] = (v >> rem) & 255;
}
return (array_resize(out_, io), out_);
#endif
}
#endif // array_resize
#endif // BASE64_C
#line 0
//#define SQLITE_OMIT_LOAD_EXTENSION
//#define SQLITE_CORE 1
//#define SQLITE_DEBUG 1

File diff suppressed because it is too large Load Diff

View File

@ -1575,7 +1575,6 @@ typedef enum OBJTYPE_BUILTINS {
OBJTYPE_vec2i = 9,
OBJTYPE_vec3i = 10,
} OBJTYPE_BUILTINS;
#line 0
@ -2992,6 +2991,7 @@ API int saveb(unsigned char *buf, const char *format, ...);
API int loadf(FILE *file, const char *format, ...);
API int loadb(const unsigned char *buf, const char *format, ...);
#line 0
#line 1 "engine/split/v4k_profile.h"
@ -3133,6 +3133,9 @@ API unsigned alpha( unsigned rgba );
#define BLUE RGBX(0xB55A06,255)
API unsigned atorgba(const char *s);
API char * rgbatoa(unsigned rgba);
// -----------------------------------------------------------------------------
// images
@ -3217,6 +3220,7 @@ typedef struct texture_t {
char* filename;
bool transparent;
unsigned fbo; // for texture recording
union { unsigned userdata, delay; };
} texture_t;
API texture_t texture_compressed(const char *filename, unsigned flags);
@ -3281,8 +3285,8 @@ API void fullscreen_quad_ycbcr_flipped( texture_t texture_YCbCr[3], float gamma
// texture id, position(x,y,depth sort), tint color, rotation angle
API void sprite( texture_t texture, float position[3], float rotation /*0*/, uint32_t color /*~0u*/);
// texture id, rect(x,y,w,h) is [0..1] normalized, z-index, pos(xy,scale), rotation (degrees), color (rgba)
API void sprite_rect( texture_t t, vec4 rect, float zindex, vec3 pos, float tilt_deg, unsigned tint_rgba);
// texture id, rect(x,y,w,h) is [0..1] normalized, z-index, pos(xy,scale.xy), rotation (degrees), color (rgba)
API void sprite_rect( texture_t t, vec4 rect, float zindex, vec4 pos, float tilt_deg, unsigned tint_rgba);
// texture id, sheet(frameNumber,X,Y) (frame in a X*Y spritesheet), position(x,y,depth sort), rotation angle, offset(x,y), scale(x,y), is_additive, tint color
API void sprite_sheet( texture_t texture, float sheet[3], float position[3], float rotation, float offset[2], float scale[2], int is_additive, uint32_t rgba, int resolution_independant);
@ -3300,7 +3304,8 @@ typedef struct tileset_t {
} tileset_t;
API tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols, unsigned rows);
API int tileset_ui( tileset_t t );
API int ui_tileset( tileset_t t );
typedef struct tilemap_t {
int blank_chr; // transparent tile
@ -3335,7 +3340,8 @@ typedef struct tiled_t {
API tiled_t tiled(const char *file_tmx);
API void tiled_render(tiled_t tmx, vec3 pos);
API void tiled_ui(tiled_t *t);
API void ui_tiled(tiled_t *t);
// -----------------------------------------------------------------------------
// spines
@ -3346,7 +3352,8 @@ API spine_t*spine(const char *file_json, const char *file_atlas, unsigned flags)
API void spine_skin(spine_t *p, unsigned skin);
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
API void spine_animate(spine_t *p, float delta);
API void spine_ui(spine_t *p);
API void ui_spine(spine_t *p);
// -----------------------------------------------------------------------------
// cubemaps
@ -4155,13 +4162,13 @@ API void trap_on_quit(int signal); // helper util
API void trap_on_abort(int signal); // helper util
API void trap_on_debug(int signal); // helper util
#define PANIC(...) PANIC(va(__VA_ARGS__), strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__) // die() ?
#define PANIC(...) PANIC(va(""__VA_ARGS__), __FILE__, __LINE__) // die() ?
API int (PANIC)(const char *error, const char *file, int line);
#define PRINTF(...) PRINTF(va(__VA_ARGS__), 1[#__VA_ARGS__] == '!' ? callstack(+48) : "", strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__, __FUNCTION__)
#define PRINTF(...) PRINTF(va(""__VA_ARGS__), 1[""#__VA_ARGS__] == '!' ? callstack(+48) : "", __FILE__, __LINE__, __FUNCTION__)
API int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
#define test(expr) test(strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__,__LINE__,#expr,!!(expr))
#define test(expr) test(__FILE__,__LINE__,#expr,!!(expr))
API int (test)(const char *file, int line, const char *expr, bool result);
#if ENABLE_AUTOTESTS
@ -4371,11 +4378,13 @@ API int ui_mat44(const char *label, float mat44[16]);
API int ui_double(const char *label, double *value);
API int ui_buffer(const char *label, char *buffer, int buflen);
API int ui_string(const char *label, char **string);
API int ui_color3(const char *label, float *color3); //[0..255]
API int ui_color3f(const char *label, float *color3); //[0..1]
API int ui_color4(const char *label, float *color4); //[0..255]
API int ui_color4f(const char *label, float *color4); //[0..1]
API int ui_color3(const char *label, unsigned *color); //[0..255]
API int ui_color3f(const char *label, float color[3]); //[0..1]
API int ui_color4(const char *label, unsigned *color); //[0..255]
API int ui_color4f(const char *label, float color[4]); //[0..1]
API int ui_unsigned(const char *label, unsigned *value);
API int ui_unsigned2(const char *label, unsigned *value);
API int ui_unsigned3(const char *label, unsigned *value);
API int ui_button(const char *label);
API int ui_button_transparent(const char *label);
API int ui_buttons(int buttons, /*labels*/...);
@ -4494,6 +4503,7 @@ enum WINDOW_FLAGS {
WINDOW_ASPECT = 0x100, // keep aspect
WINDOW_FIXED = 0x200, // disable resizing
WINDOW_TRANSPARENT = 0x400,
WINDOW_BORDERLESS = 0x800,
WINDOW_VSYNC = 0,
WINDOW_VSYNC_ADAPTIVE = 0x1000,

1348
tools/3rd_aseprite.h 100644

File diff suppressed because it is too large Load Diff

857
tools/3rd_atlasc.h 100644
View File

@ -0,0 +1,857 @@
// atlasc.c
// Copyright 2019 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/atlasc#license-bsd-2-clause
//
// sx_math.h
// Copyright 2018 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/sx#license-bsd-2-clause
#ifndef ATLASC_HEADER
#define ATLASC_HEADER
#define ATLASC_VERSION "1.2.3"
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#ifndef __cplusplus
#define ATLAS_CAST
#else
#define ATLAS_CAST(T) T
extern "C" {
#endif
typedef union vec2 { struct { float x, y; }; float f[2]; } vec2;
typedef union vec3 { struct { float x, y, z; }; float f[3]; } vec3;
typedef union vec2i { struct { int x, y; }; int n[2]; } vec2i;
typedef union recti { struct { int xmin, ymin, xmax, ymax; }; struct { vec2i vmin, vmax; }; int f[4]; } recti;
typedef struct atlas_flags {
int alpha_threshold;
float dist_threshold;
int max_width;
int max_height;
int border;
int pot;
int padding;
int mesh;
int max_verts_per_mesh;
float scale;
} atlas_flags;
typedef struct atlas_image {
uint8_t* pixels; // only supports 32bpp RGBA format
int width;
int height;
char *name;
} atlas_image;
typedef struct atlas_sprite {
uint8_t* src_image; // RGBA image buffer (32bpp)
vec2i src_size; // widthxheight
recti sprite_rect; // cropped rectangle relative to sprite's source image (pixels)
recti sheet_rect; // rectangle in final sheet (pixels)
char *name;
unsigned frame;
// sprite-mesh data (if flag is set. see atlas_flags)
uint16_t num_tris;
int num_points;
vec2i* pts;
vec2i* uvs;
uint16_t* tris;
} atlas_sprite;
typedef struct atlas_t {
atlas_sprite* sprites;
int num_sprites;
int* frames;
int num_frames;
atlas_image output;
} atlas_t;
// receives input files and common arguments. returns atlas_t
// you have to free the data after use with `atlas_free`
atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags);
// receives input image buffers and common arguments. returns atlas_t
// you have to free the data after use with `atlas_free`
atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags);
//
bool atlas_save(const char *outfile, const atlas_t* atlas, atlas_flags flags);
// frees atlas_t memory
void atlas_free(atlas_t* atlas);
// returns the last error string
const char* atlas_last_error();
#ifdef __cplusplus
}
#endif
#endif // ATLASC_HEADER
//
#ifdef ATLASC_IMPLEMENTATION
#include <math.h>
#include <assert.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
// Types/Primitives
#define vec2(x,y) (ATLAS_CAST(vec2) { (float)(x), (float)(y) })
#define vec3(x,y,z) (ATLAS_CAST(vec3) { (float)(x), (float)(y), (float)(z) })
#define vec2i(x,y) (ATLAS_CAST(vec2i) { (int)(x), (int)(y) })
#define recti(x,y,X,Y) (ATLAS_CAST(recti) { (int)(x), (int)(y), (int)(X), (int)(Y) })
#define minf(a,b) ((a) < (b) ? (a) : (b))
#define maxf(a,b) ((a) > (b) ? (a) : (b))
#define clampf(a,b,c) ( (a) < (b) ? (b) : (a) > (c) ? (c) : (a))
static int nearest_pow2(int n) { return --n, n |= n >> 1, n |= n >> 2, n |= n >> 4, n |= n >> 8, n |= n >> 16, ++n; } // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static float sx_abs(float _a) { union { float f; unsigned int ui; } u = { _a }; return u.ui &= 0x7FFFFFFF, u.f; }
static bool equalf(float _a, float _b, float _epsilon) { const float lhs = sx_abs(_a - _b), aa = sx_abs(_a), ab = sx_abs(_b), rhs = _epsilon * maxf(1.0f, maxf(aa, ab)); return lhs <= rhs; } // http://realtimecollisiondetection.net/blog/?t=89
static vec3 cross3(const vec3 _a, const vec3 _b) { return vec3(_a.y * _b.z - _a.z * _b.y, _a.z * _b.x - _a.x * _b.z, _a.x * _b.y - _a.y * _b.x); }
static float dot2(const vec2 _a, const vec2 _b) { return _a.x * _b.x + _a.y * _b.y; }
static float len2(const vec2 _a) { return sqrt(dot2(_a, _a)); }
static vec2 norm2(const vec2 _a) { const float len = len2(_a); /*assert(len > 0 && "Divide by zero");*/ return vec2(_a.x / (len + !len), _a.y / (len + !len)); }
static vec2 add2(const vec2 _a, const vec2 _b) { return vec2(_a.x + _b.x, _a.y + _b.y); }
static vec2 sub2(const vec2 _a, const vec2 _b) { return vec2(_a.x - _b.x, _a.y - _b.y); }
static vec2 scale2(const vec2 _a, float _b) { return vec2(_a.x * _b, _a.y * _b); }
static vec2i add2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x + _b.x, _a.y + _b.y); }
static vec2i sub2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x - _b.x, _a.y - _b.y); }
static vec2i min2i(const vec2i _a, const vec2i _b) { return vec2i(minf(_a.x, _b.x), minf(_a.y, _b.y)); }
static vec2i max2i(const vec2i _a, const vec2i _b) { return vec2i(maxf(_a.x, _b.x), maxf(_a.y, _b.y)); }
static recti rectiwh(int _x, int _y, int _w, int _h) { return recti(_x, _y, _x + _w, _y + _h); }
static recti recti_expand(const recti rc, const vec2i expand) { return recti(rc.xmin - expand.x, rc.ymin - expand.y, rc.xmax + expand.x, rc.ymax + expand.y); }
static void recti_add_point(recti* rc, const vec2i pt) { rc->vmin = min2i(rc->vmin, pt); rc->vmax = max2i(rc->vmax, pt); }
// ----------------------------------------------------------------------------
#ifndef ATLAS_REALLOC
#define ATLAS_REALLOC realloc
#endif
#ifndef ATLAS_MSIZE
#define ATLAS_MSIZE _msize
#endif
#ifndef ATLAS_CALLOC
#define ATLAS_CALLOC(n,m) memset(ATLAS_REALLOC(0, (n)*(m)), 0, (n)*(m))
#endif
#ifndef ATLAS_FREE
#define ATLAS_FREE(ptr) ((ptr) = ATLAS_REALLOC((ptr), 0))
#endif
#define align_mask(_value, _mask) (((_value) + (_mask)) & ((~0) & (~(_mask))))
static void panic_if(int fail) { if(fail) exit(-fprintf(stderr, "out of memory!\n")); }
static void path_unixpath(char *buf, unsigned buflen, const char *inpath) {
snprintf(buf, buflen, "%s", inpath);
while( strchr(buf, '\\') ) *strchr(buf, '\\') = '/';
}
static void path_basename(char *buf, unsigned buflen, const char *inpath) {
const char *a = strrchr(inpath, '\\');
const char *b = strrchr(inpath, '/');
snprintf(buf, buflen, "%s", a > b ? a+1 : b > a ? b+1 : inpath );
}
static bool path_isfile(const char* filepath) {
FILE *f = fopen(filepath, "rb");
return f ? fclose(f), 1 : 0;
}
static char g_error_str[512];
const char* atlas_last_error()
{
return g_error_str;
}
static void atlas__free_sprites(atlas_sprite* sprites, int num_sprites)
{
for (int i = 0; i < num_sprites; i++) {
if (sprites[i].src_image) {
stbi_image_free(sprites[i].src_image);
}
if (sprites[i].tris) {
ATLAS_FREE(sprites[i].tris);
}
if (sprites[i].pts) {
ATLAS_FREE(sprites[i].pts);
}
if (sprites[i].uvs) {
ATLAS_FREE(sprites[i].uvs);
}
if (sprites[i].name) {
ATLAS_FREE(sprites[i].name);
}
}
ATLAS_FREE(sprites);
}
static void atlas__blit(uint8_t* dst, int dst_x, int dst_y, int dst_pitch, const uint8_t* src,
int src_x, int src_y, int src_w, int src_h, int src_pitch, int bpp)
{
assert(dst);
assert(src);
const int pixel_sz = bpp / 8;
const uint8_t* src_ptr = src + src_y * src_pitch + src_x * pixel_sz;
uint8_t* dst_ptr = dst + dst_y * dst_pitch + dst_x * pixel_sz;
for (int y = src_y; y < (src_y + src_h); y++) {
memcpy(dst_ptr, src_ptr, src_w * pixel_sz);
src_ptr += src_pitch;
dst_ptr += dst_pitch;
}
}
static vec2 atlas__itof2(const s2o_point p)
{
return vec2((float)p.x, (float)p.y);
}
// modified version of:
// https://github.com/anael-seghezzi/Maratis-Tiny-C-library/blob/master/include/m_raster.h
static bool atlas__test_line(const uint8_t* buffer, int w, int h, s2o_point p0, s2o_point p1)
{
const uint8_t* data = buffer;
int x0 = p0.x;
int y0 = p0.y;
int x1 = p1.x;
int y1 = p1.y;
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
while (1) {
if (x0 > -1 && y0 > -1 && x0 < w && y0 < h) {
const uint8_t* pixel = data + (y0 * w + x0);
if (*pixel)
return true; // line intersects with image data
}
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
}
if (e2 <= dx) {
err += dx;
y0 += sy;
}
}
return false;
}
// returns true if 'pts' buffer is changed
static bool atlas__offset_pt(s2o_point* pts, int num_pts, int pt_idx, float amount, int w, int h)
{
s2o_point ipt = pts[pt_idx];
s2o_point _ipt = ipt;
vec2 pt = atlas__itof2(ipt);
vec2 prev_pt = (pt_idx > 0) ? atlas__itof2(pts[pt_idx - 1]) : atlas__itof2(pts[num_pts - 1]);
vec2 next_pt = (pt_idx + 1) < num_pts ? atlas__itof2(pts[pt_idx + 1]) : atlas__itof2(pts[0]);
vec2 edge1 = norm2(sub2(prev_pt, pt));
vec2 edge2 = norm2(sub2(next_pt, pt));
// calculate normal vector to move the point away from the polygon
vec2 n;
vec3 c = cross3(vec3(edge1.x, edge1.y, 0), vec3(edge2.x, edge2.y, 0));
if (equalf(c.z, 0.0f, 0.00001f)) {
n = scale2(vec2(-edge1.y, edge1.x), amount);
} else {
// c.z < 0 -> point intersecting convex edges
// c.z > 0 -> point intersecting concave edges
float k = c.z < 0.0f ? -1.0f : 1.0f;
n = scale2(norm2(add2(edge1, edge2)), k * amount);
}
pt = add2(pt, n);
ipt.x = (int)pt.x;
ipt.y = (int)pt.y;
ipt.x = clampf(ipt.x, 0, w);
ipt.y = clampf(ipt.y, 0, h);
pts[pt_idx] = ipt;
return (_ipt.x != ipt.x) || (_ipt.y != ipt.y);
}
static void atlas__fix_outline_pts(const uint8_t* thresholded, int tw, int th, s2o_point* pts,
int num_pts)
{
// NOTE: winding is assumed to be CW
const float offset_amount = 2.0f;
for (int i = 0; i < num_pts; i++) {
s2o_point pt = pts[i];
int next_i = (i + 1) < num_pts ? (i + 1) : 0;
// assert(!thresholded[pt.y * tw + pt.x]); // point shouldn't be inside threshold
s2o_point next_pt = pts[next_i];
while (atlas__test_line(thresholded, tw, th, pt, next_pt)) {
if (!atlas__offset_pt(pts, num_pts, i, offset_amount, tw, th))
break;
atlas__offset_pt(pts, num_pts, next_i, offset_amount, tw, th);
// refresh points for the new line intersection test
pt = pts[i];
next_pt = pts[next_i];
}
}
}
static void atlas__make_mesh(atlas_sprite* spr, const s2o_point* pts, int pt_count, int max_verts,
const uint8_t* thresholded, int width, int height)
{
s2o_point* temp_pts = ATLAS_CALLOC(pt_count,sizeof(s2o_point));
panic_if(!temp_pts);
memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count);
int num_verts = pt_count;
if (width > 1 && height > 1) {
const float delta = 0.5f;
const float threshold_start = 0.5f;
float threshold = threshold_start;
for(;;) {
s2o_distance_based_path_simplification(temp_pts, &num_verts, threshold);
if(num_verts <= max_verts) break;
memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count);
num_verts = pt_count;
threshold += delta;
}
// fix any collisions with the actual image // @r-lyeh: method below is buggy. will return dupe points
atlas__fix_outline_pts(thresholded, width, height, temp_pts, num_verts);
}
//< @r-lyeh: remove dupes
for (int i = 0; i < num_verts - 1; i++) {
for (int j = i + 1; j < num_verts; j++) {
if( temp_pts[i].x == temp_pts[j].x && temp_pts[i].y == temp_pts[j].y ) {
temp_pts[j].x = temp_pts[num_verts - 1].x;
temp_pts[j].y = temp_pts[num_verts - 1].y;
--num_verts;
--j;
}
}
}
//<
// triangulate
del_point2d_t* dpts = ATLAS_CALLOC(num_verts, sizeof(del_point2d_t));
panic_if(!dpts);
for (int i = 0; i < num_verts; i++) {
dpts[i].x = (double)temp_pts[i].x;
dpts[i].y = (double)temp_pts[i].y;
//printf("%d) %f,%f\n", i, dpts[i].x, dpts[i].y); //< @r-lyeh: debug dupe points
}
delaunay2d_t* polys = delaunay2d_from(dpts, num_verts);
assert(polys);
tri_delaunay2d_t* tris = tri_delaunay2d_from(polys);
assert(tris);
ATLAS_FREE(dpts);
delaunay2d_release(polys);
assert(tris->num_triangles < UINT16_MAX);
spr->tris = ATLAS_CALLOC(tris->num_triangles * 3,sizeof(uint16_t));
spr->pts = ATLAS_CALLOC(tris->num_points, sizeof(vec2i));
assert(spr->tris);
assert(spr->pts);
for (unsigned int i = 0; i < tris->num_triangles; i++) {
unsigned int index = i * 3;
spr->tris[index] = (uint16_t)tris->tris[index];
spr->tris[index + 1] = (uint16_t)tris->tris[index + 1];
spr->tris[index + 2] = (uint16_t)tris->tris[index + 2];
}
for (unsigned int i = 0; i < tris->num_points; i++) {
spr->pts[i] = vec2i((int)tris->points[i].x, (int)tris->points[i].y);
}
spr->num_tris = (uint16_t)tris->num_triangles;
spr->num_points = (int)tris->num_points;
tri_delaunay2d_release(tris);
ATLAS_FREE(temp_pts);
}
atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags)
{
assert(images);
array(int) frames = 0;
array(atlas_sprite) sprites = 0;
for (int i = 0; i < array_count(images); i++) {
// find is_cached
{
int found = 0, k = 0;
static array(uint64_t) cache = 0;
static array(uint64_t) index = 0;
uint64_t hash = hash_init;
hash = hash_bin(&images[i].width, sizeof(images[i].width), hash);
hash = hash_bin(&images[i].height, sizeof(images[i].height), hash);
hash = hash_bin((char*)images[i].pixels, images[i].width * images[i].height * 4, hash);
for (; k < array_count(cache); ++k)
if (cache[k] == hash) { found = 1; break; }
if (found) {
array_push(frames, index[k]);
continue;
} else {
array_push(cache, hash);
array_push(index, k);
array_push(frames, k);
}
//printf("%d) %llx\n", array_count(cache), hash);
}
atlas_sprite zero = {0};
atlas_sprite* spr = &zero;
if(images[i].name) spr->name = STRDUP(images[i].name);
spr->frame = i;
spr->src_size.x = images[i].width;
spr->src_size.y = images[i].height;
assert(images[i].width > 0 && images[i].height > 0);
assert(images[i].pixels);
uint8_t* pixels = images[i].pixels;
// rescale
if (!equalf(flags.scale, 1.0f, 0.0001f)) {
int target_w = (int)((float)spr->src_size.x * flags.scale);
int target_h = (int)((float)spr->src_size.y * flags.scale);
uint8_t* resized_pixels = ATLAS_CALLOC(1, 4 * target_w * target_h);
panic_if(!resized_pixels);
if (!stbir_resize_uint8(pixels, spr->src_size.x, spr->src_size.y, 4 * spr->src_size.x,
resized_pixels, target_w, target_h, 4 * target_w, 4)) {
snprintf(g_error_str, sizeof(g_error_str), "could not resize image: #%d", i + 1);
atlas__free_sprites(sprites, array_count(sprites));
return NULL;
}
stbi_image_free(pixels);
spr->src_size.x = target_w;
spr->src_size.y = target_h;
pixels = resized_pixels;
}
spr->src_image = pixels;
recti sprite_rect = {0};
int pt_count = 0;
s2o_point* pts = 0;
uint8_t* alpha = s2o_rgba_to_alpha(spr->src_image, spr->src_size.x, spr->src_size.y);
uint8_t* thresholded = s2o_alpha_to_thresholded(alpha, spr->src_size.x, spr->src_size.y, flags.alpha_threshold);
free(alpha);
if (flags.mesh && spr->src_size.x > 1 && spr->src_size.y > 1) {
uint8_t* dilate_thres = s2o_dilate_thresholded(thresholded, spr->src_size.x, spr->src_size.y);
uint8_t* outlined = s2o_thresholded_to_outlined(dilate_thres, spr->src_size.x, spr->src_size.y);
free(dilate_thres);
pts = s2o_extract_outline_path(outlined, spr->src_size.x, spr->src_size.y, &pt_count, NULL);
free(outlined);
//< @r-lyeh @fixme: many sprites will return extremely low num of points (like 8) even if the sprite is complex enough.
//< this will lead to produce here a nearly zero sprite_rect, then sheet_rect, then eventually an empty frame at end of pipeline.
// calculate cropped rectangle
sprite_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN);
for (int k = 0; k < pt_count; k++) {
recti_add_point(&sprite_rect, vec2i(pts[k].x, pts[k].y));
}
sprite_rect.xmax++;
sprite_rect.ymax++;
} else {
sprite_rect = recti(0, 0, spr->src_size.x, spr->src_size.y);
pt_count = 4;
pts = ATLAS_CALLOC(pt_count, sizeof(s2o_point));
pts[0] = (s2o_point) {0, 0};
pts[1] = (s2o_point) {spr->src_size.x, 0};
pts[2] = (s2o_point) {spr->src_size.x, spr->src_size.y};
pts[3] = (s2o_point) {0, spr->src_size.y};
}
// generate mesh if set in arguments
if (flags.mesh) {
atlas__make_mesh(spr, pts, pt_count, flags.max_verts_per_mesh, thresholded,
spr->src_size.x, spr->src_size.y);
}
ATLAS_FREE(pts);
free(thresholded);
spr->sprite_rect = sprite_rect;
array_push(sprites, *spr);
}
int num_sprites = array_count(sprites);
// pack sprites into a sheet
stbrp_context rp_ctx = {0};
int max_width = flags.max_width;
int max_height = flags.max_height;
int num_rp_nodes = max_width + max_height;
stbrp_rect* rp_rects = ATLAS_CALLOC(num_sprites, sizeof(stbrp_rect));
stbrp_node* rp_nodes = ATLAS_CALLOC(num_rp_nodes, sizeof(stbrp_node));
panic_if(!rp_rects || !rp_nodes);
for (int i = 0; i < num_sprites; i++) {
recti rc = sprites[i].sprite_rect;
int rc_resize = (flags.border + flags.padding) * 2;
rp_rects[i].w = (rc.xmax - rc.xmin) + rc_resize;
rp_rects[i].h = (rc.ymax - rc.ymin) + rc_resize;
}
stbrp_init_target(&rp_ctx, max_width, max_height, rp_nodes, num_rp_nodes);
recti final_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN);
if (stbrp_pack_rects(&rp_ctx, rp_rects, num_sprites)) {
for (int i = 0; i < num_sprites; i++) {
atlas_sprite* spr = &sprites[i];
recti sheet_rect = rectiwh(rp_rects[i].x, rp_rects[i].y, rp_rects[i].w, rp_rects[i].h);
// calculate the total size of output image
recti_add_point(&final_rect, sheet_rect.vmin);
recti_add_point(&final_rect, sheet_rect.vmax);
// shrink back rect and set the real sheet_rect for the sprite
spr->sheet_rect =
recti_expand(sheet_rect, vec2i(-flags.border, -flags.border));
}
}
int dst_w = final_rect.xmax - final_rect.xmin;
int dst_h = final_rect.ymax - final_rect.ymin;
// make output size divide by 4 by default
dst_w = align_mask(dst_w, 3);
dst_h = align_mask(dst_h, 3);
if (flags.pot) {
dst_w = nearest_pow2(dst_w);
dst_h = nearest_pow2(dst_h);
}
uint8_t* dst = ATLAS_CALLOC(1, dst_w * dst_h * 4);
panic_if(!dst);
// calculate UVs for sprite meshes
if (flags.mesh) {
for (int i = 0; i < num_sprites; i++) {
atlas_sprite* spr = &sprites[i];
// if sprite has mesh, calculate UVs for it
if (spr->pts && spr->num_points) {
const int padding = flags.padding;
vec2i offset = spr->sprite_rect.vmin;
vec2i sheet_pos =
vec2i(spr->sheet_rect.xmin + padding, spr->sheet_rect.ymin + padding);
vec2i* uvs = ATLAS_CALLOC(spr->num_points, sizeof(vec2i));
assert(uvs);
for (int pi = 0; pi < spr->num_points; pi++) {
vec2i pt = spr->pts[pi];
uvs[pi] = add2i(sub2i(pt, offset), sheet_pos);
}
spr->uvs = uvs;
} // generate uvs
}
}
for (int i = 0; i < num_sprites; i++) {
const atlas_sprite* spr = &sprites[i];
// calculate UVs for sprite-meshes
// remove padding and blit from src_image to dst
recti dstrc = recti_expand(spr->sheet_rect, vec2i(-flags.padding, -flags.padding));
recti srcrc = spr->sprite_rect;
atlas__blit(dst, dstrc.xmin, dstrc.ymin, dst_w * 4, spr->src_image, srcrc.xmin, srcrc.ymin,
srcrc.xmax - srcrc.xmin, srcrc.ymax - srcrc.ymin, spr->src_size.x * 4, 32);
}
atlas_t* atlas = ATLAS_CALLOC(1, sizeof(atlas_t));
panic_if(!atlas);
atlas->output.pixels = dst;
atlas->output.width = dst_w;
atlas->output.height = dst_h;
atlas->sprites = sprites;
atlas->num_sprites = num_sprites;
atlas->frames = frames;
atlas->num_frames = array_count(frames);
ATLAS_FREE(rp_nodes);
ATLAS_FREE(rp_rects);
return atlas;
}
static char *atlas_anims = 0;
static char *atlas_current_anim = 0;
atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags)
{
assert(files);
array(atlas_image) images = 0;
for (int i = 0; i < array_count(files); ++i) {
if (!path_isfile(files[i])) {
snprintf(g_error_str, sizeof(g_error_str), "input image not found: %s", files[i]);
goto err_cleanup;
}
int comp;
atlas_image img = {0};
img.pixels = stbi_load(files[i], &img.width, &img.height, &comp, 4);
#ifdef CUTE_ASEPRITE_H
if (!img.pixels) {
bool loaded = 0;
for( ase_t* ase = cute_aseprite_load_from_file(files[i], NULL); ase; cute_aseprite_free(ase), ase = 0, loaded = 1) {
ase_tag_t *parent = ase->tags + 0;
//< abc/def/ghi.aseprite -> ghi
if( atlas_current_anim ) *atlas_current_anim = '\0';
strcatf(&atlas_current_anim, files[i]);
path_basename(atlas_current_anim, strlen(atlas_current_anim), files[i]);
if( strrchr(atlas_current_anim, '.')) *strrchr(atlas_current_anim, '.') = '\0';
trimspace(atlas_current_anim);
//<
for( int f = 0; f < ase->frame_count; ++f) {
ase_frame_t *frame = ase->frames + f;
// find rect
int x = INT_MAX, y = INT_MAX, x2 = INT_MIN, y2 = INT_MIN;
for( int c = 0; c < frame->cel_count; ++c ) {
ase_cel_t *cel = frame->cels + c;
if( cel->layer->flags & ASE_LAYER_FLAGS_VISIBLE ) {
if( cel->x < x ) x = cel->x;
if( cel->h < y ) y = cel->y;
if( (cel->x + cel->w) > x2 ) x2 = cel->x + cel->w;
if( (cel->y + cel->h) > y2 ) y2 = cel->y + cel->h;
}
}
if (x2 <= 0 || y2 <= 0) { // submit empty frame
img.width = 1;
img.height = 1;
img.pixels = calloc(1, 1*1*4);
array_push(images, img);
continue;
}
int cx = x;
int cy = y;
int cw = x2-x;
int ch = y2-y;
int tn = 4;
int tw = ase->w;
// find clip
img.width = cw;
img.height = ch;
img.pixels = calloc(1, cw*ch*4); // @fixme: because of a stbi_image_free() within rescale section, this should be allocated with stbi allocator
for( unsigned y = 0; y < ch; ++y )
memcpy((char *)img.pixels + (0+(0+y)*cw)*tn, (char*)frame->pixels + (cx+(cy+y)*tw)*tn, cw*tn);
array_push(images, img);
}
static int count = 0;
if(!atlas_anims) strcatf(&atlas_anims, "[anims]\n");
for( int t = 0; t < ase->tag_count; ++t) {
ase_tag_t *tag = ase->tags + t;
// find full name
int range[2] = {tag->from_frame, tag->to_frame};
char name[256] = {0};
for( int tt = 0; tt < ase->tag_count; ++tt ) {
ase_tag_t *ttag = ase->tags + tt;
if( range[0] >= ttag->from_frame && range[1] <= ttag->to_frame )
strcat(name, "."), strcat(name, ttag->name);
}
trimspace(name);
char *sep = "";
strcatf(&atlas_anims, "[%d].name=%s.%s\n", count, atlas_current_anim, name+1);
strcatf(&atlas_anims, "[%d].frames=", count);
if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_BACKWARDS)
for( int from = tag->from_frame; from <= tag->to_frame; ++from ) {
strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ",";
}
sep = "";
if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_FORWARDS)
for( int from = tag->from_frame; from <= tag->to_frame; ++from ) {
strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ",";
}
strcatf(&atlas_anims,"\n");
++count;
}
}
if( loaded ) continue;
}
#endif
if (!img.pixels) {
continue; //< @r-lyeh: keep going
snprintf(g_error_str, sizeof(g_error_str), "invalid image format: %s", files[i]);
goto err_cleanup;
}
if( !img.name ) img.name = STRDUP(files[i]);
array_push(images, img);
}
atlas_t* atlas = atlas_loadimages(images, flags);
return atlas;
err_cleanup:
for (int i = 0; i < array_count(images); i++) {
if (images[i].pixels) {
stbi_image_free(images[i].pixels);
}
if (images[i].name) {
ATLAS_FREE(images[i].name);
}
}
array_free(images);
return NULL;
}
void atlas_free(atlas_t* atlas)
{
assert(atlas);
if (atlas->sprites)
atlas__free_sprites(atlas->sprites, atlas->num_sprites);
if (atlas->frames)
ATLAS_FREE(atlas->frames);
if (atlas->output.pixels)
ATLAS_FREE(atlas->output.pixels);
ATLAS_FREE(atlas);
}
// custom write function
typedef struct {
int offset;
void *buffer;
} stbi_mem_context;
static void stbi_write_mem(void *context, void *data, int size) {
stbi_mem_context *ctx = (stbi_mem_context*)context;
memcpy( ctx->buffer, data, size );
ctx->offset += size;
}
bool atlas_save(const char *outfile, const atlas_t *atlas, atlas_flags flags)
{
assert(outfile);
const bool is_file = strcmp(outfile, "stdout");
const atlas_sprite* sprites = atlas->sprites;
const int* frames = atlas->frames;
const int num_frames = atlas->num_frames;
const int num_sprites = atlas->num_sprites;
const uint8_t* dst = atlas->output.pixels;
const int dst_w = atlas->output.width;
const int dst_h = atlas->output.height;
char image_filepath[256];
char image_filename[256];
snprintf(image_filepath, sizeof(image_filepath), "%s.png", outfile);
path_basename(image_filename, sizeof(image_filename), image_filepath);
stbi_write_png_compression_level = 5; // 8
// write texture, if needed
if( is_file ) {
if (!stbi_write_png(image_filepath, dst_w, dst_h, 4, dst, dst_w * 4)) {
fprintf(stderr, "could not write image file `%s`\n", image_filepath);
return false;
}
}
// write atlas description into .ini file
FILE *writer = is_file ? fopen(outfile, "wt") : stdout;
if (!writer) {
fprintf(stderr, "could not write ini file `%s`\n", outfile);
return false;
}
fprintf(writer, "[atlas]\n");
if (is_file) {
fprintf(writer, "file=%s\n", image_filepath);
} else {
stbi_mem_context ctx = {0, ATLAS_CALLOC(1, dst_w*dst_h*4+256) };
int result = stbi_write_png_to_func(stbi_write_mem, &ctx, dst_w, dst_h, 4, dst, dst_w*4);
char *b64 = base64_encode(ctx.buffer, ctx.offset);
fprintf(writer, "bitmap=%s\n", b64); // %d:%s\n", ctx.offset, b64);
ATLAS_FREE(ctx.buffer);
free(b64);
}
fprintf(writer, "size=%d,%d\n", dst_w, dst_h);
fprintf(writer, "border=%d,%d\n", flags.border, flags.border);
fprintf(writer, "padding=%d,%d\n", flags.padding, flags.padding);
for( int i = 0; i < num_frames; i++ ) {
const atlas_sprite* spr = sprites + frames[i];
char name[256];
path_unixpath(name, sizeof(name), spr->name ? spr->name : "");
if(name[0])
fprintf(writer, "[%d].name=%s\n", i, name);
fprintf(writer, "[%d].frame=%u\n", i, spr->frame);
//fprintf(writer, "[%d].size=%d,%d\n", i, spr->src_size.n[0], spr->src_size.n[1]);
//fprintf(writer, "[%d].rect=%u,%u,%u,%u\n", i, spr->sprite_rect.f[0], spr->sprite_rect.f[1], spr->sprite_rect.f[2], spr->sprite_rect.f[3]);
fprintf(writer, "[%d].sheet=%u,%u,%u,%u\n", i, spr->sheet_rect.f[0], spr->sheet_rect.f[1], spr->sheet_rect.f[2], spr->sheet_rect.f[3]);
if( spr->num_tris ) {
fprintf(writer, "[%d].indices=", i); // %d:", i, (int)spr->num_tris * 3);
for( int j = 0, jend = (int)spr->num_tris * 3; j < jend; ++j )
fprintf(writer, "%u%s", spr->tris[j], j < (jend-1) ? "," : "\n");
fprintf(writer, "[%d].coords=", i); // %d:", i, spr->num_points*2);
for( int j = 0, jend = spr->num_points; j < jend; j++ )
fprintf(writer, "%.f,%.f%s", (double)spr->pts[j].x, (double)spr->pts[j].y, j < (jend-1) ? ",":"\n" );
fprintf(writer, "[%d].uvs=", i); // %d:", i, spr->num_points*2);
for( int j = 0, jend = spr->num_points; j < jend; j++ )
fprintf(writer, "%.f,%.f%s", (double)spr->uvs[j].x, (double)spr->uvs[j].y, j < (jend-1) ? ",":"\n" );
}
}
if( atlas_anims ) fprintf(writer, "%s\n", atlas_anims);
if(writer != stdout) fclose(writer);
return true;
}
#endif // ATLASC_IMPLEMENTATION

224
tools/ase2ini.c 100644
View File

@ -0,0 +1,224 @@
// ase2spr, based on atlas code by Sepehr Taghdisian (BSD2 licensed)
// - rlyeh, public domain.
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <strings.h>
#define strcmpi strcasecmp
#else
#define strcmpi stricmp
#endif
#define ATLAS_REALLOC vrealloc
#define ATLAS_MSIZE vlen
void* vrealloc( void* p, size_t sz ) {
if( !sz ) {
if( p ) {
size_t *ret = (size_t*)p - 2;
ret[0] = 0;
ret[1] = 0;
realloc( ret, 0 );
}
return 0;
} else {
size_t *ret;
if( !p ) {
ret = (size_t*)realloc( 0, sizeof(size_t) * 2 + sz );
ret[0] = sz;
ret[1] = 0;
} else {
ret = (size_t*)p - 2;
size_t osz = ret[0];
size_t ocp = ret[1];
if( sz <= (osz + ocp) ) {
ret[0] = sz;
ret[1] = ocp - (sz - osz);
} else {
ret = (size_t*)realloc( ret, sizeof(size_t) * 2 + sz * 1.75 );
ret[0] = sz;
ret[1] = (size_t)(sz * 1.75) - sz;
}
}
return &ret[2];
}
}
size_t vlen( void* p ) {
return p ? 0[ (size_t*)p - 2 ] : 0;
}
char *STRDUP(const char *s) { size_t n = strlen(s)+1; return ((char*)memcpy(ATLAS_REALLOC(0,n), s, n)); } ///-
static unsigned array_c;
#define array(t) t*
#define array_push(arr, v) ( array_c = array_count(arr), array_c[(arr) = vrealloc((arr), (array_c + 1) * sizeof(0[arr]))] = (v) )
#define array_pop(arr) ( (arr) ? (arr) = vrealloc((arr), (array_count(arr)-1) * sizeof(0[arr])) : (0) )
#define array_count(arr) (int)( (arr) ? vlen(arr) / sizeof(0[arr]) : sizeof(0[arr]) - sizeof(0[arr]) )
#define array_free(arr) ( (arr) ? (vrealloc(arr, 0), 1) : 0 )
const uint64_t hash_init = 14695981039346656037ULL;
uint64_t hash_bin(const void* ptr, unsigned len, uint64_t hash ) {
for( unsigned char *str = (unsigned char *)ptr; len--; )
hash = ( (unsigned char)*str++ ^ hash ) * 0x100000001b3ULL;
return hash;
}
char *trimspace(char *str) {
for( char *s = str; *s; ++s )
if(*s <= 32) memmove(s, s+1, strlen(s));
return str;
}
char* strcatf(char **src_, const char *fmt, ...) {
static char buf[1024] = {0};
va_list va;
va_start(va, fmt);
int buflen = vsnprintf( buf, sizeof(buf), fmt, va );
va_end(va);
char *src = src_ ? *src_ : 0;
int srclen = (src ? strlen(src) : 0);
src = (char*)vrealloc(src, srclen + buflen + 1 );
memcpy(src + srclen, buf, buflen + 1 );
if(src_) *src_ = src;
return src;
}
#ifdef __TINYC__
#define STBI_NO_SIMD
#endif
#define STB_IMAGE_IMPLEMENTATION
#include "3rd_stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "3rd_stb_image_write.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "3rd_stb_image_resize.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "3rd_stb_rect_pack.h"
#define S2O_IMPLEMENTATION
#include "3rd_sproutline.h"
#define DELAUNAY_C
#include "3rd_delaunay.h"
#define CUTE_ASEPRITE_IMPLEMENTATION
#include "3rd_aseprite.h"
#define DIR_C
#include "3rd_archive.h"
#define BASE64_C
#include "3rd_base64.h"
#define ATLASC_IMPLEMENTATION
#include "3rd_atlasc.h"
int main(int argc, char* argv[]) {
const char *help =
" [flags] -o outfile [folder or file...]\n\n"
"-h Show help\n"
"-V Show version\n"
"-o Output file (defaults: stdout)\n"
"-W Maximum output image width (default:1024)\n"
"-H Maximum output image height (default:1024)\n"
"-B Border size for each sprite (default:2)\n"
"-2 Make output image size power-of-two\n"
"-P Set padding for each sprite (default:1)\n"
"-m Make sprite meshes\n"
"-M Set maximum vertices for each generated sprite mesh (default:25)\n"
"-A Alpha threshold for cropping [1..255] (default:20)\n"
"-s Set scale for individual images (default:1.0)\n\n"
;
atlas_flags flags = {
.alpha_threshold = 20,
.max_width = 2048,
.max_height = 2048,
.border = 2,
.padding = 1,
.max_verts_per_mesh = 25,
.scale = 1.0
};
array(char*) files = 0;
char *outfile = 0;
for( int i = 1; i < argc; ++i) {
const char *arg = argv[i];
if( arg[0] != '-' ) {
assert(array_count(files) == 0);
if( path_isfile(arg) )
array_push(files, STRDUP(arg));
else
for( dir *d = dir_open(arg, "r"); d; dir_close(d), d = 0) {
for( int i = 0, end = dir_count(d); i < end; ++i ) {
if( path_isfile(dir_name(d, i)))
array_push(files, STRDUP(dir_name(d, i)));
}
}
assert(array_count(files) == 1);
}
else
switch( arg[1] ) {
case 'V': exit(-printf("%s\nUsing atlasc v" ATLASC_VERSION "\n", argv[0]));
case '2': flags.pot = 1; break;
case 'm': flags.mesh = 1; break;
case '?':
case 'h': exit(-printf("%s%s", argv[0], help));
case 'o': outfile = argv[++i]; break;
case 'A': flags.alpha_threshold = atoi(argv[++i]); break;
case 'W': flags.max_width = atoi(argv[++i]); break;
case 'H': flags.max_height = atoi(argv[++i]); break;
case 'B': flags.border = atoi(argv[++i]); break;
case 'P': flags.padding = atoi(argv[++i]); break;
case 'M': flags.max_verts_per_mesh = atoi(argv[++i]); break;
case 's': flags.scale = atof(argv[++i]); break;
default: exit(-printf("Unknown argument: %s\n", arg));
}
}
if( flags.scale < 0.0001f ) {
printf("%s%s", argv[0], help);
return(-puts("invalid `scale` parameter"));
}
if( !files ) {
printf("%s%s", argv[0], help);
return(-puts("no input file(s)"));
}
if( !outfile ) outfile = "stdout";
char *error = g_error_str;
atlas_t* atlas = atlas_loadfiles(files, flags);
if ( atlas ) {
bool r = atlas_save(outfile, atlas, flags);
if( r ) {
// fprintf(stderr, "Written: %d->%d\n", flags.num_files, !!r);
error = 0;
}
atlas_free(atlas);
}
for( int i = 0; i < array_count(files); ++i)
free(files[i]);
array_free(files);
return error ? fprintf(stderr, "%s\n", error), -1 : 0;
}
// cl ase2ini.c -I ..\engine\split /DNDEBUG /O2 /Ox /MT
// tcc ase2ini.c -I ..\engine\split -DNDEBUG
// cc ase2ini.c -I ../engine/split -lm -O3 -oase2ini.linux

BIN
tools/ase2ini.exe 100644

Binary file not shown.

View File

@ -1,7 +1,6 @@
#define V4K_IMPLEMENTATION
#define COOK_ON_DEMAND 0
#define COOK_FROM_TERMINAL 1
#include "joint/v4k.h"
#include "v4k.c"
int main(int argc, const char **argv) {
double timer = time_ss();
@ -34,3 +33,4 @@ int main(int argc, const char **argv) {
// cl cook.c -I..\engine /openmp /Os /Ox /O2 /Oy /MT /DNDEBUG /GL /GF /Gw /arch:AVX2 /link /OPT:ICF /LTCG
// cc -ObjC cook.c -I../engine -o cook.osx -framework Cocoa -framework IOKit -framework audiotoolbox -O3
// cc cook.c -I../engine -o cook.linux -lm -lpthread -ldl -lX11 -O3
// del cook.o & del cook.obj & del cook.lib & del cook.exp

Binary file not shown.

View File

@ -25,9 +25,9 @@ ART=../demos/art/,../engine/art/,../tools/editor/art/ ; comma-separated folder(
; also, once a symbol is found, it is replaced by its value always.
; some predefined symbols: INPUT (input filename), OUTPUT (output filename), PRETTY (clean input filename), PROGRESS (cook progress).
@windows `echo Cooking PROGRESS% PRETTY...`
@linux `echo "Cooking PROGRESS% PRETTY..."`
@osx `echo "Cooking PROGRESS% PRETTY..."`
;@windows `echo Cooking PROGRESS% PRETTY...`
;@linux `echo "Cooking PROGRESS% PRETTY..."`
;@osx `echo "Cooking PROGRESS% PRETTY..."`
; ------------------------------------------------------------------------------
; groups below are collection of files that we want to cook, and then package.
@ -49,6 +49,7 @@ shader=hlsl,fx,dxil,dxbc,glsl,vert,frag,geom,tese,tesc,comp,vs,fs,gs,ts,cs,spirv
script=lua,tl
video=mp4,ogv,avi,mkv,wmv,mpg,mpeg
tiled=tmx,tsx
atlas=ase,aseprite
;excel=xlsx
; ------------------------------------------------------------------------------
@ -60,10 +61,10 @@ tiled=tmx,tsx
[cook audio-module]
TOOLS/mod2wav.EXE INPUT OUTPUT -> wav
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -f ogg -b:a 192k OUTPUT -> ogg ; -stats
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -threads 1 -f ogg -b:a 192k OUTPUT -> ogg ; -stats
[cook flac]
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -f ogg -b:a 384k OUTPUT -> ogg ; -stats
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -threads 1 -f ogg -b:a 384k OUTPUT -> ogg ; -stats
; cook midis as wavs here
@ -85,7 +86,7 @@ TOOLS/sfxr2wav.EXE INPUT OUTPUT -> wav
; note that all the recently generated wav files from previous steps are included in here as well (like those from the flac->wav recipe above).
[cook audio && !mp3 && !ogg]
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -f wav -acodec adpcm_ms OUTPUT -> wav ; -stats
TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -threads 1 -f wav -acodec adpcm_ms OUTPUT -> wav ; -stats
; ------------------------------------------------------------------------------
; cook all videos that are not mpeg, into mpeg.
@ -97,13 +98,14 @@ TOOLS/ffmpeg.EXE -hide_banner -nostdin -loglevel fatal -y -i INPUT -f wav -acode
FLAGS1+=-hide_banner -nostdin -loglevel fatal -y ; -stats
FLAGS2+=-qscale:v 4 -y -c:v mpeg1video -c:a mp2 -ac 1 -b:a 128k -ar 44100 -format mpeg
;FLAGS_EXTRA_QUALITY=-vf scale=iw*2:ih*2
TOOLS/ffmpeg.EXE FLAGS1 -i INPUT FLAGS2 OUTPUT ==0 -> mpg
TOOLS/ffmpeg.EXE FLAGS1 -i INPUT -threads 1 FLAGS2 OUTPUT ==0 -> mpg
; ------------------------------------------------------------------------------
; let's cook all images into ktx
[cook image && !png && !jpg && !hdr]
TOOLS/cuttlefish.EXE -q -i INPUT -o OUTPUT -f R8G8B8A8 -> png
;TOOLS/cuttlefish.EXE -q -i INPUT -o OUTPUT -f R8G8B8A8 -> ktx
TOOLS/PVRTexToolCLI.EXE -noout -m -i INPUT -o OUTPUT -> png
[cook texture && tga]
TOOLS/cuttlefish.EXE -q -m -i INPUT -o OUTPUT -f BC1_RGB -> ktx
@ -190,6 +192,11 @@ TOOLS/ass2iqe.EXE FLAGS -L -o OUTPUT INPUT 2> NUL -> animlist.txt
;[cook excel]
;TOOLS/xlsx2ini.EXE INPUT OUTPUT -> ini
; ------------------------------------------------------------------------------
; cook sprites
[cook atlas]
TOOLS/ase2ini.EXE "INPUT" > OUTPUT
; ------------------------------------------------------------------------------
; assets that need to be compressed at end of whole pipeline process are specified here.
; by default, no assets are compressed unless explictly listed below.
@ -205,4 +212,4 @@ TOOLS/ass2iqe.EXE FLAGS -L -o OUTPUT INPUT 2> NUL -> animlist.txt
[compress]
0|ULZ=texture,image,model,audio,font,text,shader,script
0=video,flac,ogg,wav,mp1,mp3,jpg,png
0=video,flac,ogg,wav,mp1,mp3,jpg,png,atlas,tiled

BIN
tools/cook.lib 100644

Binary file not shown.

View File

@ -1122,6 +1122,7 @@ void lt_init(lua_State *L, void *handle, const char *pathdata, int argc, char **
" SCALE = tonumber(os.getenv(\"LITE_SCALE\")) or SCALE\n"
" PATHSEP = package.config:sub(1, 1)\n"
" EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n"
" USERDIR = EXEDIR .. 'data/user/'\n"
" package.path = EXEDIR .. '/data/?.lua;' .. package.path\n"
" package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n"
" core = require('core')\n"

View File

@ -88,14 +88,14 @@ void *lt_load_file(const char *filename, int *size) {
}
const char* lt_button_name(int button) {
if(button == GLFW_MOUSE_BUTTON_1) return "left";
if(button == GLFW_MOUSE_BUTTON_2) return "middle";
if(button == GLFW_MOUSE_BUTTON_3) return "right";
if(button == GLFW_MOUSE_BUTTON_LEFT) return "left";
if(button == GLFW_MOUSE_BUTTON_RIGHT) return "right";
if(button == GLFW_MOUSE_BUTTON_MIDDLE) return "middle";
return "?";
}
char* lt_key_name(char *dst, int key, int vk, int mods) {
// @todo: ALTGR -> left ctrl + right alt
// @todo: "altgr" -> left ctrl + right alt
if( key == GLFW_KEY_UP ) return "up";
if( key == GLFW_KEY_DOWN ) return "down";

View File

@ -24,32 +24,18 @@ local function get_indent_string()
end
local function insert_at_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc():get_selection(true)
for line = line1, line2 do
local line_text = doc().lines[line]
if (not skip_empty or line_text:find("%S")) then
doc():insert(line, 1, text)
local function doc_multiline_selections(sort)
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
return function()
idx, line1, col1, line2, col2 = iter(state, idx)
if idx and line2 > line1 and col2 == 1 then
line2 = line2 - 1
col2 = #doc().lines[line2]
end
return idx, line1, col1, line2, col2
end
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
end
local function remove_from_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc():get_selection(true)
for line = line1, line2 do
local line_text = doc().lines[line]
if line_text:sub(1, #text) == text
and (not skip_empty or line_text:find("%S"))
then
doc():remove(line, 1, line, #text + 1)
end
end
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
end
local function append_line_if_last_line(line)
if line >= #doc().lines then
doc():insert(line, math.huge, "\n")
@ -62,6 +48,32 @@ local function save(filename)
core.log("Saved \"%s\"", doc().filename)
end
local function cut_or_copy(delete)
local full_text = ""
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 ~= line2 or col1 ~= col2 then
local text = doc():get_text(line1, col1, line2, col2)
if delete then
doc():delete_to_cursor(idx, 0)
end
full_text = full_text == "" and text or (full_text .. "\n" .. text)
doc().cursor_clipboard[idx] = text
else
doc().cursor_clipboard[idx] = ""
end
end
system.set_clipboard(full_text)
end
local function split_cursor(direction)
local new_cursors = {}
for _, line1, col1 in doc():get_selections() do
if line1 > 1 and line1 < #doc().lines then
table.insert(new_cursors, { line1 + direction, col1 })
end
end
for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end
end
local commands = {
["doc:undo"] = function()
@ -73,92 +85,66 @@ local commands = {
end,
["doc:cut"] = function()
if doc():has_selection() then
local text = doc():get_text(doc():get_selection())
system.set_clipboard(text)
doc():delete_to(0)
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
else
local line = doc():get_selection()
local text = doc().lines[line]
system.set_clipboard(text)
core.line_in_clipboard = text
if line < #doc().lines then
doc():remove(line, 1, line + 1, 1)
else
doc():remove(line - 1, math.huge, line, math.huge)
end
end
--<
cut_or_copy(true)
end,
["doc:copy"] = function()
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
local line1, col1, line2, col2, text = doc():get_selection()
if doc():has_selection() then
text = doc():get_text(line1, col1, line2, col2)
else
text = doc().lines[line1]
core.line_in_clipboard = text
end
--<
system.set_clipboard(text)
cut_or_copy(false)
end,
["doc:paste"] = function()
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
local clipboard = system.get_clipboard():gsub("\r", "")
if core.line_in_clipboard == clipboard then
local line, col = doc():get_selection()
doc():insert(line, 1, clipboard)
doc():set_selection(line+1, col)
else
doc():text_input(clipboard)
for idx, line1, col1, line2, col2 in doc():get_selections() do
local value = doc().cursor_clipboard[idx] or system.get_clipboard()
doc():text_input(value:gsub("\r", ""), idx)
end
--<
end,
["doc:newline"] = function()
local line, col = doc():get_selection()
for idx, line, col in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
if col <= #indent then
indent = indent:sub(#indent + 2 - col)
end
doc():text_input("\n" .. indent)
doc():text_input("\n" .. indent, idx)
end
end,
["doc:newline-below"] = function()
local line = doc():get_selection()
for idx, line in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, math.huge, "\n" .. indent)
doc():set_selection(line + 1, math.huge)
doc():set_selections(idx, line + 1, math.huge)
end
end,
["doc:newline-above"] = function()
local line = doc():get_selection()
for idx, line in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, 1, indent .. "\n")
doc():set_selection(line, math.huge)
doc():set_selections(idx, line, math.huge)
end
end,
["doc:delete"] = function()
local line, col = doc():get_selection()
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
doc():remove(line, col, line, math.huge)
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
doc():remove(line1, col1, line1, math.huge)
end
doc():delete_to_cursor(idx, translate.next_char)
end
doc():delete_to(translate.next_char)
end,
["doc:backspace"] = function()
local line, col = doc():get_selection()
if not doc():has_selection() then
local text = doc():get_text(line, 1, line, col)
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 == line2 and col1 == col2 then
local text = doc():get_text(line1, 1, line1, col1)
if #text >= config.indent_size and text:find("^ *$") then
doc():delete_to(0, -config.indent_size)
doc():delete_to_cursor(idx, 0, -config.indent_size)
return
end
end
doc():delete_to(translate.previous_char)
doc():delete_to_cursor(idx, translate.previous_char)
end
end,
["doc:select-all"] = function()
@ -170,101 +156,135 @@ local commands = {
doc():set_selection(line, col)
end,
["doc:indent"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
if l1 then
doc():set_selections(idx, l1, c1, l2, c2)
end
end
end,
["doc:select-lines"] = function()
local line1, _, line2, _, swap = doc():get_selection(true)
for idx, line1, _, line2 in doc():get_selections(true) do
append_line_if_last_line(line2)
doc():set_selection(line1, 1, line2 + 1, 1, swap)
doc():set_selections(idx, line1, 1, line2 + 1, 1, swap)
end
end,
["doc:select-word"] = function()
local line1, col1 = doc():get_selection(true)
for idx, line1, col1 in doc():get_selections(true) do
local line1, col1 = translate.start_of_word(doc(), line1, col1)
local line2, col2 = translate.end_of_word(doc(), line1, col1)
doc():set_selection(line2, col2, line1, col1)
doc():set_selections(idx, line2, col2, line1, col1)
end
end,
["doc:join-lines"] = function()
local line1, _, line2 = doc():get_selection(true)
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 == line2 then line2 = line2 + 1 end
local text = doc():get_text(line1, 1, line2, math.huge)
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
text = text:gsub("\n[\t ]*", " ")
--<
text = text:gsub("(.-)\n[\t ]*", function(x)
return x:find("^%s*$") and x or x .. " "
end)
doc():insert(line1, 1, text)
doc():remove(line1, #text + 1, line2, math.huge)
if doc():has_selection() then
doc():set_selection(line1, math.huge)
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line1, math.huge)
end
end
end,
["doc:indent"] = function()
local text = get_indent_string()
if doc():has_selection() then
insert_at_start_of_selected_lines(text)
else
doc():text_input(text)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
if l1 then
doc():set_selections(idx, l1, c1, l2, c2)
end
end
end,
["doc:unindent"] = function()
local text = get_indent_string()
remove_from_start_of_selected_lines(text)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2)
if l1 then
doc():set_selections(idx, l1, c1, l2, c2)
end
end
end,
["doc:duplicate-lines"] = function()
local line1, col1, line2, col2, swap = doc():get_selection(true)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
local text = doc():get_text(line1, 1, line2 + 1, 1)
doc():insert(line2 + 1, 1, text)
local n = line2 - line1 + 1
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
doc():set_selections(idx, line1 + n, col1, line2 + n, col2, swap)
end
end,
["doc:delete-lines"] = function()
local line1, col1, line2 = doc():get_selection(true)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
doc():remove(line1, 1, line2 + 1, 1)
doc():set_selection(line1, col1)
doc():set_selections(idx, line1, col1)
end
end,
["doc:move-lines-up"] = function()
local line1, col1, line2, col2, swap = doc():get_selection(true)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
if line1 > 1 then
local text = doc().lines[line1 - 1]
doc():insert(line2 + 1, 1, text)
doc():remove(line1 - 1, 1, line1, 1)
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
end
end
end,
["doc:move-lines-down"] = function()
local line1, col1, line2, col2, swap = doc():get_selection(true)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2 + 1)
if line2 < #doc().lines then
local text = doc().lines[line2 + 1]
doc():remove(line2 + 1, 1, line2 + 2, 1)
doc():insert(line1, 1, text)
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
end
end
end,
["doc:toggle-line-comments"] = function()
local comment = doc().syntax.comment
if not comment then return end
local indentation = get_indent_string()
local comment_text = comment .. " "
local line1, _, line2 = doc():get_selection(true)
for idx, line1, _, line2 in doc_multiline_selections(true) do
local uncomment = true
local start_offset = math.huge
for line = line1, line2 do
local text = doc().lines[line]
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
local s = text:find("%S")
local cs, ce = text:find(comment_text, s, true)
if s and cs ~= s then
uncomment = false
start_offset = math.min(start_offset, s)
end
end
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if uncomment then
remove_from_start_of_selected_lines(comment_text, true)
else
insert_at_start_of_selected_lines(comment_text, true)
local cs, ce = text:find(comment_text, s, true)
if ce then
doc():remove(line, cs, line, ce + 1)
end
elseif s then
doc():insert(line, start_offset, comment_text)
end
end
end
end,
@ -343,6 +363,17 @@ local commands = {
end
end, common.path_suggest)
end,
["doc:create-cursor-previous-line"] = function()
split_cursor(-1)
doc():merge_cursors()
end,
["doc:create-cursor-next-line"] = function()
split_cursor(1)
doc():merge_cursors()
end
}
@ -372,21 +403,21 @@ for name, fn in pairs(translations) do
end
commands["doc:move-to-previous-char"] = function()
if doc():has_selection() then
local line, col = doc():get_selection(true)
doc():set_selection(line, col)
else
doc():move_to(translate.previous_char)
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line1, col1)
end
end
doc():move_to(translate.previous_char)
end
commands["doc:move-to-next-char"] = function()
if doc():has_selection() then
local _, _, line, col = doc():get_selection(true)
doc():set_selection(line, col)
else
doc():move_to(translate.next_char)
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line2, col2)
end
end
doc():move_to(translate.next_char)
end
command.add("core.docview", commands)

View File

@ -90,13 +90,84 @@ local function has_selection()
and core.active_view.doc:has_selection()
end
command.add(has_selection, {
["find-replace:select-next"] = function()
local function has_unique_selection()
if not doc() then return false end
local text = nil
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
if line1 == line2 and col1 == col2 then return false end
local selection = doc():get_text(line1, col1, line2, col2)
if text ~= nil and text ~= selection then return false end
text = selection
end
return text ~= nil
end
local function is_in_selection(line, col, l1, c1, l2, c2)
if line < l1 or line > l2 then return false end
if line == l1 and col <= c1 then return false end
if line == l2 and col > c2 then return false end
return true
end
local function is_in_any_selection(line, col)
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
end
return false
end
local function select_add_next(all)
local il1, ic1 = doc():get_selection(true)
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
local text = doc():get_text(l1, c1, l2, c2)
repeat
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l1 == il1 and c1 == ic1 then break end
if l2 and (all or not is_in_any_selection(l2, c2)) then
doc():add_selection(l2, c2, l1, c1)
if not all then
core.active_view:scroll_to_make_visible(l2, c2)
return
end
end
until not all or not l2
if all then break end
end
end
local function select_next(reverse)
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l2 then doc():set_selection(l2, c2, l1, c1) end
if reverse then
l1, c1, l2, c2 = search.find(doc(), l1, c1, text, { wrap = true, reverse = true })
else
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
end
if l2 then doc():set_selection(l2, c2, l1, c1) end
end
---@param in_selection? boolean whether to replace in the selections only, or in the whole file.
local function find_replace(in_selection)
local l1, c1, l2, c2 = doc():get_selection()
local selected_text = ""
if not in_selection then
selected_text = doc():get_text(l1, c1, l2, c2)
doc():set_selection(l2, c2, l2, c2)
end
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
if not find_regex then
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
end
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
return result, matches
end)
end
command.add(has_unique_selection, {
["find-replace:select-next"] = select_next,
["find-replace:select-previous"] = function() select_next(true) end,
["find-replace:select-add-next"] = select_add_next,
["find-replace:select-add-all"] = function() select_add_next(true) end
})
command.add("core.docview", {

View File

@ -1,5 +1,9 @@
local common = {}
function common.copy_position_and_size(dst, src)
dst.position.x, dst.position.y = src.position.x, src.position.y
dst.size.x, dst.size.y = src.size.x, src.size.y
end
function common.is_utf8_cont(char)
local byte = char:byte()
@ -29,6 +33,13 @@ function common.round(n)
end
function common.find_index(tbl, prop)
for i, o in ipairs(tbl) do
if o[prop] then return i end
end
end
function common.lerp(a, b, t)
if type(a) ~= "table" then
return a + (b - a) * t
@ -61,6 +72,26 @@ function common.color(str)
end
function common.splice(t, at, remove, insert)
insert = insert or {}
local offset = #insert - remove
local old_len = #t
if offset < 0 then
for i = at - offset, old_len - offset do
t[i + offset] = t[i]
end
elseif offset > 0 then
for i = old_len, at, -1 do
t[i + offset] = t[i]
end
end
for i, item in ipairs(insert) do
t[at + i - 1] = item
end
end
local function compare_score(a, b)
return a.score > b.score
end
@ -144,4 +175,17 @@ function common.bench(name, fn, ...)
end
function common.serialize(val)
if type(val) == "string" then
return string.format("%q", val)
elseif type(val) == "table" then
local t = {}
for k, v in pairs(val) do
table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v))
end
return "{" .. table.concat(t, ",") .. "}"
end
return tostring(val)
end
return common

View File

@ -1,9 +1,8 @@
local config = {}
config.project_scan_rate = 5000 --< @r-lyeh: 5 -> 5000: from 5ms to 5s
config.fps = 60
config.max_log_items = 80
config.message_timeout = 3
config.message_timeout = 6 --< @r-lyeh 3>6
config.mouse_wheel_scroll = 50 * SCALE
config.file_size_limit = 10
config.ignore_files = "^%."
@ -16,7 +15,8 @@ config.line_height = 1.2
config.indent_size = 2
config.tab_type = "soft"
config.line_limit = 80
config.project_scan_depth = 5
config.project_scan_rate = 5 -- seconds to wait before running next full tree globber
config.project_scan_depth = 8 -- max folder depth tree
config.project_max_files_per_folder = 2000
config.blink_period = 1.3 --< https://github.com/rxi/lite/issues/235
config.tabs_allowed = true --< https://github.com/rxi/lite/issues/191

View File

@ -7,6 +7,7 @@ local common = require "core.common"
local Doc = Object:extend()
local unpack = table.unpack
local function split_lines(text)
local res = {}
@ -16,26 +17,6 @@ local function split_lines(text)
return res
end
local function splice(t, at, remove, insert)
insert = insert or {}
local offset = #insert - remove
local old_len = #t
if offset < 0 then
for i = at - offset, old_len - offset do
t[i + offset] = t[i]
end
elseif offset > 0 then
for i = old_len, at, -1 do
t[i + offset] = t[i]
end
end
for i, item in ipairs(insert) do
t[at + i - 1] = item
end
end
function Doc:new(filename)
self:reset()
if filename then
@ -46,7 +27,8 @@ end
function Doc:reset()
self.lines = { "\n" }
self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } }
self.selections = { 1, 1, 1, 1 }
self.cursor_clipboard = {}
self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 }
self.clean_change_id = 1
@ -65,10 +47,16 @@ function Doc:reset_syntax()
end
function Doc:set_filename(filename)
self.filename = filename
self.abs_filename = system.absolute_path(filename)
end
function Doc:load(filename)
local fp = assert( io.open(filename, "rb") )
self:reset()
self.filename = filename
self:set_filename(filename)
self.lines = {}
for line in fp:lines() do
if line:byte(-1) == 13 then
@ -93,7 +81,9 @@ function Doc:save(filename)
fp:write(line)
end
fp:close()
self.filename = filename or self.filename
if filename then
self:set_filename(filename)
end
self:reset_syntax()
self:clean()
end
@ -118,45 +108,96 @@ function Doc:get_change_id()
return self.undo_stack.idx
end
-- Cursor section. Cursor indices are *only* valid during a get_selections() call.
-- Cursors will always be iterated in order from top to bottom. Through normal operation
-- curors can never swap positions; only merge or split, or change their position in cursor
-- order.
function Doc:get_selection(sort)
local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0)
return line1, col1, line2, col2, sort
end
function Doc:set_selection(line1, col1, line2, col2, swap)
assert(not line2 == not col2, "expected 2 or 4 arguments")
function Doc:get_selection_idx(idx, sort)
local line1, col1, line2, col2 = self.selections[idx*4-3], self.selections[idx*4-2], self.selections[idx*4-1], self.selections[idx*4]
if line1 and sort then
return sort_positions(line1, col1, line2, col2)
else
return line1, col1, line2, col2
end
end
function Doc:has_selection()
local line1, col1, line2, col2 = self:get_selection(false)
return line1 ~= line2 or col1 ~= col2
end
function Doc:sanitize_selection()
for idx, line1, col1, line2, col2 in self:get_selections() do
self:set_selections(idx, line1, col1, line2, col2)
end
end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1
end
return line1, col1, line2, col2
end
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
assert(not line2 == not col2, "expected 3 or 5 arguments")
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
line1, col1 = self:sanitize_position(line1, col1)
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
self.selection.a.line, self.selection.a.col = line1, col1
self.selection.b.line, self.selection.b.col = line2, col2
common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 })
end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2
or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
function Doc:add_selection(line1, col1, line2, col2, swap)
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
local target = #self.selections / 4 + 1
for idx, tl1, tc1 in self:get_selections(true) do
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
target = idx
break
end
return line1, col1, line2, col2, false
end
function Doc:get_selection(sort)
local a, b = self.selection.a, self.selection.b
if sort then
return sort_positions(a.line, a.col, b.line, b.col)
end
return a.line, a.col, b.line, b.col
self:set_selections(target, line1, col1, line2, col2, swap, 0)
end
function Doc:has_selection()
local a, b = self.selection.a, self.selection.b
return not (a.line == b.line and a.col == b.col)
function Doc:set_selection(line1, col1, line2, col2, swap)
self.selections, self.cursor_clipboard = {}, {}
self:set_selections(1, line1, col1, line2, col2, swap)
end
function Doc:sanitize_selection()
self:set_selection(self:get_selection())
function Doc:merge_cursors(idx)
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
for j = 1, i - 4, 4 do
if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4)
break
end
end
end
end
local function selection_iterator(invariant, idx)
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
if invariant[2] then
return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4))
else
return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4)
end
end
-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate.
-- If a number, runs for exactly that iteration.
function Doc:get_selections(sort_intra, idx_reverse)
return selection_iterator, { self.selections, sort_intra, idx_reverse },
idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1)+1)
end
-- End of cursor seciton.
function Doc:sanitize_position(line, col)
line = common.clamp(line, 1, #self.lines)
@ -233,7 +274,7 @@ local function push_undo(undo_stack, time, type, ...)
end
local function pop_undo(self, undo_stack, redo_stack)
local function pop_undo(self, undo_stack, redo_stack, modified)
-- pop command
local cmd = undo_stack[undo_stack.idx - 1]
if not cmd then return end
@ -243,21 +284,24 @@ local function pop_undo(self, undo_stack, redo_stack)
if cmd.type == "insert" then
local line, col, text = table.unpack(cmd)
self:raw_insert(line, col, text, redo_stack, cmd.time)
elseif cmd.type == "remove" then
local line1, col1, line2, col2 = table.unpack(cmd)
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
elseif cmd.type == "selection" then
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
self.selections = { unpack(cmd) }
end
modified = modified or (cmd.type ~= "selection")
-- if next undo command is within the merge timeout then treat as a single
-- command and continue to execute it
local next = undo_stack[undo_stack.idx - 1]
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
return pop_undo(self, undo_stack, redo_stack)
return pop_undo(self, undo_stack, redo_stack, modified)
end
if modified then
self:on_text_change("undo")
end
end
@ -274,11 +318,11 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
lines[#lines] = lines[#lines] .. after
-- splice lines into line array
splice(self.lines, line, 1, lines)
common.splice(self.lines, line, 1, lines)
-- push undo
local line2, col2 = self:position_offset(line, col, #text)
push_undo(undo_stack, time, "selection", self:get_selection())
push_undo(undo_stack, time, "selection", unpack(self.selections))
push_undo(undo_stack, time, "remove", line, col, line2, col2)
-- update highlighter and assure selection is in bounds
@ -290,7 +334,7 @@ end
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
-- push undo
local text = self:get_text(line1, col1, line2, col2)
push_undo(undo_stack, time, "selection", self:get_selection())
push_undo(undo_stack, time, "selection", unpack(self.selections))
push_undo(undo_stack, time, "insert", line1, col1, text)
-- get line content before/after removed text
@ -298,7 +342,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
local after = self.lines[line2]:sub(col2)
-- splice line into line array
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
-- update highlighter and assure selection is in bounds
self.highlighter:invalidate(line1)
@ -310,6 +354,7 @@ function Doc:insert(line, col, text)
self.redo_stack = { idx = 1 }
line, col = self:sanitize_position(line, col)
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
self:on_text_change("insert")
end
@ -319,35 +364,34 @@ function Doc:remove(line1, col1, line2, col2)
line2, col2 = self:sanitize_position(line2, col2)
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
self:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
self:on_text_change("remove")
end
function Doc:undo()
pop_undo(self, self.undo_stack, self.redo_stack)
pop_undo(self, self.undo_stack, self.redo_stack, false)
end
function Doc:redo()
pop_undo(self, self.redo_stack, self.undo_stack)
pop_undo(self, self.redo_stack, self.undo_stack, false)
end
function Doc:text_input(text)
if self:has_selection() then
self:delete_to()
function Doc:text_input(text, idx)
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
if line1 ~= line2 or col1 ~= col2 then
self:delete_to_cursor(sidx)
end
self:insert(line1, col1, text)
self:move_to_cursor(sidx, #text)
end
local line, col = self:get_selection()
self:insert(line, col, text)
self:move_to(#text)
end
function Doc:replace(fn)
local line1, col1, line2, col2, swap
local had_selection = self:has_selection()
if had_selection then
line1, col1, line2, col2, swap = self:get_selection(true)
else
local line1, col1, line2, col2 = self:get_selection(true)
if line1 == line2 and col1 == col2 then
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
end
local old_text = self:get_text(line1, col1, line2, col2)
@ -355,38 +399,108 @@ function Doc:replace(fn)
if old_text ~= new_text then
self:insert(line2, col2, new_text)
self:remove(line1, col1, line2, col2)
if had_selection then
if line1 == line2 and col1 == col2 then
line2, col2 = self:position_offset(line1, col1, #new_text)
self:set_selection(line1, col1, line2, col2, swap)
self:set_selection(line1, col1, line2, col2)
end
end
return n
end
function Doc:delete_to(...)
local line, col = self:get_selection(true)
if self:has_selection() then
self:remove(self:get_selection())
function Doc:delete_to_cursor(idx, ...)
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
if line1 ~= line2 or col1 ~= col2 then
self:remove(line1, col1, line2, col2)
else
local line2, col2 = self:position_offset(line, col, ...)
self:remove(line, col, line2, col2)
line, col = sort_positions(line, col, line2, col2)
local l2, c2 = self:position_offset(line1, col1, ...)
self:remove(line1, col1, l2, c2)
line1, col1 = sort_positions(line1, col1, l2, c2)
end
self:set_selection(line, col)
self:set_selections(sidx, line1, col1)
end
self:merge_cursors(idx)
end
function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end
function Doc:move_to(...)
local line, col = self:get_selection()
self:set_selection(self:position_offset(line, col, ...))
function Doc:move_to_cursor(idx, ...)
for sidx, line, col in self:get_selections(false, idx) do
self:set_selections(sidx, self:position_offset(line, col, ...))
end
self:merge_cursors(idx)
end
function Doc:move_to(...) return self:move_to_cursor(nil, ...) end
function Doc:select_to(...)
local line, col, line2, col2 = self:get_selection()
function Doc:select_to_cursor(idx, ...)
for sidx, line, col, line2, col2 in self:get_selections(false, idx) do
line, col = self:position_offset(line, col, ...)
self:set_selection(line, col, line2, col2)
self:set_selections(sidx, line, col, line2, col2)
end
self:merge_cursors(idx)
end
function Doc:select_to(...) return self:select_to_cursor(nil, ...) end
local function get_indent_string()
if config.tab_type == "hard" then
return "\t"
end
return string.rep(" ", config.indent_size)
end
-- returns the size of the original indent, and the indent
-- in your config format, rounded either up or down
local function get_line_indent(line, rnd_up)
local _, e = line:find("^[ \t]+")
local soft_tab = string.rep(" ", config.indent_size)
if config.tab_type == "hard" then
local indent = e and line:sub(1, e):gsub(soft_tab, "\t") or ""
return e, indent:gsub(" +", rnd_up and "\t" or "")
else
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
local number = #indent / #soft_tab
return e, indent:sub(1,
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
end
end
-- un/indents text; behaviour varies based on selection and un/indent.
-- * if there's a selection, it will stay static around the
-- text for both indenting and unindenting.
-- * if you are in the beginning whitespace of a line, and are indenting, the
-- cursor will insert the exactly appropriate amount of spaces, and jump the
-- cursor to the beginning of first non whitespace characters
-- * if you are not in the beginning whitespace of a line, and you indent, it
-- inserts the appropriate whitespace, as if you typed them normally.
-- * if you are unindenting, the cursor will jump to the start of the line,
-- and remove the appropriate amount of spaces (or a tab).
function Doc:indent_text(unindent, line1, col1, line2, col2)
local text = get_indent_string()
local _, se = self.lines[line1]:find("^[ \t]+")
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
local has_selection = line1 ~= line2 or col1 ~= col2
if unindent or has_selection or in_beginning_whitespace then
local l1d, l2d = #self.lines[line1], #self.lines[line2]
for line = line1, line2 do
local e, rnded = get_line_indent(self.lines[line], unindent)
self:remove(line, 1, line, (e or 0) + 1)
self:insert(line, 1,
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
end
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
if (unindent or in_beginning_whitespace) and not has_selection then
local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1])
return line1, start_cursor, line2, start_cursor
end
return line1, col1 + l1d, line2, col2 + l2d
end
self:insert(line1, col1, text)
return line1, col1 + #text, line1, col1 + #text
end
-- For plugins to add custom actions of document change
function Doc:on_text_change(type)
end

View File

@ -56,7 +56,6 @@ function DocView:new(doc)
self.doc = assert(doc)
self.font = "code_font"
self.last_x_offset = {}
self.blink_timer = 0
end
@ -226,10 +225,14 @@ function DocView:on_mouse_pressed(button, x, y, clicks)
end
else
local line, col = self:resolve_screen_position(x, y)
if keymap.modkeys["ctrl"] then
self.doc:add_selection(mouse_selection(self.doc, clicks, line, col, line, col))
else
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
end
self.mouse_selecting = { line, col, clicks = clicks }
end
self.blink_timer = 0
core.blink_timer = 0
end
@ -246,8 +249,16 @@ function DocView:on_mouse_moved(x, y, ...)
local l1, c1 = self:resolve_screen_position(x, y)
local l2, c2 = table.unpack(self.mouse_selecting)
local clicks = self.mouse_selecting.clicks
if keymap.modkeys["ctrl"] then
if l1 > l2 then l1, l2 = l2, l1 end
self.doc.selections = { }
for i = l1, l2 do
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
end
else
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
end
end
end
@ -269,18 +280,18 @@ function DocView:update()
if core.active_view == self then
self:scroll_to_make_visible(line, col)
end
self.blink_timer = 0
core.blink_timer = 0
self.last_line, self.last_col = line, col
end
-- update blink timer
if self == core.active_view and not self.mouse_selecting then
local n = config.blink_period / 2
local prev = self.blink_timer
self.blink_timer = (self.blink_timer + 1 / config.fps) % config.blink_period
if (self.blink_timer > n) ~= (prev > n) then
local T, t0 = config.blink_period, core.blink_start
local ta, tb = core.blink_timer, system.get_time()
if ((tb - t0) % T < T / 2) ~= ((ta - t0) % T < T / 2) then
core.redraw = true
end
core.blink_timer = tb
end
DocView.super.update(self)
@ -304,10 +315,8 @@ end
function DocView:draw_line_body(idx, x, y)
local line, col = self.doc:get_selection()
-- draw selection if it overlaps this line
local line1, col1, line2, col2 = self.doc:get_selection(true)
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
if idx >= line1 and idx <= line2 then
local text = self.doc.lines[idx]
if line1 ~= idx then col1 = 1 end
@ -317,32 +326,39 @@ function DocView:draw_line_body(idx, x, y)
local lh = self:get_line_height()
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end
end
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
-- draw line highlight if caret is on this line
if config.highlight_current_line and not self.doc:has_selection()
and line == idx and core.active_view == self then
if config.highlight_current_line and (line1 == line2 and col1 == col2)
and line1 == idx and core.active_view == self then
self:draw_line_highlight(x + self.scroll.x, y)
end
end
-- draw line's text
self:draw_line_text(idx, x, y)
-- draw caret if it overlaps this line
local T = config.blink_period
for _, line, col in self.doc:get_selections() do
if line == idx and core.active_view == self
and self.blink_timer < config.blink_period / 2
and (core.blink_timer - core.blink_start) % T < T / 2
and system.window_has_focus() then
local lh = self:get_line_height()
local x1 = x + self:get_col_x_offset(line, col)
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
end
end
end
function DocView:draw_line_gutter(idx, x, y)
local color = style.line_number
local line1, _, line2, _ = self.doc:get_selection(true)
for _, line1, _, line2 in self.doc:get_selections(true) do
if idx >= line1 and idx <= line2 then
color = style.line_number2
break
end
end
local yoffset = self:get_line_text_y_offset()
x = x + style.padding.x

View File

@ -108,6 +108,8 @@ function core.init()
core.docs = {}
core.threads = setmetatable({}, { __mode = "k" })
core.project_files = {}
core.blink_start = system.get_time()
core.blink_timer = core.blink_start
core.redraw = true
core.root_view = RootView()
@ -252,6 +254,7 @@ function core.add_thread(f, weak_ref)
local key = weak_ref or #core.threads + 1
local fn = function() return core.try(f) end
core.threads[key] = { cr = coroutine.create(fn), wake = 0 }
return key
end
@ -493,6 +496,10 @@ function core.run()
end
--< @r-lyeh } split core.run() into core.run1()
function core.blink_reset()
core.blink_start = system.get_time()
end
function core.on_error(err)
-- write error to file
local fp = io.open(EXEDIR .. "/error.txt", "wb")

View File

@ -127,6 +127,8 @@ keymap.add {
["ctrl+x"] = "doc:cut",
["ctrl+c"] = "doc:copy",
["ctrl+v"] = "doc:paste",
["ctrl+insert"] = "doc:copy",
["shift+insert"] = "doc:paste",
["escape"] = { "command:escape", "doc:select-none" },
["tab"] = { "command:complete", "doc:indent" },
["shift+tab"] = "doc:unindent",
@ -144,7 +146,7 @@ keymap.add {
["ctrl+shift+return"] = "doc:newline-above",
["ctrl+j"] = "doc:join-lines",
["ctrl+a"] = "doc:select-all",
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
["ctrl+l"] = "doc:select-lines",
["ctrl+/"] = "doc:toggle-line-comments",
["ctrl+up"] = "doc:move-lines-up",
@ -181,6 +183,8 @@ keymap.add {
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
["shift+pageup"] = "doc:select-to-previous-page",
["shift+pagedown"] = "doc:select-to-next-page",
["ctrl+shift+up"] = "doc:create-cursor-previous-line",
["ctrl+shift+down"] = "doc:create-cursor-next-line"
}
return keymap

View File

@ -0,0 +1,375 @@
local core = require "core"
local common = require "core.common"
local style = require "core.style"
local keymap = require "core.keymap"
local Object = require "core.object"
local View = require "core.view"
local DocView = require "core.docview"
local config = require "core.config"
local EmptyView = View:extend()
local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = th + style.padding.y * 2
x = renderer.draw_text(style.big_font, "edit", x, y + (dh - th) / 2, color) --< @r-lyeh lite>edit
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
}
th = style.font:get_height()
y = y + (dh - th * 2 - style.padding.y) / 2
local w = 0
for _, line in ipairs(lines) do
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
y = y + th + style.padding.y
end
return w, dh
end
function EmptyView:draw()
self:draw_background(style.background)
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
local y = self.position.y + (self.size.y - h) / 2
draw_text(x, y, style.dim)
end
local Node = Object:extend()
function Node:new(type)
self.type = type or "leaf"
self.position = { x = 0, y = 0 }
self.size = { x = 0, y = 0 }
self.views = {}
self.divider = 0.5
if self.type == "leaf" then
self:add_view(EmptyView())
end
end
function Node:propagate(fn, ...)
self.a[fn](self.a, ...)
self.b[fn](self.b, ...)
end
function Node:on_mouse_moved(x, y, ...)
self.hovered_tab = self:get_tab_overlapping_point(x, y)
if self.type == "leaf" then
self.active_view:on_mouse_moved(x, y, ...)
else
self:propagate("on_mouse_moved", x, y, ...)
end
end
function Node:on_mouse_released(...)
if self.type == "leaf" then
self.active_view:on_mouse_released(...)
else
self:propagate("on_mouse_released", ...)
end
end
function Node:consume(node)
for k, _ in pairs(self) do self[k] = nil end
for k, v in pairs(node) do self[k] = v end
end
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
function Node:split(dir, view, locked)
assert(self.type == "leaf", "Tried to split non-leaf node")
local type = assert(type_map[dir], "Invalid direction")
local last_active = core.active_view
local child = Node()
child:consume(self)
self:consume(Node(type))
self.a = child
self.b = Node()
if view then self.b:add_view(view) end
if locked then
self.b.locked = locked
core.set_active_view(last_active)
end
if dir == "up" or dir == "left" then
self.a, self.b = self.b, self.a
end
return child
end
function Node:close_active_view(root)
local do_close = function()
if #self.views > 1 then
local idx = self:get_view_idx(self.active_view)
table.remove(self.views, idx)
self:set_active_view(self.views[idx] or self.views[#self.views])
else
local parent = self:get_parent_node(root)
local is_a = (parent.a == self)
local other = parent[is_a and "b" or "a"]
if other:get_locked_size() then
self.views = {}
self:add_view(EmptyView())
else
parent:consume(other)
local p = parent
while p.type ~= "leaf" do
p = p[is_a and "a" or "b"]
end
p:set_active_view(p.active_view)
end
end
core.last_active_view = nil
end
self.active_view:try_close(do_close)
end
function Node:add_view(view)
assert(self.type == "leaf", "Tried to add view to non-leaf node")
assert(not self.locked, "Tried to add view to locked node")
if self.views[1] and self.views[1]:is(EmptyView) then
table.remove(self.views)
end
table.insert(self.views, view)
self:set_active_view(view)
end
function Node:set_active_view(view)
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
self.active_view = view
core.set_active_view(view)
end
function Node:get_view_idx(view)
for i, v in ipairs(self.views) do
if v == view then return i end
end
end
function Node:get_node_for_view(view)
for _, v in ipairs(self.views) do
if v == view then return self end
end
if self.type ~= "leaf" then
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
end
end
function Node:get_parent_node(root)
if root.a == self or root.b == self then
return root
elseif root.type ~= "leaf" then
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
end
end
function Node:get_children(t)
t = t or {}
for _, view in ipairs(self.views) do
table.insert(t, view)
end
if self.a then self.a:get_children(t) end
if self.b then self.b:get_children(t) end
return t
end
function Node:get_divider_overlapping_point(px, py)
if self.type ~= "leaf" then
local p = 6
local x, y, w, h = self:get_divider_rect()
x, y = x - p, y - p
w, h = w + p * 2, h + p * 2
if px > x and py > y and px < x + w and py < y + h then
return self
end
return self.a:get_divider_overlapping_point(px, py)
or self.b:get_divider_overlapping_point(px, py)
end
end
function Node:get_tab_overlapping_point(px, py)
if #self.views == 1 then return nil end
local x, y, w, h = self:get_tab_rect(1)
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
return math.floor((px - x) / w) + 1
end
end
function Node:get_child_overlapping_point(x, y)
local child
if self.type == "leaf" then
return self
elseif self.type == "hsplit" then
child = (x < self.b.position.x) and self.a or self.b
elseif self.type == "vsplit" then
child = (y < self.b.position.y) and self.a or self.b
end
return child:get_child_overlapping_point(x, y)
end
function Node:get_tab_rect(idx)
if not config.tabs_allowed then return 0,0,0,0 end --< https://github.com/rxi/lite/issues/191
local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
local h = style.font:get_height() + style.padding.y * 2
return self.position.x + (idx-1) * tw, self.position.y, tw, h
end
function Node:get_divider_rect()
local x, y = self.position.x, self.position.y
if self.type == "hsplit" then
return x + self.a.size.x, y, style.divider_size, self.size.y
elseif self.type == "vsplit" then
return x, y + self.a.size.y, self.size.x, style.divider_size
end
end
function Node:get_locked_size()
if self.type == "leaf" then
if self.locked then
local size = self.active_view.size
return size.x, size.y
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if x1 and x2 then
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
return x1 + x2 + dsx, y1 + y2 + dsy
end
end
end
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2)
local n
local ds = (x1 and x1 < 1 or x2 and x2 < 1) and 0 or style.divider_size
if x1 then
n = x1 + ds
elseif x2 then
n = self.size[x] - x2
else
n = math.floor(self.size[x] * self.divider)
end
self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y]
self.a.size[x] = n - ds
self.a.size[y] = self.size[y]
self.b.position[x] = self.position[x] + n
self.b.position[y] = self.position[y]
self.b.size[x] = self.size[x] - n
self.b.size[y] = self.size[y]
end
function Node:update_layout()
if self.type == "leaf" then
local av = self.active_view
if #self.views > 1 then
local _, _, _, th = self:get_tab_rect(1)
av.position.x, av.position.y = self.position.x, self.position.y + th
av.size.x, av.size.y = self.size.x, self.size.y - th
else
common.copy_position_and_size(av, self)
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if self.type == "hsplit" then
calc_split_sizes(self, "x", "y", x1, x2)
elseif self.type == "vsplit" then
calc_split_sizes(self, "y", "x", y1, y2)
end
self.a:update_layout()
self.b:update_layout()
end
end
function Node:update()
if self.type == "leaf" then
for _, view in ipairs(self.views) do
view:update()
end
else
self.a:update()
self.b:update()
end
end
function Node:draw_tabs()
local x, y, _, h = self:get_tab_rect(1)
local ds = style.divider_size
core.push_clip_rect(x, y, self.size.x, h)
renderer.draw_rect(x, y, self.size.x, h, style.background2)
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
for i, view in ipairs(self.views) do
local x, y, w, h = self:get_tab_rect(i)
local text = view:get_name()
local color = style.dim
if view == self.active_view then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
end
if i == self.hovered_tab then
color = style.text
end
core.push_clip_rect(x, y, w, h)
x, w = x + style.padding.x, w - style.padding.x * 2
local align = style.font:get_width(text) > w and "left" or "center"
common.draw_text(style.font, color, text, align, x, y, w, h)
core.pop_clip_rect()
end
core.pop_clip_rect()
end
function Node:draw()
if self.type == "leaf" then
if #self.views > 1 and config.tabs_allowed then --< https://github.com/rxi/lite/issues/191
self:draw_tabs()
end
local pos, size = self.active_view.position, self.active_view.size
core.push_clip_rect(pos.x, pos.y, size.x + pos.x % 1, size.y + pos.y % 1)
self.active_view:draw()
core.pop_clip_rect()
else
local x, y, w, h = self:get_divider_rect()
renderer.draw_rect(x, y, w, h, style.divider)
self:propagate("draw")
end
end
return Node

View File

@ -5,378 +5,13 @@ local keymap = require "core.keymap"
local Object = require "core.object"
local View = require "core.view"
local DocView = require "core.docview"
local Node = require "core.node"
local config = require "core.config"
local EmptyView = View:extend()
local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = th + style.padding.y * 2
x = renderer.draw_text(style.big_font, "edit", x, y + (dh - th) / 2, color) --< @r-lyeh lite>edit
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
}
th = style.font:get_height()
y = y + (dh - th * 2 - style.padding.y) / 2
local w = 0
for _, line in ipairs(lines) do
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
y = y + th + style.padding.y
end
return w, dh
end
function EmptyView:draw()
self:draw_background(style.background)
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
local y = self.position.y + (self.size.y - h) / 2
draw_text(x, y, style.dim)
end
local Node = Object:extend()
function Node:new(type)
self.type = type or "leaf"
self.position = { x = 0, y = 0 }
self.size = { x = 0, y = 0 }
self.views = {}
self.divider = 0.5
if self.type == "leaf" then
self:add_view(EmptyView())
end
end
function Node:propagate(fn, ...)
self.a[fn](self.a, ...)
self.b[fn](self.b, ...)
end
function Node:on_mouse_moved(x, y, ...)
self.hovered_tab = self:get_tab_overlapping_point(x, y)
if self.type == "leaf" then
self.active_view:on_mouse_moved(x, y, ...)
else
self:propagate("on_mouse_moved", x, y, ...)
end
end
function Node:on_mouse_released(...)
if self.type == "leaf" then
self.active_view:on_mouse_released(...)
else
self:propagate("on_mouse_released", ...)
end
end
function Node:consume(node)
for k, _ in pairs(self) do self[k] = nil end
for k, v in pairs(node) do self[k] = v end
end
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
function Node:split(dir, view, locked)
assert(self.type == "leaf", "Tried to split non-leaf node")
local type = assert(type_map[dir], "Invalid direction")
local last_active = core.active_view
local child = Node()
child:consume(self)
self:consume(Node(type))
self.a = child
self.b = Node()
if view then self.b:add_view(view) end
if locked then
self.b.locked = locked
core.set_active_view(last_active)
end
if dir == "up" or dir == "left" then
self.a, self.b = self.b, self.a
end
return child
end
function Node:close_active_view(root)
local do_close = function()
if #self.views > 1 then
local idx = self:get_view_idx(self.active_view)
table.remove(self.views, idx)
self:set_active_view(self.views[idx] or self.views[#self.views])
else
local parent = self:get_parent_node(root)
local is_a = (parent.a == self)
local other = parent[is_a and "b" or "a"]
if other:get_locked_size() then
self.views = {}
self:add_view(EmptyView())
else
parent:consume(other)
local p = parent
while p.type ~= "leaf" do
p = p[is_a and "a" or "b"]
end
p:set_active_view(p.active_view)
end
end
core.last_active_view = nil
end
self.active_view:try_close(do_close)
end
function Node:add_view(view)
assert(self.type == "leaf", "Tried to add view to non-leaf node")
assert(not self.locked, "Tried to add view to locked node")
if self.views[1] and self.views[1]:is(EmptyView) then
table.remove(self.views)
end
table.insert(self.views, view)
self:set_active_view(view)
end
function Node:set_active_view(view)
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
self.active_view = view
core.set_active_view(view)
end
function Node:get_view_idx(view)
for i, v in ipairs(self.views) do
if v == view then return i end
end
end
function Node:get_node_for_view(view)
for _, v in ipairs(self.views) do
if v == view then return self end
end
if self.type ~= "leaf" then
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
end
end
function Node:get_parent_node(root)
if root.a == self or root.b == self then
return root
elseif root.type ~= "leaf" then
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
end
end
function Node:get_children(t)
t = t or {}
for _, view in ipairs(self.views) do
table.insert(t, view)
end
if self.a then self.a:get_children(t) end
if self.b then self.b:get_children(t) end
return t
end
function Node:get_divider_overlapping_point(px, py)
if self.type ~= "leaf" then
local p = 6
local x, y, w, h = self:get_divider_rect()
x, y = x - p, y - p
w, h = w + p * 2, h + p * 2
if px > x and py > y and px < x + w and py < y + h then
return self
end
return self.a:get_divider_overlapping_point(px, py)
or self.b:get_divider_overlapping_point(px, py)
end
end
function Node:get_tab_overlapping_point(px, py)
if #self.views == 1 then return nil end
local x, y, w, h = self:get_tab_rect(1)
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
return math.floor((px - x) / w) + 1
end
end
function Node:get_child_overlapping_point(x, y)
local child
if self.type == "leaf" then
return self
elseif self.type == "hsplit" then
child = (x < self.b.position.x) and self.a or self.b
elseif self.type == "vsplit" then
child = (y < self.b.position.y) and self.a or self.b
end
return child:get_child_overlapping_point(x, y)
end
function Node:get_tab_rect(idx)
if not config.tabs_allowed then return 0,0,0,0 end --< https://github.com/rxi/lite/issues/191
local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
local h = style.font:get_height() + style.padding.y * 2
return self.position.x + (idx-1) * tw, self.position.y, tw, h
end
function Node:get_divider_rect()
local x, y = self.position.x, self.position.y
if self.type == "hsplit" then
return x + self.a.size.x, y, style.divider_size, self.size.y
elseif self.type == "vsplit" then
return x, y + self.a.size.y, self.size.x, style.divider_size
end
end
function Node:get_locked_size()
if self.type == "leaf" then
if self.locked then
local size = self.active_view.size
return size.x, size.y
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if x1 and x2 then
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
return x1 + x2 + dsx, y1 + y2 + dsy
end
end
end
local function copy_position_and_size(dst, src)
dst.position.x, dst.position.y = src.position.x, src.position.y
dst.size.x, dst.size.y = src.size.x, src.size.y
end
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2)
local n
local ds = (x1 and x1 < 1 or x2 and x2 < 1) and 0 or style.divider_size
if x1 then
n = x1 + ds
elseif x2 then
n = self.size[x] - x2
else
n = math.floor(self.size[x] * self.divider)
end
self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y]
self.a.size[x] = n - ds
self.a.size[y] = self.size[y]
self.b.position[x] = self.position[x] + n
self.b.position[y] = self.position[y]
self.b.size[x] = self.size[x] - n
self.b.size[y] = self.size[y]
end
function Node:update_layout()
if self.type == "leaf" then
local av = self.active_view
if #self.views > 1 then
local _, _, _, th = self:get_tab_rect(1)
av.position.x, av.position.y = self.position.x, self.position.y + th
av.size.x, av.size.y = self.size.x, self.size.y - th
else
copy_position_and_size(av, self)
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if self.type == "hsplit" then
calc_split_sizes(self, "x", "y", x1, x2)
elseif self.type == "vsplit" then
calc_split_sizes(self, "y", "x", y1, y2)
end
self.a:update_layout()
self.b:update_layout()
end
end
function Node:update()
if self.type == "leaf" then
for _, view in ipairs(self.views) do
view:update()
end
else
self.a:update()
self.b:update()
end
end
function Node:draw_tabs()
local x, y, _, h = self:get_tab_rect(1)
local ds = style.divider_size
core.push_clip_rect(x, y, self.size.x, h)
renderer.draw_rect(x, y, self.size.x, h, style.background2)
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
for i, view in ipairs(self.views) do
local x, y, w, h = self:get_tab_rect(i)
local text = view:get_name()
local color = style.dim
if view == self.active_view then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
end
if i == self.hovered_tab then
color = style.text
end
core.push_clip_rect(x, y, w, h)
x, w = x + style.padding.x, w - style.padding.x * 2
local align = style.font:get_width(text) > w and "left" or "center"
common.draw_text(style.font, color, text, align, x, y, w, h)
core.pop_clip_rect()
end
core.pop_clip_rect()
end
function Node:draw()
if self.type == "leaf" then
if #self.views > 1 and config.tabs_allowed then --< https://github.com/rxi/lite/issues/191
self:draw_tabs()
end
local pos, size = self.active_view.position, self.active_view.size
core.push_clip_rect(pos.x, pos.y, size.x + pos.x % 1, size.y + pos.y % 1)
self.active_view:draw()
core.pop_clip_rect()
else
local x, y, w, h = self:get_divider_rect()
renderer.draw_rect(x, y, w, h, style.divider)
self:propagate("draw")
end
end
local RootView = View:extend()
@ -430,7 +65,7 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
local idx = node:get_tab_overlapping_point(x, y)
if idx then
node:set_active_view(node.views[idx])
if button == "middle" then
if button == "right" then --< @rlyeh middle>right
node:close_active_view(self.root_node)
end
else
@ -488,7 +123,7 @@ end
function RootView:update()
copy_position_and_size(self.root_node, self)
common.copy_position_and_size(self.root_node, self)
self.root_node:update()
self.root_node:update_layout()
end

View File

@ -18,6 +18,8 @@ function StatusView:new()
StatusView.super.new(self)
self.message_timeout = 0
self.message = {}
self.tooltip_mode = false
self.tooltip = {}
end
@ -39,6 +41,17 @@ function StatusView:show_message(icon, icon_color, text)
end
function StatusView:show_tooltip(text)
self.tooltip = { text }
self.tooltip_mode = true
end
function StatusView:remove_tooltip()
self.tooltip_mode = false
end
function StatusView:update()
self.size.y = style.font:get_height() + style.padding.y * 2
@ -94,6 +107,9 @@ function StatusView:get_items()
local dv = core.active_view
local line, col = dv.doc:get_selection()
local dirty = dv.doc:is_dirty()
local indent = dv.doc.indent_info
local indent_label = (indent and indent.type == "hard") and "tabs: " or "spaces: "
local indent_size = indent and tostring(indent.size) .. (indent.confirmed and "" or "*") or "unknown"
--< https://github.com/rxi/lite/issues/300
col = common.utf8_len(dv.doc:get_text(line, 1, line, col)) + 1
@ -112,6 +128,8 @@ function StatusView:get_items()
self.separator,
string.format("%.f%%", line / #dv.doc.lines * 100), --< @r-lyeh: %d -> %.f
}, {
style.text, indent_label, indent_size,
style.dim, self.separator2, style.text,
style.icon_font, "g",
style.font, style.dim, self.separator2, style.text,
#dv.doc.lines, " lines",
@ -136,9 +154,13 @@ function StatusView:draw()
self:draw_items(self.message, false, self.size.y)
end
if self.tooltip_mode then
self:draw_items(self.tooltip)
else
local left, right = self:get_items()
self:draw_items(left)
self:draw_items(right, true)
end
end

View File

@ -134,12 +134,12 @@ Plugins can be downloaded from the [plugins repository](https://github.com/rxi/l
## Color Themes
Colors themes in lite are lua modules which overwrite the color fields of lite's
`core.style` module. Color themes should be placed in the `data/user/colors`
`core.style` module. Color themes should be placed in the `data/themes`
directory.
A color theme can be set by requiring it in your user module:
```lua
require "user.colors.winter"
require "themes.winter"
```
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).

View File

@ -0,0 +1,383 @@
local core = require "core"
local keymap = require "core.keymap"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local View = require "core.view"
config.console_size = 250 * SCALE
config.max_console_lines = 200
config.autoscroll_console = true
local files = {
script = core.temp_filename(PLATFORM == "Windows" and ".bat"),
script2 = core.temp_filename(PLATFORM == "Windows" and ".bat"),
output = core.temp_filename(),
complete = core.temp_filename(),
}
local console = {}
local views = {}
local pending_threads = {}
local thread_active = false
local output = nil
local output_id = 0
local visible = false
function console.clear()
output = { { text = "", time = 0 } }
end
local function read_file(filename, offset)
local fp = io.open(filename, "rb")
fp:seek("set", offset or 0)
local res = fp:read("*a")
fp:close()
return res
end
local function write_file(filename, text)
local fp = io.open(filename, "w")
fp:write(text)
fp:close()
end
local function lines(text)
return (text .. "\n"):gmatch("(.-)\n")
end
local function push_output(str, opt)
local first = true
for line in lines(str) do
if first then
line = table.remove(output).text .. line
end
line = line:gsub("\x1b%[[%d;]+m", "") -- strip ANSI colors
table.insert(output, {
text = line,
time = os.time(),
icon = line:find(opt.error_pattern) and "!"
or line:find(opt.warning_pattern) and "i",
file_pattern = opt.file_pattern,
})
if #output > config.max_console_lines then
table.remove(output, 1)
for view in pairs(views) do
view:on_line_removed()
end
end
first = false
end
output_id = output_id + 1
core.redraw = true
end
local function init_opt(opt)
local res = {
command = "",
file_pattern = "[^?:%s]+%.[^?:%s]+",
error_pattern = "error",
warning_pattern = "warning",
on_complete = function() end,
}
for k, v in pairs(res) do
res[k] = opt[k] or v
end
return res
end
function console.run(opt)
opt = init_opt(opt)
local function thread()
-- init script file(s)
if PLATFORM == "Windows" then
write_file(files.script, opt.command .. "\n")
write_file(files.script2, string.format([[
@echo off
call %q >%q 2>&1
echo "" >%q
exit
]], files.script, files.output, files.complete))
system.exec(string.format("call %q", files.script2))
else
write_file(files.script, string.format([[
%s
touch %q
]], opt.command, files.complete))
system.exec(string.format("bash %q >%q 2>&1", files.script, files.output))
end
-- checks output file for change and reads
local last_size = 0
local function check_output_file()
if PLATFORM == "Windows" then
local fp = io.open(files.output)
if fp then fp:close() end
end
local info = system.get_file_info(files.output)
if info and info.size > last_size then
local text = read_file(files.output, last_size)
push_output(text, opt)
last_size = info.size
end
end
-- read output file until we get a file indicating completion
while not system.get_file_info(files.complete) do
check_output_file()
coroutine.yield(0.1)
end
check_output_file()
if output[#output].text ~= "" then
push_output("\n", opt)
end
push_output("!DIVIDER\n", opt)
-- clean up and finish
for _, file in pairs(files) do
os.remove(file)
end
opt.on_complete()
-- handle pending thread
local pending = table.remove(pending_threads, 1)
if pending then
core.add_thread(pending)
else
thread_active = false
end
end
-- push/init thread
if thread_active then
table.insert(pending_threads, thread)
else
core.add_thread(thread)
thread_active = true
end
-- make sure static console is visible if it's the only ConsoleView
local count = 0
for _ in pairs(views) do count = count + 1 end
if count == 1 then visible = true end
end
local ConsoleView = View:extend()
function ConsoleView:new()
ConsoleView.super.new(self)
self.scrollable = true
self.hovered_idx = -1
views[self] = true
end
function ConsoleView:try_close(...)
ConsoleView.super.try_close(self, ...)
views[self] = nil
end
function ConsoleView:get_name()
return "Console"
end
function ConsoleView:get_line_height()
return style.code_font:get_height() * config.line_height
end
function ConsoleView:get_line_count()
return #output - (output[#output].text == "" and 1 or 0)
end
function ConsoleView:get_scrollable_size()
return self:get_line_count() * self:get_line_height() + style.padding.y * 2
end
function ConsoleView:get_visible_line_range()
local lh = self:get_line_height()
local min = math.max(1, math.floor(self.scroll.y / lh))
return min, min + math.floor(self.size.y / lh) + 1
end
function ConsoleView:on_mouse_moved(mx, my, ...)
ConsoleView.super.on_mouse_moved(self, mx, my, ...)
self.hovered_idx = 0
for i, item, x,y,w,h in self:each_visible_line() do
if mx >= x and my >= y and mx < x + w and my < y + h then
if item.text:find(item.file_pattern) then
self.hovered_idx = i
end
break
end
end
end
local function resolve_file(name)
if system.get_file_info(name) then
return name
end
local filenames = {}
for _, f in ipairs(core.project_files) do
table.insert(filenames, f.filename)
end
local t = common.fuzzy_match(filenames, name)
return t[1]
end
function ConsoleView:on_line_removed()
local diff = self:get_line_height()
self.scroll.y = self.scroll.y - diff
self.scroll.to.y = self.scroll.to.y - diff
end
function ConsoleView:on_mouse_pressed(...)
local caught = ConsoleView.super.on_mouse_pressed(self, ...)
if caught then
return
end
local item = output[self.hovered_idx]
if item then
local file, line, col = item.text:match(item.file_pattern)
local resolved_file = resolve_file(file)
if not resolved_file then
core.error("Couldn't resolve file \"%s\"", file)
return
end
core.try(function()
core.set_active_view(core.last_active_view)
local dv = core.root_view:open_doc(core.open_doc(resolved_file))
if line then
dv.doc:set_selection(line, col or 0)
dv:scroll_to_line(line, false, true)
end
end)
end
end
function ConsoleView:each_visible_line()
return coroutine.wrap(function()
local x, y = self:get_content_offset()
local lh = self:get_line_height()
local min, max = self:get_visible_line_range()
y = y + lh * (min - 1) + style.padding.y
max = math.min(max, self:get_line_count())
for i = min, max do
local item = output[i]
if not item then break end
coroutine.yield(i, item, x, y, self.size.x, lh)
y = y + lh
end
end)
end
function ConsoleView:update(...)
if self.last_output_id ~= output_id then
if config.autoscroll_console then
self.scroll.to.y = self:get_scrollable_size()
end
self.last_output_id = output_id
end
ConsoleView.super.update(self, ...)
end
function ConsoleView:draw()
self:draw_background(style.background)
local icon_w = style.icon_font:get_width("!")
for i, item, x, y, w, h in self:each_visible_line() do
local tx = x + style.padding.x
local time = os.date("%H:%M:%S", item.time)
local color = style.text
if self.hovered_idx == i then
color = style.accent
renderer.draw_rect(x, y, w, h, style.line_highlight)
end
if item.text == "!DIVIDER" then
local w = style.font:get_width(time)
renderer.draw_rect(tx, y + h / 2, w, math.ceil(SCALE * 1), style.dim)
else
tx = common.draw_text(style.font, style.dim, time, "left", tx, y, w, h)
tx = tx + style.padding.x
if item.icon then
common.draw_text(style.icon_font, color, item.icon, "left", tx, y, w, h)
end
tx = tx + icon_w + style.padding.x
common.draw_text(style.code_font, color, item.text, "left", tx, y, w, h)
end
end
self:draw_scrollbar(self)
end
-- init static bottom-of-screen console
local view = ConsoleView()
local node = core.root_view:get_active_node()
node:split("down", view, true)
function view:update(...)
local dest = visible and config.console_size or 0
self:move_towards(self.size, "y", dest)
ConsoleView.update(self, ...)
end
local last_command = ""
command.add(nil, {
["console:reset-output"] = function()
output = { { text = "", time = 0 } }
end,
["console:open-console"] = function()
local node = core.root_view:get_active_node()
node:add_view(ConsoleView())
end,
["console:toggle"] = function()
visible = not visible
end,
["console:run"] = function()
core.command_view:set_text(last_command, true)
core.command_view:enter("Run Console Command", function(cmd)
console.run { command = cmd }
last_command = cmd
end)
end
})
keymap.add {
["ctrl+."] = "console:toggle",
["ctrl+shift+."] = "console:run",
}
-- for `workspace` plugin:
package.loaded["plugins.console.view"] = ConsoleView
console.clear()
return console

View File

@ -0,0 +1,271 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local command = require "core.command"
local keymap = require "core.keymap"
local style = require "core.style"
local Object = require "core.object"
local RootView = require "core.rootview"
local border_width = 1
local divider_width = 1
local DIVIDER = {}
local ContextMenu = Object:extend()
ContextMenu.DIVIDER = DIVIDER
function ContextMenu:new()
self.itemset = {}
self.show_context_menu = false
self.selected = -1
self.height = 0
self.position = { x = 0, y = 0 }
end
local function get_item_size(item)
local lw, lh
if item == DIVIDER then
lw = 0
lh = divider_width
else
lw = style.font:get_width(item.text)
if item.info then
lw = lw + style.padding.x + style.font:get_width(item.info)
end
lh = style.font:get_height() + style.padding.y
end
return lw, lh
end
function ContextMenu:register(predicate, items)
if type(predicate) == "string" then
predicate = require(predicate)
end
if type(predicate) == "table" then
local class = predicate
predicate = function() return core.active_view:is(class) end
end
local width, height = 0, 0 --precalculate the size of context menu
for i, item in ipairs(items) do
if item ~= DIVIDER then
item.info = keymap.reverse_map[item.command]
end
local lw, lh = get_item_size(item)
width = math.max(width, lw)
height = height + lh
end
width = width + style.padding.x * 2
items.width, items.height = width, height
table.insert(self.itemset, { predicate = predicate, items = items })
end
function ContextMenu:show(x, y)
self.items = nil
for _, items in ipairs(self.itemset) do
if items.predicate(x, y) then
self.items = items.items
break
end
end
if self.items then
local w, h = self.items.width, self.items.height
-- by default the box is opened on the right and below
if x + w >= core.root_view.size.x then
x = x - w
end
if y + h >= core.root_view.size.y then
y = y - h
end
self.position.x, self.position.y = x, y
self.show_context_menu = true
return true
end
return false
end
function ContextMenu:hide()
self.show_context_menu = false
self.items = nil
self.selected = -1
self.height = 0
end
function ContextMenu:each_item()
local x, y, w = self.position.x, self.position.y, self.items.width
local oy = y
return coroutine.wrap(function()
for i, item in ipairs(self.items) do
local _, lh = get_item_size(item)
if y - oy > self.height then break end
coroutine.yield(i, item, x, y, w, lh)
y = y + lh
end
end)
end
function ContextMenu:on_mouse_moved(px, py)
if not self.show_context_menu then return end
for i, item, x, y, w, h in self:each_item() do
if px > x and px <= x + w and py > y and py <= y + h then
system.set_cursor("arrow")
self.selected = i
return true
end
end
self.selected = -1
return true
end
function ContextMenu:on_selected(item)
if type(item.command) == "string" then
command.perform(item.command)
else
item.command()
end
end
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
local selected = (self.items or {})[self.selected]
local caught = false
self:hide()
if button == "left" then
if selected then
self:on_selected(selected)
caught = true
end
end
if button == "right" then
caught = self:show(x, y)
end
return caught
end
-- copied from core.docview
function ContextMenu:move_towards(t, k, dest, rate)
if type(t) ~= "table" then
return self:move_towards(self, t, k, dest, rate)
end
local val = t[k]
if math.abs(val - dest) < 0.5 then
t[k] = dest
else
t[k] = common.lerp(val, dest, rate or 0.5)
end
if val ~= dest then
core.redraw = true
end
end
function ContextMenu:update()
if self.show_context_menu then
self:move_towards("height", self.items.height)
end
end
function ContextMenu:draw()
if not self.show_context_menu then return end
core.root_view:defer_draw(self.draw_context_menu, self)
end
function ContextMenu:draw_context_menu()
if not self.items then return end
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
renderer.draw_rect(
bx - border_width,
by - border_width,
bw + (border_width * 2),
bh + (border_width * 2),
style.divider
)
renderer.draw_rect(bx, by, bw, bh, style.background3)
for i, item, x, y, w, h in self:each_item() do
if item == DIVIDER then
renderer.draw_rect(x, y, w, h, style.caret)
else
if i == self.selected then
renderer.draw_rect(x, y, w, h, style.selection)
end
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
if item.info then
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
end
end
end
end
local menu = ContextMenu()
local root_view_on_mouse_pressed = RootView.on_mouse_pressed
local root_view_on_mouse_moved = RootView.on_mouse_moved
local root_view_update = RootView.update
local root_view_draw = RootView.draw
function RootView:on_mouse_moved(...)
if menu:on_mouse_moved(...) then return end
root_view_on_mouse_moved(self, ...)
end
-- copied from core.rootview
function RootView:on_mouse_pressed(button, x,y, clicks)
local div = self.root_node:get_divider_overlapping_point(x, y)
if div then
self.dragged_divider = div
return
end
local node = self.root_node:get_child_overlapping_point(x, y)
local idx = node:get_tab_overlapping_point(x, y)
if idx then
node:set_active_view(node.views[idx])
if button == "right" then --< @r-lyeh middle>right
node:close_active_view(self.root_node)
end
else
core.set_active_view(node.active_view)
-- send to context menu first
if not menu:on_mouse_pressed(button, x, y, clicks) then
node.active_view:on_mouse_pressed(button, x, y, clicks)
end
end
end
function RootView:update(...)
root_view_update(self, ...)
menu:update()
end
function RootView:draw(...)
root_view_draw(self, ...)
menu:draw()
end
command.add(nil, {
["context:show"] = function()
menu:show(core.active_view.position.x, core.active_view.position.y)
end
})
keymap.add {
["menu"] = "context:show"
}
-- register some sensible defaults
menu:register("core.docview", {
{ text = "Cut", command = "doc:cut" },
{ text = "Copy", command = "doc:copy" },
{ text = "Paste", command = "doc:paste" },
DIVIDER,
{ text = "Command Palette...", command = "core:find-command" }
})
return menu

View File

@ -0,0 +1,37 @@
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
local command = require "core.command"
-- originally written by luveti
config.whitespace_map = { [" "] = "·", ["\t"] = "»" }
config.draw_whitespace = true
local draw_line_text = DocView.draw_line_text
function DocView:draw_line_text(idx, x, y)
draw_line_text(self, idx, x, y)
if not config.draw_whitespace then return end
local text = self.doc.lines[idx]
local tx, ty = x, y + self:get_line_text_y_offset()
local font = self:get_font()
local color = style.whitespace or style.syntax.comment
local map = config.whitespace_map
for chr in common.utf8_chars(text) do
local rep = map[chr]
if rep then
renderer.draw_text(font, rep, tx, ty, color)
end
tx = tx + font:get_width(chr)
end
end
command.add("core.docview", {
["draw-whitespace:toggle"] = function() config.draw_whitespace = not config.draw_whitespace end,
["draw-whitespace:disable"] = function() config.draw_whitespace = false end,
["draw-whitespace:enable"] = function() config.draw_whitespace = true end,
})

View File

@ -0,0 +1,167 @@
-- mod-version:1 -- lite-xl 1.16
local core = require "core"
local common = require "core.common"
local command = require "core.command"
local fsutils = {}
function fsutils.iterdir(dir)
local stack = { dir }
return function()
local path = table.remove(stack)
if not path then return end
for _, file in ipairs(system.list_dir(path) or {}) do
stack[#stack + 1] = path .. PATHSEP .. file
end
return path, system.get_file_info(path)
end
end
function fsutils.delete(dir, yield)
local dirs = {}
local n = 0
for filename, stat in fsutils.iterdir(dir) do
if stat.type == "dir" then
-- this will later allow us to delete the dirs in correct sequence
table.insert(dirs, filename)
else
os.remove(filename)
if yield then
n = n + 1
coroutine.yield(n)
end
end
end
for i = #dirs, 1, -1 do
os.remove(dirs[i])
if yield then
n = n + 1
coroutine.yield(n)
end
end
end
function fsutils.move(oldname, newname)
os.rename(oldname, newname)
end
function fsutils.split(path)
local segments = {}
local pos = 1
while true do
local s, e = string.find(path, "[/\\]+", pos)
if not s then break end
table.insert(segments, string.sub(path, pos, s - 1))
pos = e + 1
end
table.insert(list, string.sub(path, pos))
if segments[#segments] == '' then
table.remove(segments)
end
return segments
end
function fsutils.normalize(path)
return table.concat(fsutils.split(path), PATHSEP)
end
function fsutils.normalize_posix(path)
return table.concat(fsutils.split(path), '/')
end
function fsutils.mkdir(path)
local segments = fsutils.split(path)
if system.mkdir then
local p = ""
for i = 1, #segments do
p = table.concat(segments, PATHSEP, 1, i)
end
if p == "" then
return nil, "path empty", p
end
local stat = system.get_file_info(p)
if stat and stat.type == "file" then
return nil, "path exists as a file", p
end
local ok, err = system.mkdir(p)
if not ok then
return nil, err, p
end
else
-- just wing it lol
system.exec(string.format(PLATFORM == "Windows" and "setlocal enableextensions & mkdir %q" or "mkdir -p %q", fsutils.normalize(path)))
end
end
local function async_exec(f, cb)
cb = cb or function() end
local co = coroutine.create(f)
local function resolve(...)
local ok, exec_body = coroutine.resume(co, ...)
if not ok then
error(debug.traceback(co, exec_body))
end
if coroutine.status(co) ~= "dead" then
exec_body(resolve)
end
end
resolve(cb)
end
local function prompt(text, suggest)
return coroutine.yield(function(resolve)
core.command_view:enter(text, resolve, suggest)
end)
end
command.add(nil, {
["files:delete"] = function()
async_exec(function()
local path = prompt("Delete", common.path_suggest)
core.add_thread(function()
-- we use a wrapping coroutine to get status
local function delete()
return coroutine.wrap(function() fsutils.delete(path, true) end)
end
for n in delete() do
if n % 100 == 0 then
core.log("Deleted %d items...", n)
coroutine.yield()
end
end
core.log("%q deleted.", path)
end)
end)
end,
["files:move"] = function()
async_exec(function()
local oldname = prompt("Move", common.path_suggest)
local newname = prompt("To", common.path_suggest)
fsutils.move(oldname, newname)
core.log("Moved %q to %q", oldname, newname)
end)
end
})
if not command.map["files:create-directory"] then
command.add(nil, {
["files:create-directory"] = function()
async_exec(function()
local path = prompt("Name", common.path_suggest)
fsutils.mkdir(path)
core.log("%q created.", path)
end)
end
})
end
return fsutils

View File

@ -1,68 +0,0 @@
local core = require "core"
local config = require "core.config"
local style = require "core.style"
local StatusView = require "core.statusview"
local git = {
branch = nil,
inserts = 0,
deletes = 0,
}
local function exec(cmd, wait)
local tempfile = core.temp_filename()
system.exec(string.format("%s > %q", cmd, tempfile))
coroutine.yield(wait)
local fp = io.open(tempfile)
local res = fp:read("*a")
fp:close()
os.remove(tempfile)
return res
end
core.add_thread(function()
while true do
if system.get_file_info(".git") then
-- get branch name
git.branch = exec("git rev-parse --abbrev-ref HEAD", 1):match("[^\n]*")
-- get diff
local line = exec("git diff --stat", 1):match("[^\n]*%s*$")
git.inserts = tonumber(line:match("(%d+) ins")) or 0
git.deletes = tonumber(line:match("(%d+) del")) or 0
else
git.branch = nil
end
coroutine.yield(config.project_scan_rate)
end
end)
local get_items = StatusView.get_items
function StatusView:get_items()
if not git.branch then
return get_items(self)
end
local left, right = get_items(self)
local t = {
style.dim, self.separator,
(git.inserts ~= 0 or git.deletes ~= 0) and style.accent or style.text,
git.branch,
style.dim, " ",
git.inserts ~= 0 and style.accent or style.text, "+", git.inserts,
style.dim, " / ",
git.deletes ~= 0 and style.accent or style.text, "-", git.deletes,
}
for _, item in ipairs(t) do
table.insert(right, item)
end
return left, right
end

View File

@ -1,25 +1,31 @@
local core = require "core"
local config = require "core.config"
local style = require "core.style"
local Doc = require "core.doc"
local DocView = require "core.docview"
config.motiontrail_steps = 50
config.motiontrail_steps = config.motiontrail_steps or 50
local function doc()
return core.active_view.doc
end
local function lerp(a, b, t)
return a + (b - a) * t
end
local function get_caret_rect(dv)
local line, col = dv.doc:get_selection()
local x, y = dv:get_line_screen_position(line)
x = x + dv:get_col_x_offset(line, col)
return x, y, style.caret_width, dv:get_line_height()
local function get_caret_rect(dv, idx)
local line1, col1, line2, col2 = doc():get_selection_idx(idx)
local x1, y1 = dv:get_line_screen_position(line1)
x1 = x1 + dv:get_col_x_offset(line1, col1)
return x1, y1, style.caret_width, dv:get_line_height()
end
local last_x, last_y, last_view
local last_x = {}
local last_y = {}
local last_view = {}
local draw = DocView.draw
@ -27,13 +33,16 @@ function DocView:draw(...)
draw(self, ...)
if self ~= core.active_view then return end
local x, y, w, h = get_caret_rect(self)
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
--if line1 == line2 and col1 == col2 then return false end
if last_view == self and (x ~= last_x or y ~= last_y) then
local x, y, w, h = get_caret_rect(self, idx)
if last_view[idx] == self and (x ~= last_x[idx] or y ~= last_y[idx]) then
local lx = x
for i = 0, 1, 1 / config.motiontrail_steps do
local ix = lerp(x, last_x, i)
local iy = lerp(y, last_y, i)
local ix = lerp(x, last_x[idx], i)
local iy = lerp(y, last_y[idx], i)
local iw = math.max(w, math.ceil(math.abs(ix - lx)))
renderer.draw_rect(ix, iy, iw, h, style.caret)
lx = ix
@ -41,6 +50,7 @@ function DocView:draw(...)
core.redraw = true
end
last_view, last_x, last_y = self, x, y
last_view[idx], last_x[idx], last_y[idx] = self, x, y
end
end

View File

@ -0,0 +1,30 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local command = require "core.command"
local translate = require "core.doc.translate"
local function split_lines(text)
local res = {}
for line in (text .. "\n"):gmatch("(.-)\n") do
table.insert(res, line)
end
return res
end
command.add("core.docview", {
["sort:sort"] = function()
local doc = core.active_view.doc
local l1, c1, l2, c2, swap = doc:get_selection(true)
l1, c1 = translate.start_of_line(doc, l1, c1)
l2, c2 = translate.end_of_line(doc, l2, c2)
doc:set_selection(l1, c1, l2, c2, swap)
doc:replace(function(text)
local head, body, foot = text:match("(\n*)(.-)(\n*)$")
local lines = split_lines(body)
table.sort(lines, function(a, b) return a:lower() < b:lower() end)
return head .. table.concat(lines, "\n") .. foot
end)
end,
})

View File

@ -0,0 +1,389 @@
local core = require "core"
local common = require "core.common"
local command = require "core.command"
local config = require "core.config"
local keymap = require "core.keymap"
local style = require "core.style"
local View = require "core.view"
local TodoTreeView = View:extend()
config.todo_tags = { --"TODO", "BUG", "FIX", "FIXME", "IMPROVEMENT",
"@todo", "@fixme", "@testme", "@leak" } --< @r-lyeh
-- Paths or files to be ignored
config.todo_ignore_paths = {
"tools/tcc", --< @r-lyeh
"tools\\tcc", --< @r-lyeh
"engine/fwk", --< @r-lyeh
"engine\\fwk", --< @r-lyeh
"engine/joint", --< @r-lyeh
"engine\\joint", --< @r-lyeh
}
-- 'tag' mode can be used to group the todos by tags
-- 'file' mode can be used to group the todos by files
config.todo_mode = "tag"
-- Tells if the plugin should start with the nodes expanded. default: true for tag mode
config.todo_expanded = config.todo_mode == "tag"
-- list of allowed extensions: items must start and end with a dot character
config.todo_allowed_extensions = '.h.c.m.hh.cc.hpp.cpp.cxx.lua.py.cs.vs.fs.bat.' --< @r-lyeh
-- whether the sidebar treeview is initially visible or not
config.todo_visible = false
function TodoTreeView:new()
TodoTreeView.super.new(self)
self.scrollable = true
self.focusable = false
self.visible = config.todo_visible
self.times_cache = {}
self.cache = {}
self.cache_updated = false
self.init_size = true
-- Items are generated from cache according to the mode
self.items = {}
end
local function is_file_ignored(filename)
for _, path in ipairs(config.todo_ignore_paths) do
local s, _ = filename:find(path)
if s then
return true
end
end
return false
end
function TodoTreeView:refresh_cache()
local items = {}
if not next(self.items) then
items = self.items
end
self.updating_cache = true
core.add_thread(function()
for _, item in ipairs(core.project_files) do
local ignored = is_file_ignored(item.filename)
if not ignored and item.type == "file" then
local cached = self:get_cached(item)
if config.todo_mode == "file" then
items[cached.filename] = cached
else
for _, todo in ipairs(cached.todos) do
local tag = todo.tag
if not items[tag] then
local t = {}
t.expanded = config.todo_expanded
t.type = "group"
t.todos = {}
t.tag = tag
items[tag] = t
end
table.insert(items[tag].todos, todo)
end
end
end
end
-- Copy expanded from old items
if config.todo_mode == "tag" and next(self.items) then
for tag, data in pairs(self.items) do
if items[tag] then
items[tag].expanded = data.expanded
end
end
end
self.items = items
core.redraw = true
self.cache_updated = true
self.updating_cache = false
end, self)
end
local function find_file_todos(t, filename)
--< @r-lyeh
local ext = (filename:match "[^.]+$") .. '.'
if not string.find(config.todo_allowed_extensions,ext) then
return
end
--<
local fp = io.open(filename)
if not fp then return t end
--< @r-lyeh: optimized loops: early exit if quicksearch fails
local function lines(str)
local result = {}
for line in string.gmatch(str, "(.-)%c") do -- line in str:gmatch '[^\n]+' do
-- Add spaces at the start and end of line so the pattern will pick
-- tags at the start and at the end of lines
table.insert(result, " "..line.." ")
end
return result
end
local before = #t
local content = fp:read("*all")
for _, todo_tag in ipairs(config.todo_tags) do
if string.find(content, todo_tag) then
local n = 0
for _, line in ipairs(lines(content)) do
n = n + 1
local match_str = todo_tag[1] == '@' and todo_tag or "[^a-zA-Z_\"'`]"..todo_tag.."[^a-zA-Z_\"'`]+"
local s, e = line:find(match_str)
if s then
local d = {}
d.tag = string.sub(string.upper(todo_tag), todo_tag:byte(1) == 64 and 2 or 1) .. 's'
d.filename = filename
d.text = line:sub(e+1)
if d.text == "" then
d.text = config.todo_mode == "tag" and filename:match("^.+[/\\](.+)$") or "blank"
end
d.line = n
d.col = s
table.insert(t, d)
end
end
end
end
fp:close()
if #t ~= before then
coroutine.yield()
core.redraw = true
end
--<
end
function TodoTreeView:get_cached(item)
local t = self.cache[item.filename]
if not t then
t = {}
t.expanded = config.todo_expanded
t.filename = item.filename
t.abs_filename = system.absolute_path(item.filename)
t.type = item.type
t.todos = {}
find_file_todos(t.todos, t.filename)
self.cache[t.filename] = t
end
return t
end
function TodoTreeView:get_name()
return "Todo Tree"
end
function TodoTreeView:get_item_height()
return style.font:get_height() + style.padding.y
end
function TodoTreeView:get_cached_time(doc)
local t = self.times_cache[doc]
if not t then
local info = system.get_file_info(doc.filename)
if not info then return nil end
self.times_cache[doc] = info.modified
end
return t
end
function TodoTreeView:check_cache()
for _, doc in ipairs(core.docs) do
if doc.filename then
local info = system.get_file_info(doc.filename)
local cached = self:get_cached_time(doc)
if not info and cached then
-- document deleted
self.times_cache[doc] = nil
self.cache[doc.filename] = nil
self.cache_updated = false
elseif cached and cached ~= info.modified then
-- document modified
self.times_cache[doc] = info.modified
self.cache[doc.filename] = nil
self.cache_updated = false
end
end
end
if core.project_files ~= self.last_project_files then
self.last_project_files = core.project_files
self.cache_updated = false
end
end
function TodoTreeView:each_item()
self:check_cache()
if not self.updating_cache and not self.cache_updated then
self:refresh_cache()
end
return coroutine.wrap(function()
local ox, oy = self:get_content_offset()
local y = oy + style.padding.y
local w = self.size.x
local h = self:get_item_height()
for _, item in pairs(self.items) do
if #item.todos > 0 then
coroutine.yield(item, ox, y, w, h)
y = y + h
for _, todo in ipairs(item.todos) do
if item.expanded then
coroutine.yield(todo, ox, y, w, h)
y = y + h
end
end
end
end
end)
end
function TodoTreeView:on_mouse_moved(px, py)
self.hovered_item = nil
for item, x,y,w,h in self:each_item() do
if px > x and py > y and px <= x + w and py <= y + h then
self.hovered_item = item
break
end
end
end
function TodoTreeView:on_mouse_pressed(button, x, y)
if not self.hovered_item then
return
elseif self.hovered_item.type == "file"
or self.hovered_item.type == "group" then
self.hovered_item.expanded = not self.hovered_item.expanded
else
core.try(function()
local i = self.hovered_item
local dv = core.root_view:open_doc(core.open_doc(i.filename))
core.root_view.root_node:update_layout()
dv.doc:set_selection(i.line, i.col)
dv:scroll_to_line(i.line, false, true)
end)
end
end
function TodoTreeView:update()
self.scroll.to.y = math.max(0, self.scroll.to.y)
-- update width
local dest = self.visible and config.treeview_size or 0
if self.init_size then
self.size.x = dest
self.init_size = false
else
self:move_towards(self.size, "x", dest)
end
TodoTreeView.super.update(self)
end
function TodoTreeView:draw()
self:draw_background(style.background2)
--local h = self:get_item_height()
local icon_width = style.icon_font:get_width("D")
local spacing = style.font:get_width(" ") * 2
local root_depth = 0
for item, x,y,w,h in self:each_item() do
local color = style.text
-- hovered item background
if item == self.hovered_item then
renderer.draw_rect(x, y, w, h, style.line_highlight)
color = style.accent
end
-- icons
local item_depth = 0
x = x + (item_depth - root_depth) * style.padding.x + style.padding.x
if item.type == "file" then
local icon1 = item.expanded and "-" or "+"
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
x = x + style.padding.x
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
x = x + icon_width
elseif item.type == "group" then
local icon1 = item.expanded and "-" or "+"
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
x = x + icon_width / 2
else
if config.todo_mode == "tag" then
x = x + style.padding.x
else
x = x + style.padding.x * 1.5
end
common.draw_text(style.icon_font, color, "i", nil, x, y, 0, h)
x = x + icon_width
end
-- text
x = x + spacing
if item.type == "file" then
common.draw_text(style.font, color, item.filename, nil, x, y, 0, h)
elseif item.type == "group" then
common.draw_text(style.font, color, item.tag, nil, x, y, 0, h)
else
if config.todo_mode == "file" then
common.draw_text(style.font, color, item.tag.." - "..item.text, nil, x, y, 0, h)
else
common.draw_text(style.font, color, item.text, nil, x, y, 0, h)
end
end
end
end
-- init
local view = TodoTreeView()
local node = core.root_view:get_active_node()
view.size.x = config.treeview_size
node:split("right", view, true)
-- register commands and keymap
command.add(nil, {
["todotreeview:toggle"] = function()
view.visible = not view.visible
end,
["todotreeview:expand-items"] = function()
for _, item in pairs(view.items) do
item.expanded = true
end
end,
["todotreeview:hide-items"] = function()
for _, item in pairs(view.items) do
item.expanded = false
end
end,
})
keymap.add { ["ctrl+shift+t"] = "todotreeview:toggle" }
keymap.add { ["ctrl+shift+e"] = "todotreeview:expand-items" }
keymap.add { ["ctrl+shift+h"] = "todotreeview:hide-items" }

View File

@ -22,7 +22,7 @@ local TreeView = View:extend()
function TreeView:new()
TreeView.super.new(self)
self.scrollable = true
self.visible = true
self.visible = false --< @r-lyeh true>false
self.init_size = true
self.cache = {}
end
@ -185,7 +185,7 @@ end
-- init
local view = TreeView()
local node = core.root_view:get_active_node()
node:split("left", view, true) --< @r-lyeh true > false allows resizable treeview
node:split("left", view, true)
-- register commands and keymap
command.add(nil, {
@ -195,3 +195,100 @@ command.add(nil, {
})
keymap.add { ["ctrl+t"] = "treeview:toggle" } --< @r-lyeh ctrl+// > ctrl+t
-- register some context menu items, if available
local has_menu, menu = core.try(require, "plugins.contextmenu")
local has_fsutils, fsutils = core.try(require, "plugins.fsutils")
if has_menu and has_fsutils then
local function new_file_f(path)
command.perform "core:new-doc"
end
local function new_file()
new_file_f(view.hovered_item.abs_filename)
end
local function new_dir_f(path)
core.command_view:enter("New directory name", function(dir)
fsutils.mkdir(dir)
end)
core.command_view:set_text(path .. PATHSEP .. "New folder")
end
local function new_dir()
new_dir_f(view.hovered_item.abs_filename)
end
local function delete_f(path)
core.add_thread(function()
local function wrap()
return coroutine.wrap(function() fsutils.delete(path, true) end)
end
for n in wrap() do
if n % 100 == 0 then
core.log("Deleted %d items.", n)
coroutine.yield(0)
end
end
core.log("%q deleted.", path)
end)
end
local function delete()
local path = view.hovered_item.abs_filename
if view.hovered_item.type == "dir"
and system.show_confirm_dialog("Delete confirmation", string.format("Do you really want to delete %q ?", path)) then
delete_f(path)
else
delete_f(path)
end
end
local function dirname(path)
local p = fsutils.split(path)
table.remove(p)
return table.concat(p, PATHSEP)
end
local function rename()
local oldname = view.hovered_item.abs_filename
core.command_view:enter("Rename to", function(newname)
fsutils.move(oldname, newname)
core.log("Moved %q to %q", oldname, newname)
end, common.path_suggest)
core.command_view:set_text(dirname(oldname))
end
local function copy_path()
system.set_clipboard(view.hovered_item.abs_filename)
end
menu:register(function() return view.hovered_item and view.hovered_item.type == "dir" end, {
{ text = "New file", command = new_file },
{ text = "New folder", command = new_dir },
menu.DIVIDER,
{ text = "Rename", command = rename },
{ text = "Delete", command = delete },
menu.DIVIDER,
{ text = "Copy directory name", command = copy_path }
})
menu:register(function() return view.hovered_item and view.hovered_item.type == "file" end, {
{ text = "Rename", command = rename },
{ text = "Delete", command = delete },
menu.DIVIDER,
{ text = "Copy filename", command = copy_path }
})
-- general region of the treeview
menu:register(function(x, y)
local x1, y1, x2, y2 = view:get_content_bounds()
return not view.hovered_item and x > x1 and x <= x2 and y > y1 and y <= y2
end, {
{ text = "New file", command = function() new_file_f(system.absolute_path('.')) end },
{ text = "New folder", command = function() new_dir_f(system.absolute_path('.')) end }
})
end
return view --< @r-lyeh

View File

@ -1,82 +0,0 @@
#ifdef ICON
// 2 states
ICON(DONE),ICON(DONE_ALL),
ICON(CHECK_BOX),ICON(CHECK_BOX_OUTLINE_BLANK),
ICON(VISIBILITY),ICON(VISIBILITY_OFF),
ICON(NOTIFICATIONS),ICON(NOTIFICATIONS_OFF),
ICON(TOGGLE_ON),ICON(TOGGLE_OFF),
ICON(LIGHTBULB),ICON(LIGHTBULB_OUTLINE),
ICON(FULLSCREEN),ICON(FULLSCREEN_EXIT),
ICON(VIDEOCAM),ICON(VIDEOCAM_OFF),
ICON(FIBER_MANUAL_RECORD),ICON(FIBER_SMART_RECORD),
ICON(EXPAND_MORE),ICON(EXPAND_LESS),
ICON(SPORTS_ESPORTS),ICON(VIDEOGAME_ASSET),ICON(GAMEPAD),//ICON(STADIA_CONTROLLER),
ICON(FILE_DOWNLOAD),ICON(FILE_UPLOAD),
// 3 states
ICON(FOLDER_OPEN),ICON(CREATE_NEW_FOLDER),ICON(FOLDER_SPECIAL),
ICON(CONTENT_CUT),ICON(CONTENT_COPY),ICON(CONTENT_PASTE),
ICON(STAR),ICON(STAR_HALF),ICON(STAR_OUTLINE),
ICON(VOLUME_DOWN),ICON(VOLUME_UP),ICON(VOLUME_OFF),
// 6 states
ICON(FAST_REWIND),ICON(SKIP_PREVIOUS),ICON(PLAY_ARROW),ICON(SKIP_NEXT),ICON(FAST_FORWARD),ICON(REPEAT),
// 12 states
ICON(POWER),ICON(BATTERY_CHARGING_FULL),ICON(BATTERY_FULL),ICON(BATTERY_6_BAR),ICON(BATTERY_5_BAR),ICON(BATTERY_4_BAR),ICON(BATTERY_3_BAR),ICON(BATTERY_2_BAR),ICON(BATTERY_1_BAR),ICON(BATTERY_0_BAR),ICON(BATTERY_ALERT),
ICON(MOVIE),
ICON(CAMERA),ICON(PHOTO_CAMERA),
ICON(SPEED),
ICON(ROOM),
ICON(PUSH_PIN),
ICON(FLAG),
ICON(G_TRANSLATE),
ICON(SETTINGS),
ICON(CLOSED_CAPTION),
ICON(BUILD),
ICON(ROCKET_LAUNCH),
ICON(TODAY),ICON(EVENT_NOTE),
ICON(3D_ROTATION),
ICON(LAUNCH),
ICON(SEARCH),
ICON(TIMELAPSE),
ICON(ARROW_DOWNWARD),
ICON(CALL_MERGE),
ICON(ARCHIVE),
ICON(SHOW_CHART),
ICON(WARNING),
ICON(CREATE),ICON(ADD),
ICON(TEXT_FORMAT),
ICON(CHECK),
ICON(SAVE),
ICON(CANCEL),
ICON(DELETE),
ICON(CLOSE),
ICON(REFRESH),
ICON(SYNC),
ICON(HIGHLIGHT_OFF),
ICON(SYSTEM_UPDATE),
ICON(UNDO),ICON(REDO),
ICON(CLASS),
ICON(TITLE),
ICON(HD),
ICON(VIEW_QUILT),
ICON(FINGERPRINT),
ICON(VPN_KEY),
ICON(FACE),ICON(PERSON),
ICON(CHAT_BUBBLE),
ICON(COPYRIGHT),
#undef ICON
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,404 +0,0 @@
#define COOK_ON_DEMAND 1 // @fixme: these directives should be on client, not within v4k.dll
#define ENABLE_AUTOTESTS 1
#define V4K_IMPLEMENTATION
#include "v4k.h"
#include "objtests.h"
// ----------------------------------------------------------------------------
TODO("file_id: glow.hdr vs glow.png");
TODO("reflect: iterate components+metas on REFLECT too, so they're properly saved/loaded");
TODO("edit: tree nav");
TODO("edit: keys up,down,left,right -> move selection");
TODO("edit: reordering/dragging items on a list. ctrl+cursors");
TODO("edit: tab -> cycle next node of matching highlighted type");
TODO("edit: ^C^V^X thru clipboard. ^C to serialize to clipboard.");
TODO("edit: ^Z^Y cursor too. also fix undo ordering");
TODO("edit: ^S^L^N. load/save as filesystems");
TODO("edit: ^B(toolbar)");
TODO("edit: ^E prop single-view for multiple selections: should inspect common fields only");
TODO("edit: two-column way (or Nth) for multiple selections");
TODO("edit: tab/caps view, world + entity only, obj printf");
TODO("edit: obj bounds, obj picking, obj collisions");
TODO("edit: LMB object picking, RMB object serialization + log, floating ICONS bulb light");
TODO("edit: worldtraveller component");
TODO("edit: levelstreamer component");
TODO("edit: OSC server/client port 2023");
TODO("edit: add/rem entities, add/rem components, add/rem/pause/resume systems");
TODO("edit: event loop: tick,draw*,spawn,delete,un/load from bvh stream,");
TODO("edit: overlay scene editor");
TODO("edit: overlay0 artwork");
TODO("edit: overlay1 world editor: gizmo, grid, snap, splats (@todo: fixed screen size gizmos)");
TODO("edit: overlay2 script editor");
TODO("edit: overlay3 track, spline, keys editor");
TODO("edit: overlay4 node editor (shader/anim/bt/hfsm/material/audio/blueprints)");
TODO("edit: overlay5 csv editor");
TODO("edit: overlay6 bug/task editor");
TODO("gfx: tree traversal from game");
TODO("gfx: bvh and collision queries");
TODO("gfx: visibility and pvs queries");
TODO("obj: finish SYSTEMS and join queries");
TODO("obj: make it work with /GL flag (VC)");
TODO("obj: impl obj_mutate() ... deprecate?");
TODO("obj: make() from mpack(m) + native(n)");
TODO("obj: make() should save schema `[{mn`+version. and (m)pack and (n)ative should start with objtype");
TODO("obj: super()");
TODO("obj: lock()/trylock()/unlock()/barrier(N)");
TODO("obj: diff()/patch()");
TODO("obj: free obj_children()/payload");
TODO("obj: free obj_components()/payload2");
TODO("pack: mp2json, json2mp");
TODO("pack: simplify msgpack API, make it growth similar to va()")
#if 0 // v4k_pack proposal
static __thread char* mpin;
static __thread unsigned mpinlen;
static __thread char mpinbuf[256];
static __thread char* mpout;
static __thread unsigned mpoutlen;
static __thread char mpoutbuf[256];
#define PACKMSG(...) (msgpack_new(mpin = mpinbuf, mpinlen = sizeof(mpinbuf)), mpinlen = msgpack(__VA_ARGS__), cobs_encode(mpin, mpinlen, mpout = mpoutbuf, mpoutlen = cobs_bounds(mpinlen)), mpout)
#define UNPACKMSG(ptr,fmt,...) (mpin = (char*)ptr, mpinlen = strlen(ptr), mpout = mpoutbuf, mpoutlen = sizeof(mpoutbuf), mpoutlen = cobs_decode(mpin, mpinlen, mpout, mpoutlen), msgunpack_new(mpout, mpoutlen) && msgunpack(fmt, __VA_ARGS__))
#endif
#include "3rd_icon_mdi.h"
#include "v4k_editor.h"
void editor_gizmos(int dim) {
// debugdraw
if(dim == 2) ddraw_push_2d();
ddraw_ontop_push(0);
// draw gizmos, aabbs, markers, etc
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( !*selected ) continue;
void *obj = *o;
// get transform
vec3 *p = NULL;
vec3 *r = NULL;
vec3 *s = NULL;
aabb *a = NULL;
for each_objmember(obj,TYPE,NAME,PTR) {
/**/ if( !strcmp(NAME, "position") ) p = PTR;
else if( !strcmp(NAME, "pos") ) p = PTR;
else if( !strcmp(NAME, "rotation") ) r = PTR;
else if( !strcmp(NAME, "rot") ) r = PTR;
else if( !strcmp(NAME, "scale") ) s = PTR;
else if( !strcmp(NAME, "sca") ) s = PTR;
else if( !strcmp(NAME, "aabb") ) a = PTR;
}
ddraw_ontop(0);
// bounding box 3d
if( 0 ) {
aabb box;
if( obj_hasmethod(*o, aabb) && obj_aabb(*o, &box) ) {
ddraw_color_push(YELLOW);
ddraw_aabb(box.min, box.max);
ddraw_color_pop();
}
}
// position marker
if( p ) {
static map(void*, vec3) prev_dir = 0;
do_once map_init_ptr(prev_dir);
vec3* dir = map_find_or_add(prev_dir, obj, vec3(1,0,0));
static map(void*, vec3) prev_pos = 0;
do_once map_init_ptr(prev_pos);
vec3* found = map_find_or_add(prev_pos, obj, *p), fwd = sub3(*p, *found);
if( (fwd.y = 0, len3sq(fwd)) ) {
*found = *p;
*dir = norm3(fwd);
}
// float diameter = len2( sub2(vec2(box->max.x,box->max.z), vec2(box->min.x,box->min.z) ));
// float radius = diameter * 0.5;
ddraw_position_dir(*p, *dir, 1);
}
ddraw_ontop(1);
// transform gizmo
if( p && r && s ) {
gizmo(p,r,s);
}
}
ddraw_ontop_pop();
if(dim == 2) ddraw_pop_2d();
}
// ----------------------------------------------------------------------------
// demo
typedef struct lit { OBJ
vec3 pos;
vec3 dir;
int type;
} lit;
int lit_aabb(lit *obj, aabb *box) {
*box = aabb( vec3(obj->pos.x-16,obj->pos.y-16,0), vec3(obj->pos.x+16,obj->pos.y+16,1) );
return 1;
}
const char *lit_icon(lit *obj) {
const char *icon =
obj->type == 0 ? ICON_MD_WB_IRIDESCENT :
obj->type == 1 ? ICON_MD_WB_INCANDESCENT :
obj->type == 2 ? ICON_MD_FLARE :
obj->type == 3 ? ICON_MD_WB_SUNNY : "";
return icon;
}
int lit_edit(lit *obj) {
const char *all_icons =
ICON_MD_WB_IRIDESCENT
ICON_MD_WB_INCANDESCENT
ICON_MD_FLARE
ICON_MD_WB_SUNNY
ICON_MD_LIGHT_MODE
ICON_MD_LIGHT
ICON_MD_FLASHLIGHT_OFF
ICON_MD_FLASHLIGHT_ON
ICON_MD_HIGHLIGHT
ICON_MD_HIGHLIGHT_ALT
ICON_MD_LIGHTBULB
ICON_MD_LIGHTBULB_OUTLINE
ICON_MD_NIGHTLIGHT
ICON_MD_NIGHTLIGHT_ROUND
// MDI
ICON_MDI_LIGHTBULB_ON_OUTLINE // generic
ICON_MDI_WALL_SCONCE_ROUND //
ICON_MDI_WALL_SCONCE_FLAT // emissive
ICON_MDI_CEILING_LIGHT // spotlight
ICON_MDI_TRACK_LIGHT // spotlight
ICON_MDI_WEATHER_SUNNY // directional
ICON_MDI_LIGHTBULB_FLUORESCENT_TUBE_OUTLINE
;
// editor_symbol(obj->pos.x+16,obj->pos.y-32,all_icons);
if( editor_selected(obj) ) {
obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT);
obj->pos.y += input(KEY_DOWN) - input(KEY_UP);
obj->type = (obj->type + !!input_down(KEY_SPACE)) % 4;
}
editor_symbol(obj->pos.x,obj->pos.y,lit_icon(obj));
return 1;
}
OBJTYPEDEF(lit,200);
AUTORUN {
STRUCT(lit, vec3, pos);
STRUCT(lit, vec3, dir);
STRUCT(lit, int, type);
EXTEND(lit, edit,icon,aabb);
}
typedef struct kid { OBJ
int kid;
vec2 pos;
vec2 vel;
float angle;
vec4 color;
int controllerid;
// --- private
char *filename;
unsigned rgba_;
texture_t texture_;
} kid;
void kid_ctor(kid *obj) {
obj->kid = randi(0,3);
obj->pos = vec2(randi(0, window_width()), randi(0, window_height()));
obj->vel.x = obj->vel.y = 100 + 200 * randf();
obj->controllerid = randi(0,3);
obj->texture_ = texture(obj->filename, TEXTURE_RGBA|TEXTURE_LINEAR);
obj->rgba_ = rgbaf( obj->color.x/255.0, obj->color.y/255.0, obj->color.z/255.0, obj->color.w/255.0 );
}
void kid_tick(kid *obj, float dt) {
// add velocity to position
vec2 off = vec2( input(KEY_RIGHT)-input(KEY_LEFT), input(KEY_DOWN)-input(KEY_UP) );
obj->pos = add2(obj->pos, scale2(mul2(obj->vel, off), dt * (obj->controllerid == 0)));
// wrap at screen boundaries
const int appw = window_width(), apph = window_height();
if( obj->pos.x < 0 ) obj->pos.x += appw; else if( obj->pos.x > appw ) obj->pos.x -= appw;
if( obj->pos.y < 0 ) obj->pos.y += apph; else if( obj->pos.y > apph ) obj->pos.y -= apph;
}
void kid_draw(kid *obj) {
// 4x4 tilesheet
int col = (((int)obj->kid) % 4);
int row = (((int)obj->pos.x / 10 ^ (int)obj->pos.y / 10) % 4);
float position[3] = {obj->pos.x,obj->pos.y,obj->pos.y}; // position(x,y,depth: sort by Y)
float offset[2]={0,0}, scale[2]={1,1};
float coords[3]={col * 4 + row,4,4}; // num_frame(x) in a 4x4(y,z) spritesheet
sprite_sheet(obj->texture_, coords, position, obj->angle*TO_DEG, offset, scale,
0, obj->rgba_, 0); // is_additive, tint color, resolution independant
}
int kid_aabb(kid *obj, aabb *box) {
*box = aabb( vec3(obj->pos.x-16,obj->pos.y-16,0), vec3(obj->pos.x+16,obj->pos.y+16,1) );
return 1;
}
int kid_edit(kid *obj) {
aabb box;
if( kid_aabb(obj, &box) ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(box.min, box.max);
ddraw_pop_2d();
ddraw_color_pop();
}
if( editor_selected(obj) ) {
obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT);
obj->pos.y += input(KEY_DOWN) - input(KEY_UP);
editor_symbol(obj->pos.x+16,obj->pos.y-16,ICON_MD_VIDEOGAME_ASSET);
}
return 1;
}
void kid_menu(kid *obj, const char *argv) {
ui_label("Private section");
ui_color4("Color_", &obj->color.x);
ui_texture("Texture_", obj->texture_);
ui_separator();
obj->rgba_ = rgbaf( obj->color.x/255.0, obj->color.y/255.0, obj->color.z/255.0, obj->color.w/255.0 );
}
OBJTYPEDEF(kid,201);
AUTORUN {
// reflect
STRUCT(kid, int, kid);
STRUCT(kid, vec2, pos);
STRUCT(kid, vec2, vel);
STRUCT(kid, float, angle, "Tilt degrees");
STRUCT(kid, vec4, color, "Tint color");
STRUCT(kid, char*, filename, "Filename" );
EXTEND(kid, ctor,tick,draw,aabb,edit,menu);
}
void game(unsigned frame, float dt, double t) {
static kid *root;
static kid *o1;
static kid *o2;
static camera_t cam;
if( !frame ) {
// init camera (x,y) (z = zoom)
cam = camera();
cam.position = vec3(window_width()/2,window_height()/2,1);
camera_enable(&cam);
root = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=5,2\n"
"angle=pi/12\n"
"color=255, 255, 192, 255\n"
);
o1 = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=1,100\n"
"angle=pi/12\n"
"color=255, 192, 192, 255\n"
);
o2 = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=50,200\n"
"angle=pi/12\n"
"color=192, 192, 255, 255\n"
);
//obj_setname(root, "root");
obj_setname(o1, "o1");
obj_setname(o2, "o2");
obj*o3 = obj_make(
"[lit]\n"
"pos=300,300,0\n"
"type=1"
);
obj*o4 = obj_new_ext(obj, "o4");
obj*o5 = obj_new_ext(obj, "o5");
obj_attach(root, o1);
obj_attach(o1, o2);
obj_attach(o2, o3);
obj_attach(o1, o4);
obj_attach(root, o5);
editor_watch(root);
}
// camera panning (x,y) & zooming (z)
if(0)
if( !ui_hover() && !ui_active() ) {
if( input(MOUSE_L) ) cam.position.x -= input_diff(MOUSE_X);
if( input(MOUSE_L) ) cam.position.y -= input_diff(MOUSE_Y);
cam.position.z += input_diff(MOUSE_W) * 0.1; // cam.p.z += 0.001f; for tests
}
// tick game
if( dt ) {
kid_tick(root, dt);
kid_tick(o1, dt);
kid_tick(o2, dt);
root->angle = 5 * sin(t+dt);
}
// fps camera
bool active = 0;
if( input_down(MOUSE_M) || input_down(MOUSE_R) ) {
active = ui_hover() || ui_active() || gizmo_active() || editor_first_selected() ? false : true;
} else {
active = !window_has_cursor() && (input(MOUSE_M) || input(MOUSE_R));
}
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_moveby(&cam, wasdecq);
camera_fps(&cam, mouse.x,mouse.y);
// draw world
ddraw_ontop_push(0);
ddraw_grid(0);
ddraw_ontop_pop();
ddraw_flush();
// draw game
kid_draw(root);
kid_draw(o1);
kid_draw(o2);
}
int main(){
window_title("Editor " EDITOR_VERSION);
window_create(flag("--transparent") ? 101 : 80,0);
window_icon("scale-ruler-icon.png");
while( window_swap() ) {
editor_frame(game);
editor_gizmos(2);
}
}

View File

@ -26,13 +26,8 @@ int boy_tick(boy* self) { printf("%p boy tick, hp:%f\n", self, self->hp); retu
char* boy_save(boy *self) { return obj_saveini(self); } // PACKMSG("ssf", "boy_v1", self->name, self->hp); }
AUTOTEST {
obj_extend(orc, ctor);
obj_extend(orc, tick);
obj_extend(orc, save);
obj_extend(boy, ctor);
obj_extend(boy, tick);
obj_extend(boy, save);
EXTEND(orc, ctor,tick,save);
EXTEND(boy, ctor,tick,save);
// instance gameobjs

View File

@ -0,0 +1,297 @@
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#if 1
#define V4K_IMPLEMENTATION
#include "/prj/v4k/engine/joint/v4k.h"
#define vl tempvl
#define test2 test2_
#else
#define vl(fmt, list) fmt // @fixme
static unsigned array_c;
#define array(t) t*
#define array_resize(arr, c) ( (arr) = vrealloc((arr), (c) * sizeof(0[arr])) )
#define array_push(arr, v) ( array_c = array_count(arr), array_c[(arr) = vrealloc((arr), (array_c + 1) * sizeof(0[arr]))] = (v) )
#define array_pop(arr) ( (arr) ? (arr) = vrealloc((arr), (array_count(arr)-1) * sizeof(0[arr])) : (0) )
#define array_count(arr) (int)( (arr) ? vlen(arr) / sizeof(0[arr]) : sizeof(0[arr]) - sizeof(0[arr]) )
#define array_free(arr) ( (arr) ? (vrealloc(arr, 0), 1) : 0 )
void* vrealloc( void* p, size_t sz ) {
if( !sz ) {
if( p ) {
size_t *ret = (size_t*)p - 2;
ret[0] = 0;
ret[1] = 0;
realloc( ret, 0 );
}
return 0;
} else {
size_t *ret;
if( !p ) {
ret = (size_t*)realloc( 0, sizeof(size_t) * 2 + sz );
ret[0] = sz;
ret[1] = 0;
} else {
ret = (size_t*)p - 2;
size_t osz = ret[0];
size_t ocp = ret[1];
if( sz <= (osz + ocp) ) {
ret[0] = sz;
ret[1] = ocp - (sz - osz);
} else {
ret = (size_t*)realloc( ret, sizeof(size_t) * 2 + sz * 1.75 );
ret[0] = sz;
ret[1] = (size_t)(sz * 1.75) - sz;
}
}
return &ret[2];
}
}
size_t vlen( void* p ) {
return p ? 0[ (size_t*)p - 2 ] : 0;
}
#endif
typedef struct FILE2 {
FILE *fp;
int64_t pos;
int errno_;
array(char) arr;
} FILE2;
void *tmpfile2(void) {
FILE2 *mem = (FILE2*)calloc(1, sizeof(FILE2));
mem->fp = tmpfile();
return mem;
}
void *fopen2(void *buf, const char *mode) {
FILE2 *mem = (FILE2*)calloc(1, sizeof(FILE2));
if( mode[0] != 'm' && buf ) {
mem->fp = fopen((const char *)buf, mode);
}
return (FILE*)mem;
}
int fclose2(void* handler) {
FILE2 *mem = (FILE2*)handler;
if( mem->arr ) {
array_free(mem->arr);
}
if( mem->fp ) {
fclose(mem->fp);
}
memset(mem, 0, sizeof(FILE2));
free(mem);
return 0;
}
// r/w
int fwrite2(const void *buf, int size, int count, void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return fwrite(buf, size, count, mem->fp);
size *= count;
size_t available = array_count(mem->arr) - mem->pos;
if( size > available ) {
if( 1 ) { // if growthable
array_resize(mem->arr, size - available);
} else {
size = available;
}
}
memcpy(mem->arr, buf, size);
mem->pos += size;
return count;
}
int fread2(void *buf, int size, int count, void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return fread(buf, size, count, mem->fp);
size_t available = array_count(mem->arr) - mem->pos;
size_t total = size * count;
while( (total/size) > count ) {
total -= size;
}
if( total > 0 ) {
memcpy(buf, mem->arr, total);
mem->pos += total;
return total / size;
}
return 0;
}
// cursor
int64_t ftello2(void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return ftell(mem->fp);
return mem->pos;
}
int64_t fseeko2(void *handler, int64_t offset, int whence) {
FILE2 *mem = handler;
if( mem->fp ) return fseek(mem->fp, offset, whence);
/**/ if( whence == SEEK_SET ) mem->pos = offset;
else if( whence == SEEK_CUR ) mem->pos += offset;
else if( whence == SEEK_END ) mem->pos = array_count(mem->arr) + offset; // - 1 - offset;
else return -1;
if( mem->pos >= array_count(mem->arr) ) {
return -1;
}
return mem->pos; // (fpos_t)mem->pos;
}
// buffering
void setbuf2(void *handler, char *buffer) {
FILE2 *mem = handler;
if( mem->fp ) setbuf(mem->fp, buffer);
}
int setvbuf2(void *handler, char *buffer, int mode, size_t size) {
FILE2 *mem = handler;
if( mem->fp ) return setvbuf2(mem->fp, buffer, mode, size);
return 0;
}
int fflush2(void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return fflush(mem->fp);
return 0;
}
int ungetc2(int ch, void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return ungetc(ch, mem->fp);
#if 0
// todo:
pushes the uint8_t character onto the specified stream so that
it's available for the next read operation.
#endif
return -1;
}
// internal
array(char)* fdata(void *handler) {
FILE2 *mem = handler;
if( mem->fp ) return NULL;
return &mem->arr;
}
#define FILE void
#define tmpfile tmpfile2
#define fopen fopen2
#define ftello ftello2
#define ftell (int)ftello2
#define fgetpos fgetpos2
#define fseeko fseeko2
#define fseek (int)fseeko2
#define fsetpos fsetpos2
#define rewind rewind2
#define fread fread2
#define fgets fgets2
#define fgetc fgetc2
#define getc getc2
#define fwrite fwrite2
#define fputs fputs2
#define fputc fputc2
#define putc putc2
#define ferror ferror2
#define feof feof2
#define clearerr clearerr2
#define vfprintf vfprintf2 // @fixme: return type
#define fprintf fprintf2
#define setbuf setbuf2
#define setvbuf setvbuf2
#define fflush fflush2
#define ungetc ungetc2
#define fclose fclose2
// not doing:
#define freopen(filename,mode,handler) freopen(filename,mode, ((FILE2*)(handler))->fp )
#define freopen(filename,mode,handler) freopen(filename,mode, ((FILE2*)(handler))->fp )
#define fscanf(handler,format,...) fscanf(((FILE2*)(handler))->fp, format, __VA_ARGS__)
// format
// int fscanf(FILE *fp, const char *format, ...);
int vfprintf(FILE *fp, const char *format, va_list args) { // @fixme: return type
const char *str = vl(format, args);
return fwrite(str, strlen(str), 1, fp) == 1;
}
int fprintf(FILE *fp, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int rc = vfprintf(fp, fmt, va);
va_end(va);
return rc;
}
// error handling
int feof(FILE *fp) { return ((FILE2*)fp)->errno_ == EOF; }
int ferror(FILE *fp) { return ((FILE2*)fp)->errno_; }
void clearerr(FILE *fp) { ((FILE2*)fp)->errno_ = 0; }
// aliases
int fgetpos(FILE *fp, fpos_t *pos) { return (*pos = ftell(fp)) != -1L; }
int fsetpos(FILE *fp, const fpos_t *pos) { return fseek(fp, *pos, SEEK_SET); }
void rewind(FILE *fp) { fseek(fp, 0L, SEEK_SET); }
// aliases
int fgetc(FILE *fp) { int ch = EOF; return fread(&ch, sizeof(char), 1, fp) != 1 ? EOF : ch; }
int fputc(int ch, FILE *fp) { return fwrite(&ch, sizeof(char), 1, fp) == 1; }
int getc(FILE *fp) { return fgetc(fp); }
int putc(int ch, FILE *fp) { return fputc(ch, fp); }
int fputs(const char *str, FILE *fp) { return fwrite(str, strlen(str), 1, fp) == 1 && fwrite("\n",1,1,fp) == 1; }
char* fgets(char *str, int n, FILE *fp) {
char *cpy = str;
int ch = EOF;
while( n && (ch = fgetc(fp)) != EOF && !strchr("\r\n", ch) ) *str++ = ch, --n;
while( n && (ch = fgetc(fp)) != EOF && strchr("\r\n", ch) ) *str++ = ch, --n;
return ch == EOF ? NULL : cpy;
}
#include <assert.h>
void test1() {
char buf[256] = {0};
FILE *fp = fopen(__FILE__,"rb");
assert(fp);
assert(fgets(buf, sizeof(buf), fp));
assert(~puts(buf));
assert(0 == fclose(fp));
assert(~puts("Ok"));
}
void test2() {
const char *buf = "hello world";
FILE *fp = fopen(NULL, "rb");
assert(fwrite(buf, strlen(buf), 1, fp));
puts(*fdata(fp));
assert(0 == fclose(fp));
assert(~puts("Ok"));
}
void test3() {
FILE *fp = fopen(NULL, "rb");
assert(fprintf(fp, "hello %s! %d\n", "world", 123));
puts(*fdata(fp));
assert(0 == fclose(fp));
assert(~puts("Ok"));
}
int main() {
test1();
test2();
test3();
}

View File

@ -415,7 +415,11 @@ void editor_inspect(obj *o) {
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(label, PTR);
else if( !strcmp(TYPE,"vec4") ) changed = ui_float4(label, PTR);
else if( !strcmp(TYPE,"color") ) changed = ui_color4(label, PTR);
else if( !strcmp(TYPE,"rgb") ) changed = ui_color3(label, PTR);
else if( !strcmp(TYPE,"rgba") ) changed = ui_color4(label, PTR);
else if( !strcmp(TYPE,"color") ) changed = ui_color4f(label, PTR);
else if( !strcmp(TYPE,"color3f") ) changed = ui_color3f(label, PTR);
else if( !strcmp(TYPE,"color4f") ) changed = ui_color4f(label, PTR);
else if( !strcmp(TYPE,"char*") ) changed = ui_string(label, PTR);
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
if( changed ) {
@ -600,8 +604,10 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
const char *ICON_PL4Y = window_has_pause() ? ICON_MDI_PLAY : ICON_MDI_PAUSE;
const char *ICON_SKIP = window_has_pause() ? ICON_MDI_STEP_FORWARD/*ICON_MDI_SKIP_NEXT*/ : ICON_MDI_FAST_FORWARD;
int is_borderless = !glfwGetWindowAttrib(window, GLFW_DECORATED);
int ingame = !editor.active;
UI_MENU(14, \
static double clicked_titlebar = 0;
UI_MENU(14+is_borderless, \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
if(ingame) ui_enable(); \
@ -610,7 +616,7 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
UI_MENU_ITEM(ICON_MDI_STOP, editor_send("stop")) \
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
UI_MENU_ITEM(STATS, stats_mode = (++stats_mode) % 3) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+34) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+34 + 32*is_borderless, clicked_titlebar = time_ms()) \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
UI_MENU_ITEM(ICON_MDI_SCRIPT_TEXT, editor_send("script")) \
@ -623,6 +629,23 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
UI_MENU_ITEM(ICON_MD_CLOSE, editor_send("quit")) \
);
if( is_borderless ) {
static vec3 drag = {0};
if( clicked_titlebar ) {
static double clicks = 0;
if( input_up(MOUSE_L) ) ++clicks;
if( input_up(MOUSE_L) && clicks == 2 ) window_visible(false), window_maximize( window_has_maximize() ^ 1 ), window_visible(true);
if( (time_ms() - clicked_titlebar) > 400 ) clicks = 0, clicked_titlebar = 0;
if( input_down(MOUSE_L) ) drag = vec3(input(MOUSE_X), input(MOUSE_Y), 1);
}
if( drag.z *= !input_up(MOUSE_L) ) {
int wx = 0, wy = 0;
glfwGetWindowPos(window_handle(), &wx, &wy);
glfwSetWindowPos(window_handle(), wx + input(MOUSE_X) - drag.x, wy + input(MOUSE_Y) - drag.y);
}
}
if( !editor.active ) return;
// draw edit view (gizmos, position markers, etc).

View File

@ -7,14 +7,18 @@
#include "3rd_lite.h"
// }
TODO("new: integrate with Art/ browser")
TODO("bug: lite key bindings are being sent to editor")
TODO("eval: https://github.com/Jipok/lite-plugins")
TODO("eval: https://github.com/drmargarido/linters")
TODO("eval: https://github.com/monolifed/theme16")
TODO("eval: https://github.com/takase1121/lite-contextmenu ")
TODO("eval: https://github.com/drmargarido/TodoTreeView")
TODO("eval: https://github.com/takase1121/lite-nagbar")
TODO("eval: https://github.com/rxi/console")
TODO("bug: not sending quit signal to lite neither at window close nor editor close (see: temporary files)")
TODO("bug: missing search results window")
TODO("bug: missing code completions popup")
// TODO("eval: https://github.com/drmargarido/linters")
// TODO("eval: https://github.com/monolifed/theme16")
// TODO("eval: https://github.com/vincens2005/lite-formatters")
// https://github.com/takase1121/lite-xl-img
// https://github.com/takase1121/lite-xl-finder
// https://github.com/rxi/lite/commit/236a585756cb9fa70130eee6c9a604780aced424 > suru.png
// https://github.com/rxi/lite/commit/f90b00748e1fe1cd2340aaa06d2526a1b2ea54ec
int ui_texture_fit(texture_t t, struct nk_rect bounds) {
// allocate complete window space
@ -56,7 +60,7 @@ int editor_scripted(int window_mode) {
}
unsigned lt_none = 0u;
unsigned lt_all = ~0u & ~(GLEQ_WINDOW_MOVED|GLEQ_WINDOW_RESIZED/*|GLEQ_WINDOW_REFRESH*/);
unsigned lt_all = ~0u & ~(GLEQ_WINDOW_MOVED/*|GLEQ_WINDOW_RESIZED|GLEQ_WINDOW_REFRESH*/);
lt_events = lt_none;
int mouse_in_rect = 0;
@ -80,7 +84,7 @@ int editor_scripted(int window_mode) {
// fullscreen_quad_rgb( lt_getsurface(0)->t, 1.2f );
ui_texture_fit(lt_getsurface(0)->t, bounds);
if( !!nk_input_is_mouse_hovering_rect(&ui_ctx->input, bounds) ) {
if( !!nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect){lt_wx+5,lt_wy+5,lt_ww-10,lt_wh-10})) ) {
lt_events &= ~(1<<31); // dont cursor shape
}

View File

@ -0,0 +1,455 @@
#include "engine/v4k.c"
// texture_t texture_createclip(unsigned cx,unsigned cy,unsigned cw,unsigned ch, unsigned tw,unsigned th,unsigned tn,void *pixels, unsigned flags) {
// return texture_create(tw,th,tn,pixels,flags);
// static array(unsigned) clip = 0;
// array_resize(clip, cw*ch*4);
// for( unsigned y = 0; y < ch; ++y )
// memcpy((char *)clip + (0+(0+y)*cw)*tn, (char*)pixels + (cx+(cy+y)*tw)*tn, cw*tn);
// return texture_create(cw,ch,tn,clip,flags);
// }
#define array_reserve_(arr,x) (array_count(arr) > (x) ? (arr) : array_resize(arr, 1+(x)))
#define ui_array(label,type,ptr) do { \
int changed = 0; \
if( ui_collapse(label, va(#type "%p",ptr)) ) { \
char label_ex[8]; \
for( int idx = 0, iend = array_count(*(ptr)); idx < iend; ++idx ) { \
type* it = *(ptr) + idx; \
snprintf(label_ex, sizeof(label_ex), "[%d]", idx); \
changed |= ui_##type(label_ex, it); \
} \
ui_collapse_end(); \
} \
} while(0)
int ui_vec2i(const char *label, vec2i *v) { return ui_unsigned2(label, (unsigned*)v); }
int ui_vec3i(const char *label, vec3i *v) { return ui_unsigned3(label, (unsigned*)v); }
int ui_vec2(const char *label, vec2 *v) { return ui_float2(label, (float*)v); }
int ui_vec3(const char *label, vec3 *v) { return ui_float3(label, (float*)v); }
int ui_vec4(const char *label, vec4 *v) { return ui_float4(label, (float*)v); }
char *trimspace(char *str) {
for( char *s = str; *s; ++s )
if(*s <= 32) memmove(s, s+1, strlen(s));
return str;
}
char *file_parent(const char *f) { // folder/folder/abc
char *p = file_path(f); // folder/folder/
char *last = strrchr(p, '/'); // ^
if( !last ) return p; // return parent if no sep
*last = '\0'; // folder/folder
last = strrchr(p, '/'); // ^
return last ? last + 1 : p; // return parent if no sep
}
int ui_obj(const char *fmt, obj *o) {
int changed = 0, item = 1;
for each_objmember(o, TYPE,NAME,PTR) {
char *label = va(fmt, NAME);
/**/ if(!strcmp(TYPE,"float")) { if(ui_float(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"int")) { if(ui_int(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"unsigned")) { if(ui_unsigned(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec2")) { if(ui_float2(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec3")) { if(ui_float3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec4")) { if(ui_float4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgb")) { if(ui_color3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgba")) { if(ui_color4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color3f")) { if(ui_color3f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color4f")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"char*")) { if(ui_string(label, PTR)) changed = item; }
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
++item;
}
return changed;
}
TODO("serialize array(types)")
TODO("serialize map(char*,types)")
TODO("serialize map(int,types)")
TODO("sprite: solid platforms, one-way platforms")
TODO("sprite: shake left-right, up-down")
TODO("sprite: coyote time")
TODO("sprite: jump buffering before grounded")
TODO("sprite: double jump, wall sliding, wall climbing")
TODO("sprite: hitbox for enemies -> wall detection")
#define OBJTYPEDEF2(...) OBJTYPEDEF(__VA_ARGS__); AUTORUN
typedef unsigned quark_t;
typedef struct atlas_frame_t {
unsigned delay;
vec4 sheet;
vec2 anchor; // @todo
array(vec3i) indices;
array(vec2) coords;
array(vec2) uvs;
} atlas_frame_t;
typedef struct atlas_anim_t {
quark_t name;
array(unsigned) frames;
} atlas_anim_t;
typedef struct atlas_t {
texture_t tex;
array(atlas_frame_t) frames;
array(atlas_anim_t) anims;
quarks_db db;
} atlas_t;
int ui_atlas_frame(atlas_frame_t *f) {
ui_unsigned("delay", &f->delay);
ui_vec4("sheet", &f->sheet);
ui_array("indices", vec3i, &f->indices);
ui_array("coords", vec2, &f->coords);
ui_array("uvs", vec2, &f->uvs);
return 0;
}
int ui_atlas(atlas_t *a) {
int changed = 0;
ui_texture(NULL, a->tex);
for( int i = 0; i < array_count(a->anims); ++i ) {
if( ui_collapse(quark_string(&a->db, a->anims[i].name), va("%p%d", a, a->anims[i].name) ) ) {
changed = i+1;
for( int j = 0; j < array_count(a->anims[i].frames); ++j ) {
if( ui_collapse(va("[%d]",j), va("%p%d.%d", a, a->anims[i].name,j) ) ) {
ui_unsigned("Frame", &a->anims[i].frames[j]);
ui_atlas_frame(a->frames + a->anims[i].frames[j]);
ui_collapse_end();
}
}
ui_collapse_end();
}
}
return changed;
}
void atlas_destroy(atlas_t *a) {
if( a ) {
texture_destroy(&a->tex);
memset(a, 0, sizeof(atlas_t));
}
}
atlas_t atlas_create(const char *inifile, unsigned flags) {
atlas_t a = {0};
int padding = 0, border = 0;
ini_t kv = ini(inifile);
for each_map(kv, char*,k, char*,v ) {
unsigned index = atoi(k);
/**/ if( strend(k, ".name") ) {
array_reserve_(a.anims, index);
a.anims[index].name = quark_intern(&a.db, v);
}
else if( strend(k, ".frames") ) {
array_reserve_(a.anims, index);
array(char*) pairs = strsplit(v, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned frame = atoi(pairs[i]);
unsigned delay = atoi(pairs[i+1]);
array_reserve_(a.frames, frame);
a.frames[frame].delay = delay;
array_push(a.anims[index].frames, frame);
}
}
else if( strend(k, ".sheet") ) {
array_reserve_(a.frames, index);
vec4 sheet = atof4(v); //x,y,x2+2,y2+2 -> x,y,w,h (for 2,2 padding)
a.frames[index].sheet = vec4(sheet.x,sheet.y,sheet.z-sheet.x,sheet.w-sheet.y);
}
else if( strend(k, ".indices") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) tuples = strsplit(text, ",");
for( int i = 0, end = array_count(tuples); i < end; i += 3 ) {
unsigned p1 = atoi(tuples[i]);
unsigned p2 = atoi(tuples[i+1]);
unsigned p3 = atoi(tuples[i+2]);
array_push(a.frames[index].indices, vec3i(p1,p2,p3));
}
}
else if( strend(k, ".coords") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned x = atoi(pairs[i]);
unsigned y = atoi(pairs[i+1]);
array_push(a.frames[index].coords, vec2(x,y));
}
}
else if( strend(k, ".uvs") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned u = atoi(pairs[i]);
unsigned v = atoi(pairs[i+1]);
array_push(a.frames[index].uvs, vec2(u,v));
}
}
else if( strend(k, "padding") ) {
padding = atoi(v);
}
else if( strend(k, "border") ) {
border = atoi(v);
}
else if( strend(k, "file") ) {
a.tex = texture(v, 0);
}
else if( strend(k, "bitmap") ) {
const char *text = v;
array(char) bin = base64_decode(text, strlen(text));
a.tex = texture_from_mem(bin, array_count(bin), 0);
array_free(bin);
}
#if 0
else if( strend(k, ".frame") ) {
array_reserve_(a.frames, index);
puts(k), puts(v);
}
#endif
}
// post-process: normalize uvs and coords into [0..1] ranges
for each_array_ptr(a.frames, atlas_frame_t, f) {
for each_array_ptr(f->uvs, vec2, uv) {
uv->x /= a.tex.w;
uv->y /= a.tex.h;
}
for each_array_ptr(f->coords, vec2, xy) {
xy->x /= a.tex.w;
xy->y /= a.tex.h;
}
// @todo: adjust padding/border
}
#if 0
// post-process: specify an anchor for each anim based on 1st frame dims
for each_array_ptr(a.anims, atlas_anim_t, anim) {
atlas_frame_t *first = a.frames + *anim->frames;
for( int i = 0; i < array_count(anim->frames); i += 2) {
atlas_frame_t *ff = a.frames + anim->frames[ i ];
ff->anchor.x = (ff->sheet.z - first->sheet.z) / 2;
ff->anchor.y = (ff->sheet.w - first->sheet.w) / 2;
}
}
#endif
return a;
}
typedef struct sprite2 { OBJ
vec4 gamepad; // up,down,left,right
vec2 fire; // a,b
vec3 pos;
float tilt;
unsigned tint;
unsigned frame;
unsigned timer, timer_ms;
unsigned flip_, flipped;
unsigned play;
bool paused;
// array(unsigned) play_queue; or unsigned play_next;
atlas_t *a; // shared
atlas_t own; // owned
} sprite2;
void sprite2_setanim(sprite2 *s, unsigned name) {
if( s->play != name ) {
s->play = name;
s->frame = 0;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer = s->timer_ms;
}
}
void sprite2_ctor(sprite2 *s) {
s->tint = WHITE;
s->timer_ms = 100;
s->flipped = 1;
}
void sprite2_dtor(sprite2 *s) {
memset(s, 0, sizeof(*s));
}
void sprite2_tick(sprite2 *s) {
int move = input(s->gamepad.array[3]) - input(s->gamepad.array[2]); // RIGHT - LEFT
int dt = 16; // window_delta() * 1000;
unsigned over = (s->timer - dt) > s->timer;
if(!s->paused)
s->timer -= dt;
if( over ) {
int len = array_count(s->a->anims[s->play].frames);
unsigned next = (s->frame + 1) % (len + !len);
unsigned eoa = next < s->frame;
s->frame = next;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer += s->timer_ms;
}
if( s->play == 0 && move ) sprite2_setanim(s, 1);
if( s->play == 1 ) { //<
float speed = 1.0f;
if(move) s->pos.x += speed * move, s->flip_ = move < 0, sprite2_setanim(s, 1);
else sprite2_setanim(s, 0);
}
}
void sprite2_draw(sprite2 *s) {
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
#if 1
// @todo {
unsigned sample = s->a->anims[s->play].frames[s->frame];
sample = 0;
f->anchor.x = (-s->a->frames[sample].sheet.z + f->sheet.z) / 2;
f->anchor.y = (+s->a->frames[sample].sheet.w - f->sheet.w) / 2;
// }
#endif
// rect(x,y,w,h) is [0..1] normalized, z-index, pos(x,y,scale), rotation (degrees), color (rgba)
vec4 rect = { f->sheet.x / s->a->tex.w, f->sheet.y / s->a->tex.h, f->sheet.z / s->a->tex.w, f->sheet.w / s->a->tex.h };
sprite_rect(s->a->tex, rect, s->pos.y, vec4(s->pos.x+f->anchor.x,s->pos.y+f->anchor.y,s->flip_ ^ s->flipped?1:-1,1), s->tilt, s->tint);
}
void sprite2_edit(sprite2 *s) {
const char *id = vac("%p", s);
if( s && ui_collapse(id, id) ) {
ui_obj("%s", (obj*)s);
ui_bool("paused", &s->paused);
ui_label(va("frame anim [%d]", s->a->anims[s->play].frames[s->frame]));
int k = s->play;
if( ui_int("anim", &k) ) {
sprite2_setanim(s, k);
}
int selected = ui_atlas(s->a);
if( selected ) sprite2_setanim(s, selected - 1);
ui_collapse_end();
}
}
OBJTYPEDEF(sprite2,10);
AUTORUN {
STRUCT(sprite2, vec4, gamepad);
STRUCT(sprite2, vec2, fire);
STRUCT(sprite2, vec3, pos);
STRUCT(sprite2, float, tilt);
STRUCT(sprite2, rgba, tint);
STRUCT(sprite2, unsigned, frame);
STRUCT(sprite2, unsigned, timer);
STRUCT(sprite2, unsigned, timer_ms);
STRUCT(sprite2, unsigned, flipped);
STRUCT(sprite2, unsigned, play);
EXTEND(sprite2, ctor,edit,draw,tick);
}
sprite2* sprite2_new(const char *ase, int bindings[6]) {
sprite2 *s = obj_new(sprite2, {bindings[0],bindings[1],bindings[2],bindings[3]}, {bindings[4],bindings[5]});
s->own = atlas_create(ase, 0);
s->a = &s->own;
return s;
}
void sprite2_del(sprite2 *s) {
if( s ) {
if( s->a == &s->own ) atlas_destroy(&s->own);
obj_free(s);
memset(s, 0, sizeof(sprite2));
}
}
void game(unsigned frame) {
static camera_t cam;
static sprite2 *s1 = 0;
static sprite2 *s2 = 0;
static sprite2 *s3 = 0;
static sprite2 *s4 = 0;
if( !frame ) {
// camera center(x,y) zoom(z)
cam = camera();
cam.position = vec3(window_width()/2,window_height()/2,8);
camera_enable(&cam);
sprite2_del(s1);
sprite2_del(s2);
sprite2_del(s3);
sprite2_del(s4);
s1 = sprite2_new("Captain Clown Nose.ase", (int[6]){KEY_UP,KEY_DOWN,KEY_LEFT,KEY_RIGHT,KEY_A,KEY_S});
s2 = sprite2_new("Crew-Crabby.ase", (int[6]){KEY_I,KEY_K,KEY_J,KEY_L} );
s3 = sprite2_new("Props-Shooter Traps.ase", (int[6]){0} );
s4 = sprite2_new("Crew-Fierce Tooth.ase", (int[6]){0,0,KEY_N,KEY_M} );
// pos and z-order
s1->pos = vec3(window_width()/2, window_height()/2, 2);
s2->pos = vec3(window_width()/2, window_height()/2, 1);
s3->pos = vec3(window_width()/2, window_height()/2, 1);
s4->pos = vec3(window_width()/2, window_height()/2, 1);
s4->flipped ^= 1;
}
// camera panning (x,y) & zooming (z)
if( !ui_hover() && !ui_active() ) {
if( input(MOUSE_L) ) cam.position.x -= input_diff(MOUSE_X);
if( input(MOUSE_L) ) cam.position.y -= input_diff(MOUSE_Y);
cam.position.z += input_diff(MOUSE_W) * 0.1; // cam.p.z += 0.001f; for tests
}
obj_tick(s1);
obj_draw(s1);
obj_tick(s2);
obj_draw(s2);
obj_tick(s3);
obj_draw(s3);
obj_tick(s4);
obj_draw(s4);
if( ui_panel("Sprites", PANEL_OPEN)) {
obj_edit(s1);
obj_edit(s2);
obj_edit(s3);
obj_edit(s4);
ui_panel_end();
}
}
int main() {
unsigned frame = 0;
for( window_create(0.75, 0); window_swap(); ) {
if( input_down(KEY_Z) && input(KEY_ALT) ) window_record(file_counter(va("%s.mp4",app_name())));
if( input_down(KEY_F5) ) frame = 0;
game( frame++ );
}
}

View File

@ -15,7 +15,7 @@ x=0.000000
y=0.052117
w=0.209707
h=0.946108
visible=1
visible=0
[Nodes 󱁉]
x=0.000916
y=0.053746