huge v4k update
parent
dbef195cd2
commit
5aa83a524a
1
MAKE.bat
1
MAKE.bat
|
@ -482,6 +482,7 @@ if "%1"=="tidy" (
|
||||||
del *.mp4 > nul 2> nul
|
del *.mp4 > nul 2> nul
|
||||||
del *.def > nul 2> nul
|
del *.def > nul 2> nul
|
||||||
del *.dll > nul 2> nul
|
del *.dll > nul 2> nul
|
||||||
|
del *.csv > nul 2> nul
|
||||||
del 3rd_*.* > nul 2> nul
|
del 3rd_*.* > nul 2> nul
|
||||||
del v4k_*.* > nul 2> nul
|
del v4k_*.* > nul 2> nul
|
||||||
del v4k.html > nul 2> nul
|
del v4k.html > nul 2> nul
|
||||||
|
|
1239
engine/joint/v4k.h
1239
engine/joint/v4k.h
File diff suppressed because it is too large
Load Diff
|
@ -27,6 +27,7 @@
|
||||||
#define ZIP_H
|
#define ZIP_H
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
typedef struct zip zip;
|
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
|
// @fixme: calc whole crc contents
|
||||||
uint32_t crc = 0;
|
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));
|
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.");
|
if(ferror(in)) return ERR(false, "Error while calculating CRC, skipping store.");
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -8955,12 +8955,7 @@ static bool LzmaDec_Init(CLzmaDec *p, const uint8_t *raw_props)
|
||||||
|
|
||||||
// glue.c
|
// glue.c
|
||||||
|
|
||||||
static
|
static __thread
|
||||||
#ifdef _MSC_VER
|
|
||||||
__declspec(thread)
|
|
||||||
#else
|
|
||||||
__thread
|
|
||||||
#endif
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t *begin, *seek, *end;
|
uint8_t *begin, *seek, *end;
|
||||||
}
|
}
|
||||||
|
@ -10300,9 +10295,7 @@ static inline uint32_t DecodeMod(const uint8_t** p) {
|
||||||
|
|
||||||
// LZ77
|
// LZ77
|
||||||
|
|
||||||
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen) {
|
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen, ULZ_WORKMEM *u) {
|
||||||
ULZ_WORKMEM *u =(ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
|
|
||||||
|
|
||||||
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
||||||
u->HashTable[i]=ULZ_NIL;
|
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;
|
op+=run;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULZ_REALLOC(u, 0);
|
|
||||||
return op-out;
|
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)
|
if (level<1 || level>9)
|
||||||
return 0;
|
return 0;
|
||||||
const int max_chain=(level<9)?1<<level:1<<13;
|
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)
|
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
||||||
u->HashTable[i]=ULZ_NIL;
|
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;
|
op+=run;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULZ_REALLOC(u, 0);
|
|
||||||
return op-out;
|
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) {
|
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 level = flags > 9 ? 9 : flags; // [0..(6)..9]
|
||||||
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level)
|
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);
|
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, &u);
|
||||||
return (unsigned)rc;
|
return (unsigned)rc;
|
||||||
}
|
}
|
||||||
unsigned ulz_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
|
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;
|
double enctime = 0;
|
||||||
if( logfile ) tm = clock();
|
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};
|
uint32_t outlen[2] = {0};
|
||||||
|
|
||||||
best = clist[0];
|
best = clist[0];
|
||||||
|
@ -10748,15 +10740,15 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
|
||||||
if( compr ) {
|
if( compr ) {
|
||||||
uint8_t packer = (compr << 4) | flags;
|
uint8_t packer = (compr << 4) | flags;
|
||||||
// store block length + compressor + compr data
|
// 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(&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 {
|
} else {
|
||||||
uint8_t packer = 0;
|
uint8_t packer = 0;
|
||||||
// store block length + no-compressor + raw data
|
// 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(&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;
|
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
|
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 block8; if( fread(&block8, 1,1, in ) != 1 ) return 0;
|
||||||
uint8_t excess8; if( fread(&excess8, 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 BLOCK_SIZE = 1ull << block8;
|
||||||
uint64_t EXCESS = 1ull << excess8;
|
uint64_t EXCESS = 1ull << excess8;
|
||||||
|
|
||||||
|
@ -10803,15 +10795,15 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
|
||||||
double dectime = 0;
|
double dectime = 0;
|
||||||
if(logfile) tm = clock();
|
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;
|
if (inlen>(BLOCK_SIZE+EXCESS)) goto fail;
|
||||||
|
|
||||||
uint8_t packer;
|
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) {
|
if(packer) {
|
||||||
// read compressed
|
// read compressed
|
||||||
if (fread(inbuf, 1, inlen, in)!=inlen) goto fail;
|
if (fread(inbuf, inlen,1, in)!=1) goto fail;
|
||||||
|
|
||||||
// decompress
|
// decompress
|
||||||
uint8_t compressor = packer >> 4;
|
uint8_t compressor = packer >> 4;
|
||||||
|
@ -10819,11 +10811,11 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
|
||||||
if (!outlen) goto fail;
|
if (!outlen) goto fail;
|
||||||
} else {
|
} else {
|
||||||
// read raw
|
// read raw
|
||||||
if (fread(outbuf, 1, inlen, in)!=inlen) goto fail;
|
if (fread(outbuf, inlen,1, in)!=1) goto fail;
|
||||||
outlen=inlen;
|
outlen=inlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fwrite(outbuf, 1, outlen, out) != outlen) {
|
if (fwrite(outbuf, outlen, 1, out) != 1) {
|
||||||
perror("fwrite() failed");
|
perror("fwrite() failed");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17378,10 +17378,10 @@ extern const ltc_math_descriptor gmp_desc;
|
||||||
|
|
||||||
/* ---- LTC_BASE64 Routines ---- */
|
/* ---- LTC_BASE64 Routines ---- */
|
||||||
#ifdef LTC_BASE64
|
#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);
|
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);
|
unsigned char *out, unsigned long *outlen);
|
||||||
#endif
|
#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
|
@param outlen [in/out] The max size and resulting size of the decoded data
|
||||||
@return CRYPT_OK if successful
|
@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)
|
unsigned char *out, unsigned long *outlen)
|
||||||
{
|
{
|
||||||
return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed);
|
return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
// 3rd party libs
|
// 3rd party libs
|
||||||
|
|
||||||
#define ARCHIVE_C // archive.c
|
#define ARCHIVE_C // archive.c
|
||||||
|
#define BASE64_C // base64.c
|
||||||
#define COMPRESS_C // compress.c
|
#define COMPRESS_C // compress.c
|
||||||
#define ENET_IMPLEMENTATION // enet
|
#define ENET_IMPLEMENTATION // enet
|
||||||
#define GJK_C // gjk
|
#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)));
|
else if(!strcmp(id, "idle") && nargs ==1) push(ev, input_idle(pop(ev)));
|
||||||
{{FILE:3rd_eval.h}}
|
{{FILE:3rd_eval.h}}
|
||||||
{{FILE:3rd_luadebugger.h}}
|
{{FILE:3rd_luadebugger.h}}
|
||||||
|
{{FILE:3rd_base64.h}}
|
||||||
//#define SQLITE_OMIT_LOAD_EXTENSION
|
//#define SQLITE_OMIT_LOAD_EXTENSION
|
||||||
//#define SQLITE_CORE 1
|
//#define SQLITE_CORE 1
|
||||||
//#define SQLITE_DEBUG 1
|
//#define SQLITE_DEBUG 1
|
||||||
|
|
|
@ -23,12 +23,13 @@ typedef struct cook_subscript_t {
|
||||||
char *script;
|
char *script;
|
||||||
char *outname;
|
char *outname;
|
||||||
int compress_level;
|
int compress_level;
|
||||||
|
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
|
||||||
} cook_subscript_t;
|
} cook_subscript_t;
|
||||||
|
|
||||||
typedef struct cook_script_t {
|
typedef struct cook_script_t {
|
||||||
cook_subscript_t cs[8];
|
cook_subscript_t cs[8];
|
||||||
|
|
||||||
int num_passes;
|
int num_passes;
|
||||||
|
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
|
||||||
} cook_script_t;
|
} cook_script_t;
|
||||||
|
|
||||||
static
|
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.
|
// - if no script is going to be generated, output is in fact input file.
|
||||||
// - no compression is going to be required.
|
// - no compression is going to be required.
|
||||||
cook_subscript_t cs = { 0 };
|
cook_subscript_t cs = { 0 };
|
||||||
|
cs.gen_ns -= time_ns();
|
||||||
|
|
||||||
// reuse script heap from last call if possible (optimization)
|
// reuse script heap from last call if possible (optimization)
|
||||||
static __thread char *script = 0;
|
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(symbols);
|
||||||
map_clear(groups);
|
map_clear(groups);
|
||||||
|
|
||||||
|
|
||||||
map_find_or_add(symbols, "INFILE", STRDUP(infile));
|
map_find_or_add(symbols, "INFILE", STRDUP(infile));
|
||||||
map_find_or_add(symbols, "INPUT", 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)
|
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;
|
lines[i] = line = nl;
|
||||||
|
|
||||||
|
#if 0
|
||||||
static thread_mutex_t lock, *init = 0; if(!init) thread_mutex_init(init = &lock);
|
static thread_mutex_t lock, *init = 0; if(!init) thread_mutex_init(init = &lock);
|
||||||
thread_mutex_lock( &lock );
|
thread_mutex_lock( &lock );
|
||||||
system(line); // strcatf(&script, "%s\n", line);
|
system(line); // strcatf(&script, "%s\n", line);
|
||||||
thread_mutex_unlock( &lock );
|
thread_mutex_unlock( &lock );
|
||||||
|
#else
|
||||||
|
// append line
|
||||||
|
strcatf(&script, "%s\n", line);
|
||||||
|
#endif
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -256,15 +264,15 @@ cook_script_t cook_script(const char *rules, const char *infile, const char *out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
char *compression = 0;
|
char *compression = 0;
|
||||||
for each_map(groups, char*, key, char*, val) {
|
for each_map_ptr_sorted(groups, char*, key, char*, val) { // sorted iteration, so hopefully '0' no compression gets evaluated first
|
||||||
if( isdigit(key[0]) ) {
|
if( !compression && isdigit((*key)[0]) ) {
|
||||||
char *comma = va(",%s,", ext);
|
char *comma = va(",%s,", ext);
|
||||||
if( !strcmpi(val,ext) || strbegi(val, comma+1) || strstri(val, comma) || strendi(val, va(",%s", ext))) {
|
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
|
||||||
compression = key;
|
compression = (*key);
|
||||||
}
|
}
|
||||||
comma = va(",%s,", belongs_to);
|
comma = va(",%s,", belongs_to);
|
||||||
if( !strcmpi(val,ext) || strbegi(val, comma+1) || strstri(val, comma) || strendi(val, va(",%s", ext))) {
|
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
|
||||||
compression = key;
|
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.outname = cs.outname ? cs.outname : (char*)infile;
|
||||||
|
cs.gen_ns += time_ns();
|
||||||
|
|
||||||
ASSERT(mcs.num_passes < countof(mcs.cs));
|
ASSERT(mcs.num_passes < countof(mcs.cs));
|
||||||
mcs.cs[mcs.num_passes++] = cs;
|
mcs.cs[mcs.num_passes++] = cs;
|
||||||
|
@ -455,6 +464,11 @@ int cook(void *userdata) {
|
||||||
volatile int *progress = &job->progress;
|
volatile int *progress = &job->progress;
|
||||||
*progress = 0;
|
*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
|
// scan disk from fs_now snapshot
|
||||||
array(struct fs) filtered = zipscan_filter(job->threadid, job->numthreads);
|
array(struct fs) filtered = zipscan_filter(job->threadid, job->numthreads);
|
||||||
//printf("Scanned: %d items found\n", array_count(now));
|
//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*/);
|
zip_append_file/*_timeinfo*/(z, deleted[i], comment, in, 0/*, tm_now*/);
|
||||||
fclose(in);
|
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
|
// added or changed files
|
||||||
for( int i = 0, end = array_count(uncooked); i < end && !cook_cancelling; ++i ) {
|
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;
|
*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
|
||||||
const char *rc_output = app_exec(cs.script);
|
mcs.cs[pass].exe_ns -= time_ns();
|
||||||
int rc = atoi(rc_output);
|
// invoke cooking script
|
||||||
int outlen = file_size(cs.outfile);
|
const char *rc_output = app_exec(cs.script);
|
||||||
int failed = cs.script[0] ? rc || !outlen : 0;
|
// recap status
|
||||||
|
int rc = atoi(rc_output);
|
||||||
// print errors
|
// int outlen = file_size(cs.outfile);
|
||||||
if( failed ) {
|
int failed = rc; // cs.script[0] ? rc || !outlen : 0;
|
||||||
PRINTF("Import failed: %s while executing:\n%s\nReturned:\n%s\n", cs.outname, cs.script, rc_output);
|
// print errors
|
||||||
continue;
|
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
|
}
|
||||||
// ensure newly created files by cook are also present on repo/disc for further cook passes
|
if( pass > 0 ) { // (multi-pass cook)
|
||||||
if( pass > 0 ) { // && strchr(cs.outname, '@') ) { // pass>0 is a small optimization // special char (multi-pass cooks)
|
// newly generated file: refresh values
|
||||||
file_delete(cs.outname);
|
// ensure newly created files by cook are also present on repo/disc for further cook passes
|
||||||
file_move(cs.outfile, cs.outname);
|
file_delete(cs.outname);
|
||||||
inlen = file_size(infile = cs.outfile = 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.
|
// process only if included. may include optional compression.
|
||||||
|
mcs.cs[pass].zip_ns -= time_ns();
|
||||||
if( cs.compress_level >= 0 ) {
|
if( cs.compress_level >= 0 ) {
|
||||||
FILE *in = fopen(cs.outfile, "rb");
|
FILE *in = fopen(cs.outfile ? cs.outfile : infile, "rb");
|
||||||
|
if(!in) in = fopen(infile, "rb");
|
||||||
#if 0
|
|
||||||
struct stat st; stat(infile, &st);
|
|
||||||
struct tm *timeinfo = localtime(&st.st_mtime);
|
|
||||||
ASSERT(timeinfo);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char *comment = va("%d", inlen);
|
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);
|
PANIC("failed to add processed file into %s: %s(%s)", zipfile, cs.outname, infile);
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(in);
|
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
|
// 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(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);
|
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
|
// 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(tcc, thread_mutex_unlock( job->lock ));
|
||||||
ifdef(osx, thread_mutex_unlock( job->lock ));
|
|
||||||
|
|
||||||
thread_exit( ret );
|
thread_exit( ret );
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -669,6 +693,14 @@ bool cook_start( const char *cook_ini, const char *masks, int flags ) {
|
||||||
EDITOR = out; // @leak
|
EDITOR = out; // @leak
|
||||||
assert( EDITOR[strlen(EDITOR) - 1] == '/' );
|
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 ) {
|
if( !masks ) {
|
||||||
|
@ -715,7 +747,7 @@ bool cook_start( const char *cook_ini, const char *masks, int flags ) {
|
||||||
if( strend(fname, ".obj") ) {
|
if( strend(fname, ".obj") ) {
|
||||||
char header[4] = {0};
|
char header[4] = {0};
|
||||||
for( FILE *in = fopen(fname, "rb"); in; fclose(in), in = NULL) {
|
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, "\x64\x86", 2) ) continue;
|
||||||
if( !memcmp(header, "\x00\x00", 2) ) continue;
|
if( !memcmp(header, "\x00\x00", 2) ) continue;
|
||||||
|
@ -727,7 +759,7 @@ bool cook_start( const char *cook_ini, const char *masks, int flags ) {
|
||||||
snprintf(extdot, 32, "%s.", dot); // .png -> .png.
|
snprintf(extdot, 32, "%s.", dot); // .png -> .png.
|
||||||
// exclude vc/gcc/clang files
|
// exclude vc/gcc/clang files
|
||||||
if( strstr(fname, ".a.o.pdb.lib.ilk.exp.dSYM.") ) // must end with dot
|
if( strstr(fname, ".a.o.pdb.lib.ilk.exp.dSYM.") ) // must end with dot
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: normalize path & rebase here (absolute to local)
|
// @todo: normalize path & rebase here (absolute to local)
|
||||||
|
|
|
@ -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(json5) roots;
|
||||||
static array(char*) sources;
|
static array(char*) sources;
|
||||||
|
|
||||||
|
@ -244,7 +171,7 @@ array(char) (xml_blob)(char *key) { // base64 blob
|
||||||
if( !node ) return 0;
|
if( !node ) return 0;
|
||||||
if( !strchr(key, '$') ) return 0;
|
if( !strchr(key, '$') ) return 0;
|
||||||
const char *data = (const char*)node;
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,7 +185,7 @@ int engine_tick() {
|
||||||
window_fps_lock( hz < 5 ? 5 : hz );
|
window_fps_lock( hz < 5 ? 5 : hz );
|
||||||
} else {
|
} else {
|
||||||
// window_fps_lock( editor_hz );
|
// window_fps_lock( editor_hz );
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
//API void editor();
|
//API void editor();
|
||||||
//API bool editor_active();
|
//API bool editor_active();
|
||||||
API vec3 editor_pick(float mouse_x, float mouse_y);
|
API vec3 editor_pick(float mouse_x, float mouse_y);
|
||||||
API char* editor_path(const char *path);
|
API char* editor_path(const char *path);
|
||||||
|
|
||||||
API float* engine_getf(const char *key);
|
API float* engine_getf(const char *key);
|
||||||
API int* engine_geti(const char *key);
|
API int* engine_geti(const char *key);
|
||||||
|
|
|
@ -23,7 +23,7 @@ void* dll(const char *fname, const char *symbol) {
|
||||||
fname = (const char *)buf;
|
fname = (const char *)buf;
|
||||||
} else {
|
} else {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if is(win32)
|
#if is(win32)
|
||||||
return (void*)GetProcAddress(fname ? LoadLibraryA(fname) : GetModuleHandleA(NULL), symbol);
|
return (void*)GetProcAddress(fname ? LoadLibraryA(fname) : GetModuleHandleA(NULL), symbol);
|
||||||
|
|
|
@ -221,29 +221,29 @@ array(char*) file_list(const char *pathmasks) {
|
||||||
|
|
||||||
ASSERT(strend(cwd, "/"), "Error: dirs like '%s' must end with slash", cwd);
|
ASSERT(strend(cwd, "/"), "Error: dirs like '%s' must end with slash", cwd);
|
||||||
|
|
||||||
dir *d = dir_open(cwd, strstr(masks,"**") ? "r" : "");
|
dir *d = dir_open(cwd, strstr(masks,"**") ? "r" : "");
|
||||||
if( d ) {
|
if( d ) {
|
||||||
for( int i = 0; i < dir_count(d); ++i ) {
|
for( int i = 0; i < dir_count(d); ++i ) {
|
||||||
if( dir_file(d,i) ) {
|
if( dir_file(d,i) ) {
|
||||||
// dir_name() should return full normalized paths "C:/prj/v4k/demos/art/fx/fxBloom.fs". should exclude system dirs as well
|
// dir_name() should return full normalized paths "C:/prj/v4k/demos/art/fx/fxBloom.fs". should exclude system dirs as well
|
||||||
char *entry = dir_name(d,i);
|
char *entry = dir_name(d,i);
|
||||||
char *fname = file_name(entry);
|
char *fname = file_name(entry);
|
||||||
|
|
||||||
int allowed = 0;
|
int allowed = 0;
|
||||||
for each_substring(masks,";",mask) {
|
for each_substring(masks,";",mask) {
|
||||||
allowed |= strmatch(fname, mask);
|
allowed |= strmatch(fname, mask);
|
||||||
|
}
|
||||||
|
if( !allowed ) continue;
|
||||||
|
|
||||||
|
// if( strstr(fname, "/.") ) continue; // @fixme: still needed? useful?
|
||||||
|
|
||||||
|
// insert copy
|
||||||
|
char *copy = STRDUP(entry);
|
||||||
|
array_push(list, copy);
|
||||||
}
|
}
|
||||||
if( !allowed ) continue;
|
|
||||||
|
|
||||||
// if( strstr(fname, "/.") ) continue; // @fixme: still needed? useful?
|
|
||||||
|
|
||||||
// insert copy
|
|
||||||
char *copy = STRDUP(entry);
|
|
||||||
array_push(list, copy);
|
|
||||||
}
|
}
|
||||||
|
dir_close(d);
|
||||||
}
|
}
|
||||||
dir_close(d);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
array_sort(list, strcmp);
|
array_sort(list, strcmp);
|
||||||
|
@ -318,7 +318,7 @@ void* file_sha1(const char *file) { // 20bytes
|
||||||
sha1_init(&hs);
|
sha1_init(&hs);
|
||||||
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
||||||
char buf[8192];
|
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);
|
sha1_process(&hs, (const unsigned char *)buf, inlen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ void* file_md5(const char *file) { // 16bytes
|
||||||
md5_init(&hs);
|
md5_init(&hs);
|
||||||
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
||||||
char buf[8192];
|
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);
|
md5_process(&hs, (const unsigned char *)buf, inlen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ void* file_crc32(const char *file) { // 4bytes
|
||||||
unsigned crc = 0;
|
unsigned crc = 0;
|
||||||
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
|
||||||
char buf[8192];
|
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)
|
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_SWAP32(x) (x)
|
||||||
#define ARK_SWAP64(x) (x)
|
#define ARK_SWAP64(x) (x)
|
||||||
#define ARK_REALLOC REALLOC
|
#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 ) {
|
void ark_list( const char *infile, zip **z ) {
|
||||||
for( FILE *in = fopen(infile, "rb"); in; fclose(in), in = 0 )
|
for( FILE *in = fopen(infile, "rb"); in; fclose(in), in = 0 )
|
||||||
while(!feof(in)) {
|
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;
|
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 (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( size_out ) *size_out = 0;
|
||||||
if( strend(pathfile, "/") ) return 0; // it's a dir
|
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);
|
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
|
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( found && *found == 0 ) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//{
|
//{
|
||||||
// exclude garbage from material names
|
// 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)
|
// search (cache)
|
||||||
if( !ptr && ! is(osx) ) {
|
if( !ptr && !is(osx) ) { // @todo: remove silicon mac M1 hack
|
||||||
ptr = cache_lookup(lookup_id, &size);
|
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);
|
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
|
// cook groups
|
||||||
int rc = system(cmd);
|
int rc = atoi(app_exec(cmd));
|
||||||
if(rc < 0) PANIC("cannot invoke `%scook` (return code %d)", TOOLS, rc);
|
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
|
vfs_reload(); // @todo: optimize me. it is waaay inefficent to reload the whole VFS layout after cooking a single asset
|
||||||
|
@ -999,33 +999,33 @@ void* cache_insert(const char *pathfile, void *ptr, int size) { // append key/va
|
||||||
// keep cached files within limits
|
// keep cached files within limits
|
||||||
thread_mutex_lock(&cache_mutex);
|
thread_mutex_lock(&cache_mutex);
|
||||||
|
|
||||||
// append to cache
|
// append to cache
|
||||||
archive_dir zero = {0}, *old = dir_cache;
|
archive_dir zero = {0}, *old = dir_cache;
|
||||||
*(dir_cache = REALLOC(0, sizeof(archive_dir))) = zero;
|
*(dir_cache = REALLOC(0, sizeof(archive_dir))) = zero;
|
||||||
dir_cache->next = old;
|
dir_cache->next = old;
|
||||||
dir_cache->path = STRDUP(pathfile);
|
dir_cache->path = STRDUP(pathfile);
|
||||||
dir_cache->size = size;
|
dir_cache->size = size;
|
||||||
dir_cache->data = REALLOC(0, size+1);
|
dir_cache->data = REALLOC(0, size+1);
|
||||||
memcpy(dir_cache->data, ptr, size); size[(char*)dir_cache->data] = 0; // copy+terminator
|
memcpy(dir_cache->data, ptr, size); size[(char*)dir_cache->data] = 0; // copy+terminator
|
||||||
|
|
||||||
void *found = 0;
|
void *found = 0;
|
||||||
|
|
||||||
static int added = 0;
|
static int added = 0;
|
||||||
if( added < MAX_CACHED_FILES ) {
|
if( added < MAX_CACHED_FILES ) {
|
||||||
++added;
|
++added;
|
||||||
} else {
|
} else {
|
||||||
// remove oldest cache entry
|
// remove oldest cache entry
|
||||||
for( archive_dir *prev = dir_cache, *dir = prev; dir ; prev = dir, dir = dir->next ) {
|
for( archive_dir *prev = dir_cache, *dir = prev; dir ; prev = dir, dir = dir->next ) {
|
||||||
if( !dir->next ) {
|
if( !dir->next ) {
|
||||||
prev->next = 0; // break link
|
prev->next = 0; // break link
|
||||||
found = dir->data;
|
found = dir->data;
|
||||||
dir->path = REALLOC(dir->path, 0);
|
dir->path = REALLOC(dir->path, 0);
|
||||||
dir->data = REALLOC(dir->data, 0);
|
dir->data = REALLOC(dir->data, 0);
|
||||||
dir = REALLOC(dir, 0);
|
dir = REALLOC(dir, 0);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
thread_mutex_unlock(&cache_mutex);
|
thread_mutex_unlock(&cache_mutex);
|
||||||
|
|
||||||
|
@ -1126,7 +1126,9 @@ ini_t ini_from_mem(const char *data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_t ini(const char *filename) {
|
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) {
|
bool ini_write(const char *filename, const char *section, const char *key, const char *value) {
|
||||||
|
|
|
@ -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.
|
// 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) {
|
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];
|
unsigned index = *tag - FONT_FACE1[0];
|
||||||
|
|
||||||
if( index >= 8 ) return;
|
if( index >= 8 ) return;
|
||||||
if( font_size <= 0 || font_size > 72 ) return;
|
if( font_size <= 0 || font_size > 72 ) return;
|
||||||
if( !ttf_data || !ttf_len ) return;
|
if( !ttf_data || !ttf_len ) return;
|
||||||
|
|
|
@ -657,15 +657,15 @@ bool input_touch_active() {
|
||||||
#endif // !is(ems)
|
#endif // !is(ems)
|
||||||
|
|
||||||
int ui_mouse() {
|
int ui_mouse() {
|
||||||
ui_label2_float("X", input(MOUSE_X));
|
ui_label2_float("X", input(MOUSE_X));
|
||||||
ui_label2_float("Y", input(MOUSE_Y));
|
ui_label2_float("Y", input(MOUSE_Y));
|
||||||
ui_label2_float("Wheel", input(MOUSE_W));
|
ui_label2_float("Wheel", input(MOUSE_W));
|
||||||
ui_separator();
|
ui_separator();
|
||||||
ui_label2_bool("Left", input(MOUSE_L));
|
ui_label2_bool("Left", input(MOUSE_L));
|
||||||
ui_label2_bool("Middle", input(MOUSE_M));
|
ui_label2_bool("Middle", input(MOUSE_M));
|
||||||
ui_label2_bool("Right", input(MOUSE_R));
|
ui_label2_bool("Right", input(MOUSE_R));
|
||||||
ui_separator();
|
ui_separator();
|
||||||
for( int i = 0; i <= CURSOR_SW_AUTO; ++i ) if(ui_button(va("Cursor shape #%d", i))) window_cursor_shape(i);
|
for( int i = 0; i <= CURSOR_SW_AUTO; ++i ) if(ui_button(va("Cursor shape #%d", i))) window_cursor_shape(i);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -704,7 +704,7 @@ int ui_keyboard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int ui_gamepad(int gamepad_id) {
|
int ui_gamepad(int gamepad_id) {
|
||||||
input_use(gamepad_id);
|
input_use(gamepad_id);
|
||||||
|
|
||||||
bool connected = !!input(GAMEPAD_CONNECTED);
|
bool connected = !!input(GAMEPAD_CONNECTED);
|
||||||
|
|
||||||
|
@ -712,48 +712,48 @@ int ui_gamepad(int gamepad_id) {
|
||||||
|
|
||||||
if( !connected ) ui_disable();
|
if( !connected ) ui_disable();
|
||||||
|
|
||||||
ui_separator();
|
ui_separator();
|
||||||
|
|
||||||
ui_label2_bool("A", input(GAMEPAD_A) );
|
ui_label2_bool("A", input(GAMEPAD_A) );
|
||||||
ui_label2_bool("B", input(GAMEPAD_B) );
|
ui_label2_bool("B", input(GAMEPAD_B) );
|
||||||
ui_label2_bool("X", input(GAMEPAD_X) );
|
ui_label2_bool("X", input(GAMEPAD_X) );
|
||||||
ui_label2_bool("Y", input(GAMEPAD_Y) );
|
ui_label2_bool("Y", input(GAMEPAD_Y) );
|
||||||
ui_label2_bool("Up", input(GAMEPAD_UP) );
|
ui_label2_bool("Up", input(GAMEPAD_UP) );
|
||||||
ui_label2_bool("Down", input(GAMEPAD_DOWN) );
|
ui_label2_bool("Down", input(GAMEPAD_DOWN) );
|
||||||
ui_label2_bool("Left", input(GAMEPAD_LEFT) );
|
ui_label2_bool("Left", input(GAMEPAD_LEFT) );
|
||||||
ui_label2_bool("Right", input(GAMEPAD_RIGHT) );
|
ui_label2_bool("Right", input(GAMEPAD_RIGHT) );
|
||||||
ui_label2_bool("Menu", input(GAMEPAD_MENU) );
|
ui_label2_bool("Menu", input(GAMEPAD_MENU) );
|
||||||
ui_label2_bool("Start", input(GAMEPAD_START) );
|
ui_label2_bool("Start", input(GAMEPAD_START) );
|
||||||
|
|
||||||
ui_separator();
|
ui_separator();
|
||||||
|
|
||||||
ui_label2_float("Left pad x", input(GAMEPAD_LPADX) );
|
ui_label2_float("Left pad x", input(GAMEPAD_LPADX) );
|
||||||
ui_label2_float("Left pad y", input(GAMEPAD_LPADY) );
|
ui_label2_float("Left pad y", input(GAMEPAD_LPADY) );
|
||||||
ui_label2_float("Left trigger", input(GAMEPAD_LT) );
|
ui_label2_float("Left trigger", input(GAMEPAD_LT) );
|
||||||
ui_label2_bool("Left bumper", input(GAMEPAD_LB) );
|
ui_label2_bool("Left bumper", input(GAMEPAD_LB) );
|
||||||
ui_label2_bool("Left thumb", input(GAMEPAD_LTHUMB) );
|
ui_label2_bool("Left thumb", input(GAMEPAD_LTHUMB) );
|
||||||
|
|
||||||
vec2 v = input_filter_deadzone( input2(GAMEPAD_LPADX), 0.1f );
|
vec2 v = input_filter_deadzone( input2(GAMEPAD_LPADX), 0.1f );
|
||||||
ui_label2_float("Filtered pad x", v.x);
|
ui_label2_float("Filtered pad x", v.x);
|
||||||
ui_label2_float("Filtered pad y", v.y);
|
ui_label2_float("Filtered pad y", v.y);
|
||||||
|
|
||||||
ui_separator();
|
ui_separator();
|
||||||
|
|
||||||
ui_label2_float("Right pad x", input(GAMEPAD_RPADX) );
|
ui_label2_float("Right pad x", input(GAMEPAD_RPADX) );
|
||||||
ui_label2_float("Right pad y", input(GAMEPAD_RPADY) );
|
ui_label2_float("Right pad y", input(GAMEPAD_RPADY) );
|
||||||
ui_label2_float("Right trigger", input(GAMEPAD_RT) );
|
ui_label2_float("Right trigger", input(GAMEPAD_RT) );
|
||||||
ui_label2_bool("Right bumper", input(GAMEPAD_RB) );
|
ui_label2_bool("Right bumper", input(GAMEPAD_RB) );
|
||||||
ui_label2_bool("Right thumb", input(GAMEPAD_RTHUMB) );
|
ui_label2_bool("Right thumb", input(GAMEPAD_RTHUMB) );
|
||||||
|
|
||||||
vec2 w = input_filter_deadzone( input2(GAMEPAD_RPADX), 0.1f );
|
vec2 w = input_filter_deadzone( input2(GAMEPAD_RPADX), 0.1f );
|
||||||
ui_label2_float("Filtered pad x", w.x);
|
ui_label2_float("Filtered pad x", w.x);
|
||||||
ui_label2_float("Filtered pad y", w.y);
|
ui_label2_float("Filtered pad y", w.y);
|
||||||
|
|
||||||
ui_enable();
|
ui_enable();
|
||||||
|
|
||||||
input_use(0);
|
input_use(0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ui_gamepads() {
|
int ui_gamepads() {
|
||||||
for( int i = 0; i < 4; ++i ) ui_gamepad(i);
|
for( int i = 0; i < 4; ++i ) ui_gamepad(i);
|
||||||
|
|
|
@ -853,7 +853,7 @@ void printi_( int *m, int ii, int jj ) {
|
||||||
}
|
}
|
||||||
void print_( float *m, int ii, int jj ) {
|
void print_( float *m, int ii, int jj ) {
|
||||||
for( int j = 0; j < jj; ++j ) {
|
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("");
|
||||||
}
|
}
|
||||||
// puts("---");
|
// puts("---");
|
||||||
|
|
|
@ -215,7 +215,7 @@ void *obj_free(void *o) {
|
||||||
FREE(o);
|
FREE(o);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return o; // cannot destroy: object is still referenced
|
return o; // cannot destroy: object is still referenced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,21 +463,23 @@ void test_obj_scene() {
|
||||||
static map(int,int) oms;
|
static map(int,int) oms;
|
||||||
static thread_mutex_t *oms_lock;
|
static thread_mutex_t *oms_lock;
|
||||||
void *obj_setmeta(void *o, const char *key, const char *value) {
|
void *obj_setmeta(void *o, const char *key, const char *value) {
|
||||||
|
void *ret = 0;
|
||||||
do_threadlock(oms_lock) {
|
do_threadlock(oms_lock) {
|
||||||
if(!oms) map_init_int(oms);
|
if(!oms) map_init_int(oms);
|
||||||
int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0);
|
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);
|
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* obj_meta(const void *o, const char *key) {
|
||||||
|
const char *ret = 0;
|
||||||
do_threadlock(oms_lock) {
|
do_threadlock(oms_lock) {
|
||||||
if(!oms) map_init_int(oms);
|
if(!oms) map_init_int(oms);
|
||||||
int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0);
|
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) {
|
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, "vec2") ) return ftoa2(*(vec2*)p);
|
||||||
else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p);
|
else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p);
|
||||||
else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)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);
|
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p);
|
||||||
// @todo: if strchr('*') assume obj, if reflected save guid: obj_id();
|
// @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), "";
|
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, "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, "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, "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, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p));
|
||||||
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) {
|
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) {
|
||||||
char substring[128] = {0};
|
char substring[128] = {0};
|
||||||
|
@ -905,9 +911,9 @@ char *entity_save(entity *self) {
|
||||||
static
|
static
|
||||||
void entity_register() {
|
void entity_register() {
|
||||||
do_once {
|
do_once {
|
||||||
STRUCT(entity, uintptr_t, cflags);
|
STRUCT(entity, uintptr_t, cflags);
|
||||||
obj_extend(entity, save);
|
obj_extend(entity, save);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUTORUN{
|
AUTORUN{
|
||||||
|
|
|
@ -40,17 +40,17 @@ bool id_valid(uintptr_t id);
|
||||||
#define OBJHEADER \
|
#define OBJHEADER \
|
||||||
struct { \
|
struct { \
|
||||||
ifdef(debug, const char *objname;) \
|
ifdef(debug, const char *objname;) \
|
||||||
union { \
|
union { \
|
||||||
uintptr_t objheader; \
|
uintptr_t objheader; \
|
||||||
struct { \
|
struct { \
|
||||||
uintptr_t objtype:8; \
|
uintptr_t objtype:8; \
|
||||||
uintptr_t objsizew:8; \
|
uintptr_t objsizew:8; \
|
||||||
uintptr_t objrefs:8; \
|
uintptr_t objrefs:8; \
|
||||||
uintptr_t objheap:1; \
|
uintptr_t objheap:1; \
|
||||||
uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \
|
uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \
|
||||||
uintptr_t objunused:64-8-8-8-1-1-ID_INDEX_BITS-ID_COUNT_BITS; /*19*/ \
|
uintptr_t objunused:64-8-8-8-1-1-ID_INDEX_BITS-ID_COUNT_BITS; /*19*/ \
|
||||||
uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \
|
uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \
|
||||||
}; \
|
}; \
|
||||||
}; \
|
}; \
|
||||||
array(struct obj*) objchildren; \
|
array(struct obj*) objchildren; \
|
||||||
};
|
};
|
||||||
|
@ -342,4 +342,3 @@ typedef enum OBJTYPE_BUILTINS {
|
||||||
OBJTYPE_vec2i = 9,
|
OBJTYPE_vec2i = 9,
|
||||||
OBJTYPE_vec3i = 10,
|
OBJTYPE_vec3i = 10,
|
||||||
} OBJTYPE_BUILTINS;
|
} OBJTYPE_BUILTINS;
|
||||||
|
|
||||||
|
|
|
@ -626,7 +626,7 @@ static bool rd(void *buf, size_t len, size_t swap) { // return false any error a
|
||||||
bool ret;
|
bool ret;
|
||||||
if( in.fp ) {
|
if( in.fp ) {
|
||||||
assert( !ferror(in.fp) && "invalid file handle (reader)" );
|
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 {
|
} else {
|
||||||
assert( in.membuf && "invalid memory buffer (reader)");
|
assert( in.membuf && "invalid memory buffer (reader)");
|
||||||
assert( (in.offset + len <= in.memsize) && "memory overflow! (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" );
|
FILE *fp = fopen( "/dev/urandom", "r" );
|
||||||
if( !fp ) assert(!"/dev/urandom open failed");
|
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" );
|
assert( read == n && "/dev/urandom read failed" );
|
||||||
fclose( fp );
|
fclose( fp );
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,9 +182,9 @@ API char* ftoa3(vec3 v);
|
||||||
API char* ftoa4(vec4 v);
|
API char* ftoa4(vec4 v);
|
||||||
|
|
||||||
API float atof1(const char *s);
|
API float atof1(const char *s);
|
||||||
API vec2 atof2(const char *s);
|
API vec2 atof2(const char *s);
|
||||||
API vec3 atof3(const char *s);
|
API vec3 atof3(const char *s);
|
||||||
API vec4 atof4(const char *s);
|
API vec4 atof4(const char *s);
|
||||||
|
|
||||||
API char* itoa1(int v);
|
API char* itoa1(int v);
|
||||||
API char* itoa2(vec2i v);
|
API char* itoa2(vec2i v);
|
||||||
|
@ -197,14 +197,14 @@ API vec3i atoi3(const char *s);
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// endianness
|
// endianness
|
||||||
|
|
||||||
API int is_big();
|
API int is_big();
|
||||||
API int is_little();
|
API int is_little();
|
||||||
|
|
||||||
API uint16_t swap16( uint16_t x );
|
API uint16_t swap16( uint16_t x );
|
||||||
API uint32_t swap32( uint32_t x );
|
API uint32_t swap32( uint32_t x );
|
||||||
API uint64_t swap64( uint64_t x );
|
API uint64_t swap64( uint64_t x );
|
||||||
API float swap32f(float n);
|
API float swap32f(float n);
|
||||||
API double swap64f(double n);
|
API double swap64f(double n);
|
||||||
API void swapf(float *a, float *b);
|
API void swapf(float *a, float *b);
|
||||||
API void swapf2(vec2 *a, vec2 *b);
|
API void swapf2(vec2 *a, vec2 *b);
|
||||||
API void swapf3(vec3 *a, vec3 *b);
|
API void swapf3(vec3 *a, vec3 *b);
|
||||||
|
@ -222,17 +222,17 @@ API uint64_t big64(uint64_t n); // swap64 as big
|
||||||
API float big32f(float n); // swap32 as big
|
API float big32f(float n); // swap32 as big
|
||||||
API double big64f(double n); // swap64 as big
|
API double big64f(double n); // swap64 as big
|
||||||
|
|
||||||
API uint16_t* lil16p(void *p, int sz);
|
API uint16_t* lil16p(void *p, int sz);
|
||||||
API uint32_t* lil32p(void *p, int sz);
|
API uint32_t* lil32p(void *p, int sz);
|
||||||
API uint64_t* lil64p(void *p, int sz);
|
API uint64_t* lil64p(void *p, int sz);
|
||||||
API float * lil32pf(void *p, int sz);
|
API float * lil32pf(void *p, int sz);
|
||||||
API double * lil64pf(void *p, int sz);
|
API double * lil64pf(void *p, int sz);
|
||||||
|
|
||||||
API uint16_t* big16p(void *p, int sz);
|
API uint16_t* big16p(void *p, int sz);
|
||||||
API uint32_t* big32p(void *p, int sz);
|
API uint32_t* big32p(void *p, int sz);
|
||||||
API uint64_t* big64p(void *p, int sz);
|
API uint64_t* big64p(void *p, int sz);
|
||||||
API float * big32pf(void *p, int sz);
|
API float * big32pf(void *p, int sz);
|
||||||
API double * big64pf(void *p, int sz);
|
API double * big64pf(void *p, int sz);
|
||||||
|
|
||||||
#if is(cl)
|
#if is(cl)
|
||||||
#define swap16 _byteswap_ushort
|
#define swap16 _byteswap_ushort
|
||||||
|
@ -386,3 +386,4 @@ API int saveb(unsigned char *buf, const char *format, ...);
|
||||||
|
|
||||||
API int loadf(FILE *file, const char *format, ...);
|
API int loadf(FILE *file, const char *format, ...);
|
||||||
API int loadb(const unsigned char *buf, const char *format, ...);
|
API int loadb(const unsigned char *buf, const char *format, ...);
|
||||||
|
|
||||||
|
|
|
@ -7,50 +7,49 @@ int (profiler_enable)(bool on) { return profiler_enabled = on; }
|
||||||
void (ui_profiler)() {
|
void (ui_profiler)() {
|
||||||
// @todo: ui_plot()
|
// @todo: ui_plot()
|
||||||
|
|
||||||
double fps = window_fps();
|
double fps = window_fps();
|
||||||
profile_setstat("Render.num_fps", fps);
|
profile_setstat("Render.num_fps", fps);
|
||||||
|
|
||||||
enum { COUNT = 300 };
|
enum { COUNT = 300 };
|
||||||
|
static float values[COUNT] = {0}; static int offset = 0;
|
||||||
static float values[COUNT] = {0}; static int offset = 0;
|
values[offset=(offset+1)%COUNT] = fps;
|
||||||
values[offset=(offset+1)%COUNT] = fps;
|
|
||||||
|
|
||||||
// draw fps-meter: 300 samples, [0..70] range each, 70px height plot ...
|
// draw fps-meter: 300 samples, [0..70] range each, 70px height plot ...
|
||||||
// ... unless filtering is enabled
|
// ... unless filtering is enabled
|
||||||
if( !(ui_filter && ui_filter[0]) ) {
|
if( !(ui_filter && ui_filter[0]) ) {
|
||||||
nk_layout_row_dynamic(ui_ctx, 70, 1);
|
nk_layout_row_dynamic(ui_ctx, 70, 1);
|
||||||
|
|
||||||
int index = -1;
|
int index = -1;
|
||||||
if( nk_chart_begin(ui_ctx, NK_CHART_LINES, COUNT, 0.f, 70.f) ) {
|
if( nk_chart_begin(ui_ctx, NK_CHART_LINES, COUNT, 0.f, 70.f) ) {
|
||||||
for( int i = 0; i < COUNT; ++i ) {
|
for( int i = 0; i < COUNT; ++i ) {
|
||||||
nk_flags res = nk_chart_push(ui_ctx, (float)values[i]);
|
nk_flags res = nk_chart_push(ui_ctx, (float)values[i]);
|
||||||
if( res & NK_CHART_HOVERING ) index = i;
|
if( res & NK_CHART_HOVERING ) index = i;
|
||||||
if( res & NK_CHART_CLICKED ) index = i;
|
if( res & NK_CHART_CLICKED ) index = i;
|
||||||
}
|
|
||||||
nk_chart_end(ui_ctx);
|
|
||||||
}
|
}
|
||||||
|
nk_chart_end(ui_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
// hightlight 60fps, 36fps and 12fps
|
// hightlight 60fps, 36fps and 12fps
|
||||||
struct nk_rect space; nk_layout_peek(&space, ui_ctx);
|
struct nk_rect space; nk_layout_peek(&space, ui_ctx);
|
||||||
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
|
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
|
||||||
nk_stroke_line(canvas, space.x+0,space.y-60,space.x+space.w,space.y-60, 1.0, nk_rgba(0,255,0,128));
|
nk_stroke_line(canvas, space.x+0,space.y-60,space.x+space.w,space.y-60, 1.0, nk_rgba(0,255,0,128));
|
||||||
nk_stroke_line(canvas, space.x+0,space.y-36,space.x+space.w,space.y-36, 1.0, nk_rgba(255,255,0,128));
|
nk_stroke_line(canvas, space.x+0,space.y-36,space.x+space.w,space.y-36, 1.0, nk_rgba(255,255,0,128));
|
||||||
nk_stroke_line(canvas, space.x+0,space.y-12,space.x+space.w,space.y-12, 1.0, nk_rgba(255,0,0,128));
|
nk_stroke_line(canvas, space.x+0,space.y-12,space.x+space.w,space.y-12, 1.0, nk_rgba(255,0,0,128));
|
||||||
|
|
||||||
if( index >= 0 ) {
|
if( index >= 0 ) {
|
||||||
nk_tooltipf(ui_ctx, "%.2f fps", (float)values[index]);
|
nk_tooltipf(ui_ctx, "%.2f fps", (float)values[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for each_map_ptr_sorted(profiler, const char *, key, struct profile_t, val ) {
|
for each_map_ptr_sorted(profiler, const char *, key, struct profile_t, val ) {
|
||||||
if( isnan(val->stat) ) {
|
if( isnan(val->stat) ) {
|
||||||
float v = val->avg/1000.0;
|
float v = val->avg/1000.0;
|
||||||
ui_slider2(*key, &v, va("%.2f ms", val->avg/1000.0));
|
ui_slider2(*key, &v, va("%.2f ms", val->avg/1000.0));
|
||||||
} else {
|
} else {
|
||||||
float v = val->stat;
|
float v = val->stat;
|
||||||
ui_slider2(*key, &v, va("%.2f", val->stat));
|
ui_slider2(*key, &v, va("%.2f", val->stat));
|
||||||
val->stat = 0;
|
val->stat = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,7 +10,7 @@ static map(unsigned, array(reflect_t)) members;
|
||||||
void reflect_init() {
|
void reflect_init() {
|
||||||
if(!reflects) map_init_int(reflects);
|
if(!reflects) map_init_int(reflects);
|
||||||
if(!members) map_init_int(members);
|
if(!members) map_init_int(members);
|
||||||
}
|
}
|
||||||
AUTORUN {
|
AUTORUN {
|
||||||
reflect_init();
|
reflect_init();
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ const char* symbol_naked(const char *s) {
|
||||||
if(!strstr(s, " *") ) return s;
|
if(!strstr(s, " *") ) return s;
|
||||||
char *copy = va("%s", s);
|
char *copy = va("%s", s);
|
||||||
do strswap(copy," *","*"); while( strstr(copy, " *") ); // char * -> char*
|
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) {
|
void type_inscribe(const char *TY,unsigned TYsz,const char *infos) {
|
||||||
|
@ -138,11 +138,11 @@ int ui_reflect(const char *filter) {
|
||||||
// ENUMS, then FUNCTIONS, then STRUCTS
|
// ENUMS, then FUNCTIONS, then STRUCTS
|
||||||
unsigned masks[] = { 'E', 'F', 'S' };
|
unsigned masks[] = { 'E', 'F', 'S' };
|
||||||
for( int i = 0; i < countof(masks); ++i )
|
for( int i = 0; i < countof(masks); ++i )
|
||||||
for each_map_ptr(reflects, unsigned, k, reflect_t, R) {
|
for each_map_ptr(reflects, unsigned, k, reflect_t, R) {
|
||||||
if( strmatchi(R->name, filter)) {
|
if( strmatchi(R->name, filter)) {
|
||||||
ui_reflect_(R, filter, masks[i]);
|
ui_reflect_(R, filter, masks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( enabled ) ui_enable();
|
if( enabled ) ui_enable();
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -183,9 +183,9 @@ AUTOTEST {
|
||||||
//printf("+%s vec3.%s (+%x) // %s\n", R->type, R->name, R->member_offset, R->info);
|
//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("TEXTURE_RGBA");
|
||||||
//reflect_print("vec3");
|
//reflect_print("vec3");
|
||||||
|
|
||||||
// reflect_dump("*");
|
//reflect_dump("*");
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ unsigned shader_geom(const char *gs, const char *vs, const char *fs, const char
|
||||||
PRINTF(/*"!"*/"Compiling shader\n");
|
PRINTF(/*"!"*/"Compiling shader\n");
|
||||||
|
|
||||||
char *glsl_defines = "";
|
char *glsl_defines = "";
|
||||||
if (defines) {
|
if( defines ) {
|
||||||
for each_substring(defines, ",", def) {
|
for each_substring(defines, ",", def) {
|
||||||
glsl_defines = va("%s#define %s\n", glsl_defines, def);
|
glsl_defines = va("%s#define %s\n", glsl_defines, def);
|
||||||
}
|
}
|
||||||
|
@ -400,7 +400,7 @@ int ui_shaders() {
|
||||||
if( !map_count(shader_reflect) ) return ui_label(ICON_MD_WARNING " No shaders with annotations loaded."), 0;
|
if( !map_count(shader_reflect) ) return ui_label(ICON_MD_WARNING " No shaders with annotations loaded."), 0;
|
||||||
|
|
||||||
int changed = 0;
|
int changed = 0;
|
||||||
for each_map_ptr(shader_reflect, unsigned, k, array(char*), v) {
|
for each_map_ptr(shader_reflect, unsigned, k, array(char*), v) {
|
||||||
int open = 0, clicked_or_toggled = 0;
|
int open = 0, clicked_or_toggled = 0;
|
||||||
char *id = va("##SHD%d", *k);
|
char *id = va("##SHD%d", *k);
|
||||||
char *title = va("Shader %d", *k);
|
char *title = va("Shader %d", *k);
|
||||||
|
@ -567,6 +567,27 @@ unsigned bgraf(float b, float g, float r, float a) {
|
||||||
return rgba(r * 255, g * 255, b * 255, a * 255);
|
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
|
// 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 );
|
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)
|
// 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, vec3 pos, float tilt_deg, unsigned tint_rgba) {
|
void sprite_rect( texture_t t, vec4 rect, float zindex, vec4 pos, float tilt_deg, unsigned tint_rgba) {
|
||||||
// @todo: no need to queue if alpha or scale are zero
|
// 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};
|
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.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.rgba = tint_rgba;
|
||||||
s.ox = 0/*ox*/ * s.sx;
|
s.ox = 0/*ox*/ * s.sx;
|
||||||
s.oy = 0/*oy*/ * s.sy;
|
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;
|
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);
|
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;
|
int choice;
|
||||||
if( (choice = ui_image(0, t.tex.id, t.tex.w,t.tex.h)) ) {
|
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("Loaded map", t->map_name ? t->map_name : "(none)");
|
||||||
ui_label2("Map dimensions", va("%dx%d", t->w, t->h));
|
ui_label2("Map dimensions", va("%dx%d", t->w, t->h));
|
||||||
ui_label2("Tile dimensions", va("%dx%d", t->tilew, t->tileh));
|
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))) {
|
if( ui_collapse(va("Sets: %d", array_count(t->layers)), va("%p",t))) {
|
||||||
for( int i = 0; i < array_count(t->layers); ++i ) {
|
for( int i = 0; i < array_count(t->layers); ++i ) {
|
||||||
if( ui_collapse(va("%d", i+1), va("%p%d",t,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();
|
ui_collapse_end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2366,7 +2390,7 @@ void spine_render(spine_t *p, vec3 offset, unsigned flags) {
|
||||||
offsy = dir.y * r->sy;
|
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);
|
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))) {
|
if( ui_collapse(va("Anims: %d", array_count(p->anims)), va("%p-a", p))) {
|
||||||
for each_array_ptr(p->anims, spine_anim_t, q) {
|
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))) {
|
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,
|
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),
|
// 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
|
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();
|
sprite_flush();
|
||||||
camera_get_active()->position = vec3(+window_width()/3,window_height()/2.25,2);
|
camera_get_active()->position = vec3(+window_width()/3,window_height()/2.25,2);
|
||||||
}
|
}
|
||||||
|
@ -3503,13 +3527,13 @@ int ui_fxs() {
|
||||||
if(!fx.num_loaded) return ui_label(ICON_MD_WARNING " No Post FXs with annotations loaded."), 0;
|
if(!fx.num_loaded) return ui_label(ICON_MD_WARNING " No Post FXs with annotations loaded."), 0;
|
||||||
|
|
||||||
int changed = 0;
|
int changed = 0;
|
||||||
for( int i = 0; i < 64; ++i ) {
|
for( int i = 0; i < 64; ++i ) {
|
||||||
char *name = fx_name(i); if( !name ) break;
|
char *name = fx_name(i); if( !name ) break;
|
||||||
bool b = fx_enabled(i);
|
bool b = fx_enabled(i);
|
||||||
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
|
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
|
||||||
ui_fx(i);
|
ui_fx(i);
|
||||||
ui_separator();
|
ui_separator();
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4272,7 +4296,7 @@ bool model_load_textures(iqm_t *q, const struct iqmheader *hdr, model_t *model)
|
||||||
if( material_embedded_texture ) {
|
if( material_embedded_texture ) {
|
||||||
*material_embedded_texture = '\0';
|
*material_embedded_texture = '\0';
|
||||||
material_embedded_texture += 5;
|
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));
|
//printf("%s %d\n", material_embedded_texture, array_count(embedded_texture));
|
||||||
//hexdump(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;
|
*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",
|
"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":""));
|
va("SHADING_PHONG,%s", (flags&MODEL_RIMLIGHT)?"RIM":""));
|
||||||
// }
|
// }
|
||||||
ASSERT(shaderprog > 0);
|
// ASSERT(shaderprog > 0);
|
||||||
|
|
||||||
iqm_t *q = CALLOC(1, sizeof(iqm_t));
|
iqm_t *q = CALLOC(1, sizeof(iqm_t));
|
||||||
m.program = shaderprog;
|
m.program = shaderprog;
|
||||||
|
@ -4771,13 +4795,13 @@ anims_t animations(const char *pathfile, int flags) {
|
||||||
if( anim_file ) {
|
if( anim_file ) {
|
||||||
// deserialize anim
|
// deserialize anim
|
||||||
a.speed = 1.0;
|
a.speed = 1.0;
|
||||||
for each_substring(anim_file, "\r\n", anim) {
|
for each_substring(anim_file, "\r\n", anim) {
|
||||||
int from, to;
|
int from, to;
|
||||||
char anim_name[128] = {0};
|
char anim_name[128] = {0};
|
||||||
if( sscanf(anim, "%*s %d-%d %127[^\r\n]", &from, &to, anim_name) != 3) continue;
|
if( sscanf(anim, "%*s %d-%d %127[^\r\n]", &from, &to, anim_name) != 3) continue;
|
||||||
array_push(a.anims, !!strstri(anim_name, "loop") || !strcmpi(anim_name, "idle") ? loop(from, to, 0, 0) : clip(from, to, 0, 0)); // [from,to,flags]
|
array_push(a.anims, !!strstri(anim_name, "loop") || !strcmpi(anim_name, "idle") ? loop(from, to, 0, 0) : clip(from, to, 0, 0)); // [from,to,flags]
|
||||||
array_back(a.anims)->name = strswap(strswap(strswap(STRDUP(anim_name), "Loop", ""), "loop", ""), "()", ""); // @leak
|
array_back(a.anims)->name = strswap(strswap(strswap(STRDUP(anim_name), "Loop", ""), "loop", ""), "()", ""); // @leak
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// placeholder
|
// placeholder
|
||||||
array_push(a.anims, clip(0,1,0,0));
|
array_push(a.anims, clip(0,1,0,0));
|
||||||
|
|
|
@ -37,6 +37,9 @@ API unsigned alpha( unsigned rgba );
|
||||||
|
|
||||||
#define BLUE RGBX(0xB55A06,255)
|
#define BLUE RGBX(0xB55A06,255)
|
||||||
|
|
||||||
|
API unsigned atorgba(const char *s);
|
||||||
|
API char * rgbatoa(unsigned rgba);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// images
|
// images
|
||||||
|
|
||||||
|
@ -121,6 +124,7 @@ typedef struct texture_t {
|
||||||
char* filename;
|
char* filename;
|
||||||
bool transparent;
|
bool transparent;
|
||||||
unsigned fbo; // for texture recording
|
unsigned fbo; // for texture recording
|
||||||
|
union { unsigned userdata, delay; };
|
||||||
} texture_t;
|
} texture_t;
|
||||||
|
|
||||||
API texture_t texture_compressed(const char *filename, unsigned flags);
|
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
|
// 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*/);
|
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)
|
// 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, vec3 pos, float tilt_deg, unsigned tint_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
|
// 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);
|
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;
|
} tileset_t;
|
||||||
|
|
||||||
API tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols, unsigned rows);
|
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 {
|
typedef struct tilemap_t {
|
||||||
int blank_chr; // transparent tile
|
int blank_chr; // transparent tile
|
||||||
|
@ -239,7 +244,8 @@ typedef struct tiled_t {
|
||||||
|
|
||||||
API tiled_t tiled(const char *file_tmx);
|
API tiled_t tiled(const char *file_tmx);
|
||||||
API void tiled_render(tiled_t tmx, vec3 pos);
|
API void tiled_render(tiled_t tmx, vec3 pos);
|
||||||
API void tiled_ui(tiled_t *t);
|
|
||||||
|
API void ui_tiled(tiled_t *t);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// spines
|
// 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_skin(spine_t *p, unsigned skin);
|
||||||
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
|
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
|
||||||
API void spine_animate(spine_t *p, float delta);
|
API void spine_animate(spine_t *p, float delta);
|
||||||
API void spine_ui(spine_t *p);
|
|
||||||
|
API void ui_spine(spine_t *p);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// cubemaps
|
// cubemaps
|
||||||
|
|
|
@ -9,9 +9,9 @@ camera_t camera() {
|
||||||
static camera_t cam = {0};
|
static camera_t cam = {0};
|
||||||
do_once {
|
do_once {
|
||||||
cam.speed = 0.50f;
|
cam.speed = 0.50f;
|
||||||
cam.position = vec3(10,10,10);
|
cam.position = vec3(10,10,10);
|
||||||
cam.updir = vec3(0,1,0);
|
cam.updir = vec3(0,1,0);
|
||||||
cam.fov = 45;
|
cam.fov = 45;
|
||||||
|
|
||||||
cam.damping = false;
|
cam.damping = false;
|
||||||
cam.move_friction = 0.09f;
|
cam.move_friction = 0.09f;
|
||||||
|
@ -21,18 +21,18 @@ camera_t camera() {
|
||||||
cam.last_look = vec2(0,0);
|
cam.last_look = vec2(0,0);
|
||||||
cam.last_move = vec3(0,0,0);
|
cam.last_move = vec3(0,0,0);
|
||||||
|
|
||||||
// update proj & view
|
// update proj & view
|
||||||
camera_lookat(&cam,vec3(-5,0,-5));
|
camera_lookat(&cam,vec3(-5,0,-5));
|
||||||
|
|
||||||
// @todo: remove this hack that is used to consolidate dampings
|
// @todo: remove this hack that is used to consolidate dampings
|
||||||
if( 1 ) {
|
if( 1 ) {
|
||||||
vec3 zero = {0};
|
vec3 zero = {0};
|
||||||
for( int i = 0; i < 1000; ++i ) {
|
for( int i = 0; i < 1000; ++i ) {
|
||||||
camera_moveby(&cam, zero);
|
camera_moveby(&cam, zero);
|
||||||
camera_fps(&cam,0,0);
|
camera_fps(&cam,0,0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
last_camera = old;
|
last_camera = old;
|
||||||
*camera_get_active() = cam;
|
*camera_get_active() = cam;
|
||||||
|
|
|
@ -261,11 +261,12 @@ const char *strlerp(unsigned numpairs, const char **pairs, const char *str) { //
|
||||||
}
|
}
|
||||||
|
|
||||||
array(char*) strsplit(const char *str, const char *separators) {
|
array(char*) strsplit(const char *str, const char *separators) {
|
||||||
|
enum { SLOTS = 32 };
|
||||||
static __thread int slot = 0;
|
static __thread int slot = 0;
|
||||||
static __thread char *buf[16] = {0};
|
static __thread char *buf[SLOTS] = {0};
|
||||||
static __thread array(char*) list[16] = {0};
|
static __thread array(char*) list[SLOTS] = {0};
|
||||||
|
|
||||||
slot = (slot+1) % 16;
|
slot = (slot+1) % SLOTS;
|
||||||
array_resize(list[slot], 0);
|
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
|
*(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];
|
return list[slot];
|
||||||
}
|
}
|
||||||
char* strjoin(array(char*) list, const char *separator) {
|
char* strjoin(array(char*) list, const char *separator) {
|
||||||
|
enum { SLOTS = 16 };
|
||||||
static __thread int slot = 0;
|
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 num_list = array_count(list);
|
||||||
int len = 0, inc = 0, seplen = strlen(separator);
|
int len = 0, inc = 0, seplen = strlen(separator);
|
||||||
|
|
|
@ -90,28 +90,72 @@ const char *app_cache() {
|
||||||
|
|
||||||
const char * app_exec( const char *cmd ) {
|
const char * app_exec( const char *cmd ) {
|
||||||
static __thread char output[4096+16] = {0};
|
static __thread char output[4096+16] = {0};
|
||||||
|
char *buf = output + 16; buf[0] = 0; // memset(buf, 0, 4096);
|
||||||
|
|
||||||
if( !cmd[0] ) return "0 ";
|
if( !cmd[0] ) return "0 ";
|
||||||
cmd = file_normalize(cmd);
|
cmd = file_normalize(cmd);
|
||||||
|
|
||||||
int rc = -1;
|
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) {
|
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 ) {
|
// if( rc != 0 ) {
|
||||||
char *r = strrchr(buf, '\r'); if(r) *r = 0;
|
// char *r = strrchr(buf, '\r'); if(r) *r = 0;
|
||||||
char *n = strrchr(buf, '\n'); if(n) *n = 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;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// CreateProcess() failed
|
||||||
|
rc = GetLastError();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rc = system(cmd);
|
||||||
|
#endif
|
||||||
|
|
||||||
return snprintf(output, 16, "%-15d", rc), buf[-1] = ' ', output;
|
return snprintf(output, 16, "%-15d", rc), buf[-1] = ' ', output;
|
||||||
}
|
}
|
||||||
|
|
||||||
int app_spawn( const char *cmd ) {
|
int app_spawn( const char *cmd ) {
|
||||||
if( !cmd[0] ) return -1;
|
if( !cmd[0] ) return false;
|
||||||
cmd = file_normalize(cmd);
|
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)
|
#if is(osx)
|
||||||
|
@ -694,7 +738,7 @@ void alert(const char *message) { // @todo: move to app_, besides die()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
window_visible(true);
|
window_visible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void breakpoint() {
|
void breakpoint() {
|
||||||
debugbreak();
|
debugbreak();
|
||||||
|
@ -807,9 +851,9 @@ void app_crash() {
|
||||||
*p = 42;
|
*p = 42;
|
||||||
}
|
}
|
||||||
void app_beep() {
|
void app_beep() {
|
||||||
ifdef(win32, system("rundll32 user32.dll,MessageBeep"); return; );
|
ifdef(win32, app_spawn("rundll32 user32.dll,MessageBeep"); return; );
|
||||||
ifdef(linux, system("paplay /usr/share/sounds/freedesktop/stereo/message.oga"); return; );
|
ifdef(linux, app_spawn("paplay /usr/share/sounds/freedesktop/stereo/message.oga"); return; );
|
||||||
ifdef(osx, system("tput bel"); return; );
|
ifdef(osx, app_spawn("tput bel"); return; );
|
||||||
|
|
||||||
//fallback:
|
//fallback:
|
||||||
fputc('\x7', stdout);
|
fputc('\x7', stdout);
|
||||||
|
@ -864,7 +908,7 @@ bool app_open_folder(const char *file) {
|
||||||
#else
|
#else
|
||||||
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
|
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
|
||||||
#endif
|
#endif
|
||||||
return system(buf) == 0;
|
return app_spawn(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
|
@ -877,7 +921,7 @@ bool app_open_file(const char *file) {
|
||||||
#else
|
#else
|
||||||
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
|
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
|
||||||
#endif
|
#endif
|
||||||
return system(buf) == 0;
|
return app_spawn(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
|
|
|
@ -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_abort(int signal); // helper util
|
||||||
API void trap_on_debug(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);
|
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);
|
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);
|
API int (test)(const char *file, int line, const char *expr, bool result);
|
||||||
|
|
||||||
#if ENABLE_AUTOTESTS
|
#if ENABLE_AUTOTESTS
|
||||||
|
|
|
@ -103,8 +103,8 @@ static uint64_t nanotimer(uint64_t *out_freq) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t time_ns() {
|
uint64_t time_ns() {
|
||||||
static uint64_t epoch = 0;
|
static __thread uint64_t epoch = 0;
|
||||||
static uint64_t freq = 0;
|
static __thread uint64_t freq = 0;
|
||||||
if( !freq ) {
|
if( !freq ) {
|
||||||
epoch = nanotimer(&freq);
|
epoch = nanotimer(&freq);
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,5 +152,5 @@ typedef struct curve_t {
|
||||||
API curve_t curve();
|
API curve_t curve();
|
||||||
API void curve_add(curve_t *c, vec3 p);
|
API void curve_add(curve_t *c, vec3 p);
|
||||||
API void curve_end(curve_t *c, int num_points);
|
API void curve_end(curve_t *c, int num_points);
|
||||||
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||||
API void curve_destroy(curve_t *c);
|
API void curve_destroy(curve_t *c);
|
||||||
|
|
|
@ -107,10 +107,11 @@ static int ui_using_v2_menubar = 0;
|
||||||
nk_menu_close(ui_ctx); \
|
nk_menu_close(ui_ctx); \
|
||||||
nk_menu_end(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; \
|
int hspace = total_space.w - span - (px) - 1.5 * ITEM_WIDTH; \
|
||||||
nk_layout_row_push(ui_ctx, hspace); span += hspace; \
|
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))) { \
|
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_close(ui_ctx); \
|
||||||
nk_menu_end(ui_ctx); \
|
nk_menu_end(ui_ctx); \
|
||||||
}}
|
}}
|
||||||
|
@ -124,9 +125,9 @@ static int ui_using_v2_menubar = 0;
|
||||||
|
|
||||||
#define UI_FONT_ENUM(carlito,b612) b612 // carlito
|
#define UI_FONT_ENUM(carlito,b612) b612 // carlito
|
||||||
|
|
||||||
#define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf"
|
#define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf"
|
||||||
#define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf"
|
#define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf"
|
||||||
#define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf"
|
#define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf"
|
||||||
|
|
||||||
#if UI_LESSER_SPACING
|
#if UI_LESSER_SPACING
|
||||||
enum { UI_SEPARATOR_HEIGHT = 5, UI_MENUBAR_ICON_HEIGHT = 20, UI_ROW_HEIGHT = 22, UI_MENUROW_HEIGHT = 32 };
|
enum { UI_SEPARATOR_HEIGHT = 5, UI_MENUBAR_ICON_HEIGHT = 20, UI_ROW_HEIGHT = 22, UI_MENUROW_HEIGHT = 32 };
|
||||||
|
@ -1702,20 +1703,20 @@ int ui_label(const char *label) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nk_label_(struct nk_context *ui_ctx, const char *text_, int align2 ) {
|
static int nk_label_(struct nk_context *ui_ctx, const char *text_, int align2 ) {
|
||||||
const struct nk_input *input = &ui_ctx->input;
|
const struct nk_input *input = &ui_ctx->input;
|
||||||
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
|
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
|
||||||
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
|
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
|
||||||
if( is_hovering ) {
|
if( is_hovering ) {
|
||||||
struct nk_rect winbounds = nk_window_get_bounds(ui_ctx);
|
struct nk_rect winbounds = nk_window_get_bounds(ui_ctx);
|
||||||
is_hovering &= nk_input_is_mouse_hovering_rect(input, winbounds);
|
is_hovering &= nk_input_is_mouse_hovering_rect(input, winbounds);
|
||||||
is_hovering &= nk_window_has_focus(ui_ctx);
|
is_hovering &= nk_window_has_focus(ui_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
nk_label(ui_ctx, text_, align2);
|
nk_label(ui_ctx, text_, align2);
|
||||||
|
|
||||||
// this is an ugly hack to detect which icon (within a label) we're clicking on.
|
// 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?
|
// @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;
|
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;
|
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;
|
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;
|
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;
|
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
||||||
|
|
||||||
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
||||||
ui_label_(label, NK_TEXT_LEFT);
|
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))) {
|
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
|
||||||
nk_layout_row_dynamic(ui_ctx, 120, 1);
|
nk_layout_row_dynamic(ui_ctx, 120, 1);
|
||||||
after = nk_color_picker(ui_ctx, after, NK_RGBA);
|
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);
|
nk_layout_row_dynamic(ui_ctx, 0, 1);
|
||||||
if (ui_color_mode == COL_RGB) {
|
if (ui_color_mode == COL_RGB) {
|
||||||
after.r = nk_propertyf(ui_ctx, "#R:", 0, after.r, 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_propertyf(ui_ctx, "#G:", 0, after.g, 1.0f, 0.01f,0.005f);
|
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
|
||||||
after.b = nk_propertyf(ui_ctx, "#B:", 0, after.b, 1.0f, 0.01f,0.005f);
|
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
|
||||||
after.a = nk_propertyf(ui_ctx, "#A:", 0, after.a, 1.0f, 0.01f,0.005f);
|
after.a = nk_propertyi(ui_ctx, "#A:", 0, after.a * 255, 255, 1,1) / 255.f;
|
||||||
} else {
|
} else {
|
||||||
float hsva[4];
|
float hsva[4];
|
||||||
nk_colorf_hsva_fv(hsva, after);
|
nk_colorf_hsva_fv(hsva, after);
|
||||||
hsva[0] = nk_propertyf(ui_ctx, "#H:", 0, hsva[0], 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_propertyf(ui_ctx, "#S:", 0, hsva[1], 1.0f, 0.01f,0.05f);
|
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
|
||||||
hsva[2] = nk_propertyf(ui_ctx, "#V:", 0, hsva[2], 1.0f, 0.01f,0.05f);
|
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
|
||||||
hsva[3] = nk_propertyf(ui_ctx, "#A:", 0, hsva[3], 1.0f, 0.01f,0.05f);
|
hsva[3] = nk_propertyi(ui_ctx, "#A:", 0, hsva[3] * 255, 255, 1,1) / 255.f;
|
||||||
after = nk_hsva_colorfv(hsva);
|
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;
|
nk_label(ui_ctx, va("#%02X%02X%02X%02X", r, g, b, a), NK_TEXT_CENTERED);
|
||||||
color4[1] = after.g * 255;
|
|
||||||
color4[2] = after.b * 255;
|
|
||||||
color4[3] = after.a * 255;
|
|
||||||
|
|
||||||
nk_combo_end(ui_ctx);
|
nk_combo_end(ui_ctx);
|
||||||
}
|
}
|
||||||
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
|
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
|
||||||
}
|
}
|
||||||
|
|
||||||
int ui_color3f(const char *label, float *color3) {
|
int ui_color3f(const char *label, float *color) {
|
||||||
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) {
|
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
||||||
|
|
||||||
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
||||||
ui_label_(label, NK_TEXT_LEFT);
|
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))) {
|
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
|
||||||
nk_layout_row_dynamic(ui_ctx, 120, 1);
|
nk_layout_row_dynamic(ui_ctx, 120, 1);
|
||||||
after = nk_color_picker(ui_ctx, after, NK_RGB);
|
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);
|
nk_layout_row_dynamic(ui_ctx, 0, 1);
|
||||||
if (ui_color_mode == COL_RGB) {
|
if (ui_color_mode == COL_RGB) {
|
||||||
after.r = nk_propertyf(ui_ctx, "#R:", 0, after.r, 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_propertyf(ui_ctx, "#G:", 0, after.g, 1.0f, 0.01f,0.005f);
|
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
|
||||||
after.b = nk_propertyf(ui_ctx, "#B:", 0, after.b, 1.0f, 0.01f,0.005f);
|
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
|
||||||
} else {
|
} else {
|
||||||
float hsva[4];
|
float hsva[4];
|
||||||
nk_colorf_hsva_fv(hsva, after);
|
nk_colorf_hsva_fv(hsva, after);
|
||||||
hsva[0] = nk_propertyf(ui_ctx, "#H:", 0, hsva[0], 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_propertyf(ui_ctx, "#S:", 0, hsva[1], 1.0f, 0.01f,0.05f);
|
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
|
||||||
hsva[2] = nk_propertyf(ui_ctx, "#V:", 0, hsva[2], 1.0f, 0.01f,0.05f);
|
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
|
||||||
after = nk_hsva_colorfv(hsva);
|
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;
|
nk_label(ui_ctx, va("#%02X%02X%02X", r, g, b), NK_TEXT_CENTERED);
|
||||||
color3[1] = after.g * 255;
|
|
||||||
color3[2] = after.b * 255;
|
|
||||||
|
|
||||||
nk_combo_end(ui_ctx);
|
nk_combo_end(ui_ctx);
|
||||||
}
|
}
|
||||||
|
@ -2055,6 +2133,8 @@ int ui_bool(const char *label, bool *enabled ) {
|
||||||
return chg;
|
return chg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ui_num_signs = 0;
|
||||||
|
|
||||||
int ui_int(const char *label, int *v) {
|
int ui_int(const char *label, int *v) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
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);
|
*v = (unsigned)nk_propertyd(ui_ctx, "#", 0, *v, UINT_MAX, 1,1);
|
||||||
return prev != *v;
|
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) {
|
int ui_short(const char *label, short *v) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
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];
|
return prev != v[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ui_float_sign = 0;
|
|
||||||
|
|
||||||
int ui_float2(const char *label, float *v) {
|
int ui_float2(const char *label, float *v) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
||||||
|
|
||||||
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
||||||
ui_label_(label, NK_TEXT_LEFT);
|
ui_label_(label, NK_TEXT_LEFT);
|
||||||
|
|
||||||
char *buffer = ui_float_sign ?
|
char *buffer = ui_num_signs ?
|
||||||
--ui_float_sign, va("%+.3f %+.3f", v[0], v[1]) :
|
--ui_num_signs, va("%+.3f %+.3f", v[0], v[1]) :
|
||||||
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))) {
|
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);
|
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
||||||
ui_label_(label, NK_TEXT_LEFT);
|
ui_label_(label, NK_TEXT_LEFT);
|
||||||
|
|
||||||
char *buffer = ui_float_sign ?
|
char *buffer = ui_num_signs ?
|
||||||
--ui_float_sign, va("%+.2f %+.2f %+.2f", v[0], v[1], v[2]) :
|
--ui_num_signs, va("%+.2f %+.2f %+.2f", v[0], v[1], v[2]) :
|
||||||
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))) {
|
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);
|
nk_layout_row_dynamic(ui_ctx, 0, 2);
|
||||||
ui_label_(label, NK_TEXT_LEFT);
|
ui_label_(label, NK_TEXT_LEFT);
|
||||||
|
|
||||||
char *buffer = ui_float_sign ?
|
char *buffer = ui_num_signs ?
|
||||||
--ui_float_sign, va("%+.2f %+.2f %+.2f %+.2f", v[0], v[1], v[2], v[3]) :
|
--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]);
|
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))) {
|
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]) {
|
int ui_mat33(const char *label, float M[9]) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
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;
|
int changed = 0;
|
||||||
changed |= ui_label(label);
|
changed |= ui_label(label);
|
||||||
changed |= ui_float3(NULL, M);
|
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]) {
|
int ui_mat34(const char *label, float M[12]) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
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;
|
int changed = 0;
|
||||||
changed |= ui_label(label);
|
changed |= ui_label(label);
|
||||||
changed |= ui_float4(NULL, M);
|
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]) {
|
int ui_mat44(const char *label, float M[16]) {
|
||||||
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
|
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;
|
int changed = 0;
|
||||||
changed |= ui_label(label);
|
changed |= ui_label(label);
|
||||||
changed |= ui_float4(NULL, M);
|
changed |= ui_float4(NULL, M);
|
||||||
|
|
|
@ -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_double(const char *label, double *value);
|
||||||
API int ui_buffer(const char *label, char *buffer, int buflen);
|
API int ui_buffer(const char *label, char *buffer, int buflen);
|
||||||
API int ui_string(const char *label, char **string);
|
API int ui_string(const char *label, char **string);
|
||||||
API int ui_color3(const char *label, float *color3); //[0..255]
|
API int ui_color3(const char *label, unsigned *color); //[0..255]
|
||||||
API int ui_color3f(const char *label, float *color3); //[0..1]
|
API int ui_color3f(const char *label, float color[3]); //[0..1]
|
||||||
API int ui_color4(const char *label, float *color4); //[0..255]
|
API int ui_color4(const char *label, unsigned *color); //[0..255]
|
||||||
API int ui_color4f(const char *label, float *color4); //[0..1]
|
API int ui_color4f(const char *label, float color[4]); //[0..1]
|
||||||
API int ui_unsigned(const char *label, unsigned *value);
|
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(const char *label);
|
||||||
API int ui_button_transparent(const char *label);
|
API int ui_button_transparent(const char *label);
|
||||||
API int ui_buttons(int buttons, /*labels*/...);
|
API int ui_buttons(int buttons, /*labels*/...);
|
||||||
|
|
|
@ -168,7 +168,7 @@ void window_drop_callback(GLFWwindow* window, int count, const char** paths) {
|
||||||
void window_hints(unsigned flags) {
|
void window_hints(unsigned flags) {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
//glfwInitHint( GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE );
|
//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_GRAPHICS_SWITCHING, GLFW_FALSE );
|
||||||
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
|
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
|
||||||
#endif
|
#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_FLOATING, GLFW_TRUE); // always on top
|
||||||
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
|
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
|
||||||
}
|
}
|
||||||
|
if( flags & WINDOW_BORDERLESS ) {
|
||||||
|
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// windowed
|
// windowed
|
||||||
float ratio = (float)winWidth / (winHeight + !winHeight);
|
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));
|
PRINTF("GPU OpenGL: %d.%d\n", GLAD_VERSION_MAJOR(gl_version), GLAD_VERSION_MINOR(gl_version));
|
||||||
|
|
||||||
if( FLAGS_TRANSPARENT ) { // @transparent
|
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);
|
if( scale >= 1 ) glfwMaximizeWindow(window);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -567,7 +570,7 @@ int window_frame_begin() {
|
||||||
timer = 0;
|
timer = 0;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
glfwSetWindowTitle(window, title);
|
glfwSetWindowTitle(window, title);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void input_update();
|
void input_update();
|
||||||
|
@ -780,17 +783,17 @@ void window_icon(const char *file_icon) {
|
||||||
if( !data ) data = file_read(file_icon), len = file_size(file_icon);
|
if( !data ) data = file_read(file_icon), len = file_size(file_icon);
|
||||||
|
|
||||||
if( data && len ) {
|
if( data && len ) {
|
||||||
image_t img = image_from_mem(data, len, IMAGE_RGBA);
|
image_t img = image_from_mem(data, len, IMAGE_RGBA);
|
||||||
if( img.w && img.h && img.pixels ) {
|
if( img.w && img.h && img.pixels ) {
|
||||||
GLFWimage images[1];
|
GLFWimage images[1];
|
||||||
images[0].width = img.w;
|
images[0].width = img.w;
|
||||||
images[0].height = img.h;
|
images[0].height = img.h;
|
||||||
images[0].pixels = img.pixels;
|
images[0].pixels = img.pixels;
|
||||||
glfwSetWindowIcon(window, 1, images);
|
glfwSetWindowIcon(window, 1, images);
|
||||||
has_icon = 1;
|
has_icon = 1;
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#if 0 // is(win32)
|
#if 0 // is(win32)
|
||||||
HANDLE hIcon = LoadImageA(0, file_icon, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
HANDLE hIcon = LoadImageA(0, file_icon, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
||||||
if( hIcon ) {
|
if( hIcon ) {
|
||||||
|
@ -825,7 +828,7 @@ int window_record(const char *outfile_mp4) {
|
||||||
|
|
||||||
vec2 window_dpi() {
|
vec2 window_dpi() {
|
||||||
vec2 dpi = vec2(1,1);
|
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);
|
glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &dpi.x, &dpi.y);
|
||||||
#endif
|
#endif
|
||||||
return dpi;
|
return dpi;
|
||||||
|
|
|
@ -17,6 +17,7 @@ enum WINDOW_FLAGS {
|
||||||
WINDOW_ASPECT = 0x100, // keep aspect
|
WINDOW_ASPECT = 0x100, // keep aspect
|
||||||
WINDOW_FIXED = 0x200, // disable resizing
|
WINDOW_FIXED = 0x200, // disable resizing
|
||||||
WINDOW_TRANSPARENT = 0x400,
|
WINDOW_TRANSPARENT = 0x400,
|
||||||
|
WINDOW_BORDERLESS = 0x800,
|
||||||
|
|
||||||
WINDOW_VSYNC = 0,
|
WINDOW_VSYNC = 0,
|
||||||
WINDOW_VSYNC_ADAPTIVE = 0x1000,
|
WINDOW_VSYNC_ADAPTIVE = 0x1000,
|
||||||
|
|
198
engine/v4k
198
engine/v4k
|
@ -13943,6 +13943,7 @@ int gladLoadGL( GLADloadfunc load) {
|
||||||
// 3rd party libs
|
// 3rd party libs
|
||||||
|
|
||||||
#define ARCHIVE_C // archive.c
|
#define ARCHIVE_C // archive.c
|
||||||
|
#define BASE64_C // base64.c
|
||||||
#define COMPRESS_C // compress.c
|
#define COMPRESS_C // compress.c
|
||||||
#define ENET_IMPLEMENTATION // enet
|
#define ENET_IMPLEMENTATION // enet
|
||||||
#define GJK_C // gjk
|
#define GJK_C // gjk
|
||||||
|
@ -232706,12 +232707,7 @@ static bool LzmaDec_Init(CLzmaDec *p, const uint8_t *raw_props)
|
||||||
|
|
||||||
// glue.c
|
// glue.c
|
||||||
|
|
||||||
static
|
static __thread
|
||||||
#ifdef _MSC_VER
|
|
||||||
__declspec(thread)
|
|
||||||
#else
|
|
||||||
__thread
|
|
||||||
#endif
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t *begin, *seek, *end;
|
uint8_t *begin, *seek, *end;
|
||||||
}
|
}
|
||||||
|
@ -234051,9 +234047,7 @@ static inline uint32_t DecodeMod(const uint8_t** p) {
|
||||||
|
|
||||||
// LZ77
|
// LZ77
|
||||||
|
|
||||||
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen) {
|
static int UlzCompressFast(const uint8_t* in, int inlen, uint8_t* out, int outlen, ULZ_WORKMEM *u) {
|
||||||
ULZ_WORKMEM *u =(ULZ_WORKMEM*)ULZ_REALLOC(0, sizeof(ULZ_WORKMEM));
|
|
||||||
|
|
||||||
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
||||||
u->HashTable[i]=ULZ_NIL;
|
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;
|
op+=run;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULZ_REALLOC(u, 0);
|
|
||||||
return op-out;
|
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)
|
if (level<1 || level>9)
|
||||||
return 0;
|
return 0;
|
||||||
const int max_chain=(level<9)?1<<level:1<<13;
|
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)
|
for (int i=0; i<ULZ_HASH_SIZE; ++i)
|
||||||
u->HashTable[i]=ULZ_NIL;
|
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;
|
op+=run;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULZ_REALLOC(u, 0);
|
|
||||||
return op-out;
|
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) {
|
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 level = flags > 9 ? 9 : flags; // [0..(6)..9]
|
||||||
int rc = level ? UlzCompress((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, level)
|
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);
|
: UlzCompressFast((uint8_t *)in, (int)inlen, (uint8_t *)out, (int)outlen, &u);
|
||||||
return (unsigned)rc;
|
return (unsigned)rc;
|
||||||
}
|
}
|
||||||
unsigned ulz_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
|
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;
|
double enctime = 0;
|
||||||
if( logfile ) tm = clock();
|
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};
|
uint32_t outlen[2] = {0};
|
||||||
|
|
||||||
best = clist[0];
|
best = clist[0];
|
||||||
|
@ -234499,15 +234492,15 @@ unsigned file_encode(FILE* in, FILE* out, FILE *logfile, unsigned cnum, unsigned
|
||||||
if( compr ) {
|
if( compr ) {
|
||||||
uint8_t packer = (compr << 4) | flags;
|
uint8_t packer = (compr << 4) | flags;
|
||||||
// store block length + compressor + compr data
|
// 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(&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 {
|
} else {
|
||||||
uint8_t packer = 0;
|
uint8_t packer = 0;
|
||||||
// store block length + no-compressor + raw data
|
// 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(&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;
|
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
|
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 block8; if( fread(&block8, 1,1, in ) != 1 ) return 0;
|
||||||
uint8_t excess8; if( fread(&excess8, 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 BLOCK_SIZE = 1ull << block8;
|
||||||
uint64_t EXCESS = 1ull << excess8;
|
uint64_t EXCESS = 1ull << excess8;
|
||||||
|
|
||||||
|
@ -234554,15 +234547,15 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
|
||||||
double dectime = 0;
|
double dectime = 0;
|
||||||
if(logfile) tm = clock();
|
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;
|
if (inlen>(BLOCK_SIZE+EXCESS)) goto fail;
|
||||||
|
|
||||||
uint8_t packer;
|
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) {
|
if(packer) {
|
||||||
// read compressed
|
// read compressed
|
||||||
if (fread(inbuf, 1, inlen, in)!=inlen) goto fail;
|
if (fread(inbuf, inlen,1, in)!=1) goto fail;
|
||||||
|
|
||||||
// decompress
|
// decompress
|
||||||
uint8_t compressor = packer >> 4;
|
uint8_t compressor = packer >> 4;
|
||||||
|
@ -234570,11 +234563,11 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
|
||||||
if (!outlen) goto fail;
|
if (!outlen) goto fail;
|
||||||
} else {
|
} else {
|
||||||
// read raw
|
// read raw
|
||||||
if (fread(outbuf, 1, inlen, in)!=inlen) goto fail;
|
if (fread(outbuf, inlen,1, in)!=1) goto fail;
|
||||||
outlen=inlen;
|
outlen=inlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fwrite(outbuf, 1, outlen, out) != outlen) {
|
if (fwrite(outbuf, outlen, 1, out) != 1) {
|
||||||
perror("fwrite() failed");
|
perror("fwrite() failed");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -234627,6 +234620,7 @@ unsigned file_decode(FILE* in, FILE* out, FILE *logfile) { // multi decoder
|
||||||
#define ZIP_H
|
#define ZIP_H
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
typedef struct zip zip;
|
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
|
// @fixme: calc whole crc contents
|
||||||
uint32_t crc = 0;
|
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));
|
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.");
|
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 ---- */
|
/* ---- LTC_BASE64 Routines ---- */
|
||||||
#ifdef LTC_BASE64
|
#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);
|
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);
|
unsigned char *out, unsigned long *outlen);
|
||||||
#endif
|
#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
|
@param outlen [in/out] The max size and resulting size of the decoded data
|
||||||
@return CRYPT_OK if successful
|
@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)
|
unsigned char *out, unsigned long *outlen)
|
||||||
{
|
{
|
||||||
return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed);
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
#line 0
|
#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_OMIT_LOAD_EXTENSION
|
||||||
//#define SQLITE_CORE 1
|
//#define SQLITE_CORE 1
|
||||||
//#define SQLITE_DEBUG 1
|
//#define SQLITE_DEBUG 1
|
||||||
|
|
945
engine/v4k.c
945
engine/v4k.c
File diff suppressed because it is too large
Load Diff
96
engine/v4k.h
96
engine/v4k.h
|
@ -1273,17 +1273,17 @@ bool id_valid(uintptr_t id);
|
||||||
#define OBJHEADER \
|
#define OBJHEADER \
|
||||||
struct { \
|
struct { \
|
||||||
ifdef(debug, const char *objname;) \
|
ifdef(debug, const char *objname;) \
|
||||||
union { \
|
union { \
|
||||||
uintptr_t objheader; \
|
uintptr_t objheader; \
|
||||||
struct { \
|
struct { \
|
||||||
uintptr_t objtype:8; \
|
uintptr_t objtype:8; \
|
||||||
uintptr_t objsizew:8; \
|
uintptr_t objsizew:8; \
|
||||||
uintptr_t objrefs:8; \
|
uintptr_t objrefs:8; \
|
||||||
uintptr_t objheap:1; \
|
uintptr_t objheap:1; \
|
||||||
uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \
|
uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \
|
||||||
uintptr_t objunused:64-8-8-8-1-1-ID_INDEX_BITS-ID_COUNT_BITS; /*19*/ \
|
uintptr_t objunused:64-8-8-8-1-1-ID_INDEX_BITS-ID_COUNT_BITS; /*19*/ \
|
||||||
uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \
|
uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \
|
||||||
}; \
|
}; \
|
||||||
}; \
|
}; \
|
||||||
array(struct obj*) objchildren; \
|
array(struct obj*) objchildren; \
|
||||||
};
|
};
|
||||||
|
@ -1575,7 +1575,6 @@ typedef enum OBJTYPE_BUILTINS {
|
||||||
OBJTYPE_vec2i = 9,
|
OBJTYPE_vec2i = 9,
|
||||||
OBJTYPE_vec3i = 10,
|
OBJTYPE_vec3i = 10,
|
||||||
} OBJTYPE_BUILTINS;
|
} OBJTYPE_BUILTINS;
|
||||||
|
|
||||||
#line 0
|
#line 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -2007,8 +2006,8 @@ API void *script_init_env(unsigned flags);
|
||||||
|
|
||||||
//API void editor();
|
//API void editor();
|
||||||
//API bool editor_active();
|
//API bool editor_active();
|
||||||
API vec3 editor_pick(float mouse_x, float mouse_y);
|
API vec3 editor_pick(float mouse_x, float mouse_y);
|
||||||
API char* editor_path(const char *path);
|
API char* editor_path(const char *path);
|
||||||
|
|
||||||
API float* engine_getf(const char *key);
|
API float* engine_getf(const char *key);
|
||||||
API int* engine_geti(const char *key);
|
API int* engine_geti(const char *key);
|
||||||
|
@ -2788,9 +2787,9 @@ API char* ftoa3(vec3 v);
|
||||||
API char* ftoa4(vec4 v);
|
API char* ftoa4(vec4 v);
|
||||||
|
|
||||||
API float atof1(const char *s);
|
API float atof1(const char *s);
|
||||||
API vec2 atof2(const char *s);
|
API vec2 atof2(const char *s);
|
||||||
API vec3 atof3(const char *s);
|
API vec3 atof3(const char *s);
|
||||||
API vec4 atof4(const char *s);
|
API vec4 atof4(const char *s);
|
||||||
|
|
||||||
API char* itoa1(int v);
|
API char* itoa1(int v);
|
||||||
API char* itoa2(vec2i v);
|
API char* itoa2(vec2i v);
|
||||||
|
@ -2803,14 +2802,14 @@ API vec3i atoi3(const char *s);
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// endianness
|
// endianness
|
||||||
|
|
||||||
API int is_big();
|
API int is_big();
|
||||||
API int is_little();
|
API int is_little();
|
||||||
|
|
||||||
API uint16_t swap16( uint16_t x );
|
API uint16_t swap16( uint16_t x );
|
||||||
API uint32_t swap32( uint32_t x );
|
API uint32_t swap32( uint32_t x );
|
||||||
API uint64_t swap64( uint64_t x );
|
API uint64_t swap64( uint64_t x );
|
||||||
API float swap32f(float n);
|
API float swap32f(float n);
|
||||||
API double swap64f(double n);
|
API double swap64f(double n);
|
||||||
API void swapf(float *a, float *b);
|
API void swapf(float *a, float *b);
|
||||||
API void swapf2(vec2 *a, vec2 *b);
|
API void swapf2(vec2 *a, vec2 *b);
|
||||||
API void swapf3(vec3 *a, vec3 *b);
|
API void swapf3(vec3 *a, vec3 *b);
|
||||||
|
@ -2828,17 +2827,17 @@ API uint64_t big64(uint64_t n); // swap64 as big
|
||||||
API float big32f(float n); // swap32 as big
|
API float big32f(float n); // swap32 as big
|
||||||
API double big64f(double n); // swap64 as big
|
API double big64f(double n); // swap64 as big
|
||||||
|
|
||||||
API uint16_t* lil16p(void *p, int sz);
|
API uint16_t* lil16p(void *p, int sz);
|
||||||
API uint32_t* lil32p(void *p, int sz);
|
API uint32_t* lil32p(void *p, int sz);
|
||||||
API uint64_t* lil64p(void *p, int sz);
|
API uint64_t* lil64p(void *p, int sz);
|
||||||
API float * lil32pf(void *p, int sz);
|
API float * lil32pf(void *p, int sz);
|
||||||
API double * lil64pf(void *p, int sz);
|
API double * lil64pf(void *p, int sz);
|
||||||
|
|
||||||
API uint16_t* big16p(void *p, int sz);
|
API uint16_t* big16p(void *p, int sz);
|
||||||
API uint32_t* big32p(void *p, int sz);
|
API uint32_t* big32p(void *p, int sz);
|
||||||
API uint64_t* big64p(void *p, int sz);
|
API uint64_t* big64p(void *p, int sz);
|
||||||
API float * big32pf(void *p, int sz);
|
API float * big32pf(void *p, int sz);
|
||||||
API double * big64pf(void *p, int sz);
|
API double * big64pf(void *p, int sz);
|
||||||
|
|
||||||
#if is(cl)
|
#if is(cl)
|
||||||
#define swap16 _byteswap_ushort
|
#define swap16 _byteswap_ushort
|
||||||
|
@ -2992,6 +2991,7 @@ API int saveb(unsigned char *buf, const char *format, ...);
|
||||||
|
|
||||||
API int loadf(FILE *file, const char *format, ...);
|
API int loadf(FILE *file, const char *format, ...);
|
||||||
API int loadb(const unsigned char *buf, const char *format, ...);
|
API int loadb(const unsigned char *buf, const char *format, ...);
|
||||||
|
|
||||||
#line 0
|
#line 0
|
||||||
|
|
||||||
#line 1 "engine/split/v4k_profile.h"
|
#line 1 "engine/split/v4k_profile.h"
|
||||||
|
@ -3133,6 +3133,9 @@ API unsigned alpha( unsigned rgba );
|
||||||
|
|
||||||
#define BLUE RGBX(0xB55A06,255)
|
#define BLUE RGBX(0xB55A06,255)
|
||||||
|
|
||||||
|
API unsigned atorgba(const char *s);
|
||||||
|
API char * rgbatoa(unsigned rgba);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// images
|
// images
|
||||||
|
|
||||||
|
@ -3217,6 +3220,7 @@ typedef struct texture_t {
|
||||||
char* filename;
|
char* filename;
|
||||||
bool transparent;
|
bool transparent;
|
||||||
unsigned fbo; // for texture recording
|
unsigned fbo; // for texture recording
|
||||||
|
union { unsigned userdata, delay; };
|
||||||
} texture_t;
|
} texture_t;
|
||||||
|
|
||||||
API texture_t texture_compressed(const char *filename, unsigned flags);
|
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
|
// 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*/);
|
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)
|
// 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, vec3 pos, float tilt_deg, unsigned tint_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
|
// 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);
|
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;
|
} tileset_t;
|
||||||
|
|
||||||
API tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols, unsigned rows);
|
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 {
|
typedef struct tilemap_t {
|
||||||
int blank_chr; // transparent tile
|
int blank_chr; // transparent tile
|
||||||
|
@ -3335,7 +3340,8 @@ typedef struct tiled_t {
|
||||||
|
|
||||||
API tiled_t tiled(const char *file_tmx);
|
API tiled_t tiled(const char *file_tmx);
|
||||||
API void tiled_render(tiled_t tmx, vec3 pos);
|
API void tiled_render(tiled_t tmx, vec3 pos);
|
||||||
API void tiled_ui(tiled_t *t);
|
|
||||||
|
API void ui_tiled(tiled_t *t);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// spines
|
// 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_skin(spine_t *p, unsigned skin);
|
||||||
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
|
API void spine_render(spine_t *p, vec3 offset, unsigned flags);
|
||||||
API void spine_animate(spine_t *p, float delta);
|
API void spine_animate(spine_t *p, float delta);
|
||||||
API void spine_ui(spine_t *p);
|
|
||||||
|
API void ui_spine(spine_t *p);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// cubemaps
|
// 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_abort(int signal); // helper util
|
||||||
API void trap_on_debug(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);
|
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);
|
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);
|
API int (test)(const char *file, int line, const char *expr, bool result);
|
||||||
|
|
||||||
#if ENABLE_AUTOTESTS
|
#if ENABLE_AUTOTESTS
|
||||||
|
@ -4335,7 +4342,7 @@ typedef struct curve_t {
|
||||||
API curve_t curve();
|
API curve_t curve();
|
||||||
API void curve_add(curve_t *c, vec3 p);
|
API void curve_add(curve_t *c, vec3 p);
|
||||||
API void curve_end(curve_t *c, int num_points);
|
API void curve_end(curve_t *c, int num_points);
|
||||||
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||||
API void curve_destroy(curve_t *c);
|
API void curve_destroy(curve_t *c);
|
||||||
#line 0
|
#line 0
|
||||||
|
|
||||||
|
@ -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_double(const char *label, double *value);
|
||||||
API int ui_buffer(const char *label, char *buffer, int buflen);
|
API int ui_buffer(const char *label, char *buffer, int buflen);
|
||||||
API int ui_string(const char *label, char **string);
|
API int ui_string(const char *label, char **string);
|
||||||
API int ui_color3(const char *label, float *color3); //[0..255]
|
API int ui_color3(const char *label, unsigned *color); //[0..255]
|
||||||
API int ui_color3f(const char *label, float *color3); //[0..1]
|
API int ui_color3f(const char *label, float color[3]); //[0..1]
|
||||||
API int ui_color4(const char *label, float *color4); //[0..255]
|
API int ui_color4(const char *label, unsigned *color); //[0..255]
|
||||||
API int ui_color4f(const char *label, float *color4); //[0..1]
|
API int ui_color4f(const char *label, float color[4]); //[0..1]
|
||||||
API int ui_unsigned(const char *label, unsigned *value);
|
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(const char *label);
|
||||||
API int ui_button_transparent(const char *label);
|
API int ui_button_transparent(const char *label);
|
||||||
API int ui_buttons(int buttons, /*labels*/...);
|
API int ui_buttons(int buttons, /*labels*/...);
|
||||||
|
@ -4494,6 +4503,7 @@ enum WINDOW_FLAGS {
|
||||||
WINDOW_ASPECT = 0x100, // keep aspect
|
WINDOW_ASPECT = 0x100, // keep aspect
|
||||||
WINDOW_FIXED = 0x200, // disable resizing
|
WINDOW_FIXED = 0x200, // disable resizing
|
||||||
WINDOW_TRANSPARENT = 0x400,
|
WINDOW_TRANSPARENT = 0x400,
|
||||||
|
WINDOW_BORDERLESS = 0x800,
|
||||||
|
|
||||||
WINDOW_VSYNC = 0,
|
WINDOW_VSYNC = 0,
|
||||||
WINDOW_VSYNC_ADAPTIVE = 0x1000,
|
WINDOW_VSYNC_ADAPTIVE = 0x1000,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
Binary file not shown.
|
@ -1,7 +1,6 @@
|
||||||
#define V4K_IMPLEMENTATION
|
|
||||||
#define COOK_ON_DEMAND 0
|
#define COOK_ON_DEMAND 0
|
||||||
#define COOK_FROM_TERMINAL 1
|
#define COOK_FROM_TERMINAL 1
|
||||||
#include "joint/v4k.h"
|
#include "v4k.c"
|
||||||
|
|
||||||
int main(int argc, const char **argv) {
|
int main(int argc, const char **argv) {
|
||||||
double timer = time_ss();
|
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
|
// 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 -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
|
// 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
|
||||||
|
|
BIN
tools/cook.exe
BIN
tools/cook.exe
Binary file not shown.
|
@ -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.
|
; 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).
|
; some predefined symbols: INPUT (input filename), OUTPUT (output filename), PRETTY (clean input filename), PROGRESS (cook progress).
|
||||||
|
|
||||||
@windows `echo Cooking PROGRESS% PRETTY...`
|
;@windows `echo Cooking PROGRESS% PRETTY...`
|
||||||
@linux `echo "Cooking PROGRESS% PRETTY..."`
|
;@linux `echo "Cooking PROGRESS% PRETTY..."`
|
||||||
@osx `echo "Cooking PROGRESS% PRETTY..."`
|
;@osx `echo "Cooking PROGRESS% PRETTY..."`
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; groups below are collection of files that we want to cook, and then package.
|
; 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
|
script=lua,tl
|
||||||
video=mp4,ogv,avi,mkv,wmv,mpg,mpeg
|
video=mp4,ogv,avi,mkv,wmv,mpg,mpeg
|
||||||
tiled=tmx,tsx
|
tiled=tmx,tsx
|
||||||
|
atlas=ase,aseprite
|
||||||
;excel=xlsx
|
;excel=xlsx
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
|
@ -60,10 +61,10 @@ tiled=tmx,tsx
|
||||||
|
|
||||||
[cook audio-module]
|
[cook audio-module]
|
||||||
TOOLS/mod2wav.EXE INPUT OUTPUT -> wav
|
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]
|
[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
|
; 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).
|
; 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]
|
[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.
|
; 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
|
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
|
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
|
;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
|
; let's cook all images into ktx
|
||||||
|
|
||||||
[cook image && !png && !jpg && !hdr]
|
[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]
|
[cook texture && tga]
|
||||||
TOOLS/cuttlefish.EXE -q -m -i INPUT -o OUTPUT -f BC1_RGB -> ktx
|
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]
|
;[cook excel]
|
||||||
;TOOLS/xlsx2ini.EXE INPUT OUTPUT -> ini
|
;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.
|
; 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.
|
; 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]
|
[compress]
|
||||||
0|ULZ=texture,image,model,audio,font,text,shader,script
|
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
|
||||||
|
|
Binary file not shown.
|
@ -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"
|
" SCALE = tonumber(os.getenv(\"LITE_SCALE\")) or SCALE\n"
|
||||||
" PATHSEP = package.config:sub(1, 1)\n"
|
" PATHSEP = package.config:sub(1, 1)\n"
|
||||||
" EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n"
|
" EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n"
|
||||||
|
" USERDIR = EXEDIR .. 'data/user/'\n"
|
||||||
" package.path = EXEDIR .. '/data/?.lua;' .. package.path\n"
|
" package.path = EXEDIR .. '/data/?.lua;' .. package.path\n"
|
||||||
" package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n"
|
" package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n"
|
||||||
" core = require('core')\n"
|
" core = require('core')\n"
|
||||||
|
|
|
@ -88,14 +88,14 @@ void *lt_load_file(const char *filename, int *size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* lt_button_name(int button) {
|
const char* lt_button_name(int button) {
|
||||||
if(button == GLFW_MOUSE_BUTTON_1) return "left";
|
if(button == GLFW_MOUSE_BUTTON_LEFT) return "left";
|
||||||
if(button == GLFW_MOUSE_BUTTON_2) return "middle";
|
if(button == GLFW_MOUSE_BUTTON_RIGHT) return "right";
|
||||||
if(button == GLFW_MOUSE_BUTTON_3) return "right";
|
if(button == GLFW_MOUSE_BUTTON_MIDDLE) return "middle";
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
char* lt_key_name(char *dst, int key, int vk, int mods) {
|
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_UP ) return "up";
|
||||||
if( key == GLFW_KEY_DOWN ) return "down";
|
if( key == GLFW_KEY_DOWN ) return "down";
|
||||||
|
|
|
@ -24,32 +24,18 @@ local function get_indent_string()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function insert_at_start_of_selected_lines(text, skip_empty)
|
local function doc_multiline_selections(sort)
|
||||||
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
|
||||||
for line = line1, line2 do
|
return function()
|
||||||
local line_text = doc().lines[line]
|
idx, line1, col1, line2, col2 = iter(state, idx)
|
||||||
if (not skip_empty or line_text:find("%S")) then
|
if idx and line2 > line1 and col2 == 1 then
|
||||||
doc():insert(line, 1, text)
|
line2 = line2 - 1
|
||||||
|
col2 = #doc().lines[line2]
|
||||||
end
|
end
|
||||||
|
return idx, line1, col1, line2, col2
|
||||||
end
|
end
|
||||||
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
|
|
||||||
end
|
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)
|
local function append_line_if_last_line(line)
|
||||||
if line >= #doc().lines then
|
if line >= #doc().lines then
|
||||||
doc():insert(line, math.huge, "\n")
|
doc():insert(line, math.huge, "\n")
|
||||||
|
@ -62,6 +48,32 @@ local function save(filename)
|
||||||
core.log("Saved \"%s\"", doc().filename)
|
core.log("Saved \"%s\"", doc().filename)
|
||||||
end
|
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 = {
|
local commands = {
|
||||||
["doc:undo"] = function()
|
["doc:undo"] = function()
|
||||||
|
@ -73,92 +85,66 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:cut"] = function()
|
["doc:cut"] = function()
|
||||||
if doc():has_selection() then
|
cut_or_copy(true)
|
||||||
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
|
|
||||||
--<
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:copy"] = function()
|
["doc:copy"] = function()
|
||||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
cut_or_copy(false)
|
||||||
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)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:paste"] = function()
|
["doc:paste"] = function()
|
||||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||||
local clipboard = system.get_clipboard():gsub("\r", "")
|
local value = doc().cursor_clipboard[idx] or system.get_clipboard()
|
||||||
if core.line_in_clipboard == clipboard then
|
doc():text_input(value:gsub("\r", ""), idx)
|
||||||
local line, col = doc():get_selection()
|
|
||||||
doc():insert(line, 1, clipboard)
|
|
||||||
doc():set_selection(line+1, col)
|
|
||||||
else
|
|
||||||
doc():text_input(clipboard)
|
|
||||||
end
|
end
|
||||||
--<
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline"] = function()
|
["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 ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
if col <= #indent then
|
if col <= #indent then
|
||||||
indent = indent:sub(#indent + 2 - col)
|
indent = indent:sub(#indent + 2 - col)
|
||||||
|
end
|
||||||
|
doc():text_input("\n" .. indent, idx)
|
||||||
end
|
end
|
||||||
doc():text_input("\n" .. indent)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-below"] = function()
|
["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 ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
doc():insert(line, math.huge, "\n" .. indent)
|
doc():insert(line, math.huge, "\n" .. indent)
|
||||||
doc():set_selection(line + 1, math.huge)
|
doc():set_selections(idx, line + 1, math.huge)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-above"] = function()
|
["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 ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
doc():insert(line, 1, indent .. "\n")
|
doc():insert(line, 1, indent .. "\n")
|
||||||
doc():set_selection(line, math.huge)
|
doc():set_selections(idx, line, math.huge)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:delete"] = function()
|
["doc:delete"] = function()
|
||||||
local line, col = doc():get_selection()
|
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||||
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
|
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
|
||||||
doc():remove(line, col, line, math.huge)
|
doc():remove(line1, col1, line1, math.huge)
|
||||||
|
end
|
||||||
|
doc():delete_to_cursor(idx, translate.next_char)
|
||||||
end
|
end
|
||||||
doc():delete_to(translate.next_char)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:backspace"] = function()
|
["doc:backspace"] = function()
|
||||||
local line, col = doc():get_selection()
|
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||||
if not doc():has_selection() then
|
if line1 == line2 and col1 == col2 then
|
||||||
local text = doc():get_text(line, 1, line, col)
|
local text = doc():get_text(line1, 1, line1, col1)
|
||||||
if #text >= config.indent_size and text:find("^ *$") then
|
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
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
doc():delete_to_cursor(idx, translate.previous_char)
|
||||||
end
|
end
|
||||||
doc():delete_to(translate.previous_char)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:select-all"] = function()
|
["doc:select-all"] = function()
|
||||||
|
@ -170,101 +156,135 @@ local commands = {
|
||||||
doc():set_selection(line, col)
|
doc():set_selection(line, col)
|
||||||
end,
|
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()
|
["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)
|
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,
|
end,
|
||||||
|
|
||||||
["doc:select-word"] = function()
|
["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 line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||||
local line2, col2 = translate.end_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,
|
end,
|
||||||
|
|
||||||
["doc:join-lines"] = function()
|
["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
|
if line1 == line2 then line2 = line2 + 1 end
|
||||||
local text = doc():get_text(line1, 1, line2, math.huge)
|
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||||
text = text:gsub("\n[\t ]*", " ")
|
return x:find("^%s*$") and x or x .. " "
|
||||||
--<
|
end)
|
||||||
doc():insert(line1, 1, text)
|
doc():insert(line1, 1, text)
|
||||||
doc():remove(line1, #text + 1, line2, math.huge)
|
doc():remove(line1, #text + 1, line2, math.huge)
|
||||||
if doc():has_selection() then
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
doc():set_selection(line1, math.huge)
|
doc():set_selections(idx, line1, math.huge)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:indent"] = function()
|
["doc:indent"] = function()
|
||||||
local text = get_indent_string()
|
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||||
if doc():has_selection() then
|
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
||||||
insert_at_start_of_selected_lines(text)
|
if l1 then
|
||||||
else
|
doc():set_selections(idx, l1, c1, l2, c2)
|
||||||
doc():text_input(text)
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:unindent"] = function()
|
["doc:unindent"] = function()
|
||||||
local text = get_indent_string()
|
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||||
remove_from_start_of_selected_lines(text)
|
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,
|
end,
|
||||||
|
|
||||||
["doc:duplicate-lines"] = function()
|
["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)
|
append_line_if_last_line(line2)
|
||||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||||
doc():insert(line2 + 1, 1, text)
|
doc():insert(line2 + 1, 1, text)
|
||||||
local n = line2 - line1 + 1
|
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,
|
end,
|
||||||
|
|
||||||
["doc:delete-lines"] = function()
|
["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)
|
append_line_if_last_line(line2)
|
||||||
doc():remove(line1, 1, line2 + 1, 1)
|
doc():remove(line1, 1, line2 + 1, 1)
|
||||||
doc():set_selection(line1, col1)
|
doc():set_selections(idx, line1, col1)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:move-lines-up"] = function()
|
["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)
|
append_line_if_last_line(line2)
|
||||||
if line1 > 1 then
|
if line1 > 1 then
|
||||||
local text = doc().lines[line1 - 1]
|
local text = doc().lines[line1 - 1]
|
||||||
doc():insert(line2 + 1, 1, text)
|
doc():insert(line2 + 1, 1, text)
|
||||||
doc():remove(line1 - 1, 1, line1, 1)
|
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
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:move-lines-down"] = function()
|
["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)
|
append_line_if_last_line(line2 + 1)
|
||||||
if line2 < #doc().lines then
|
if line2 < #doc().lines then
|
||||||
local text = doc().lines[line2 + 1]
|
local text = doc().lines[line2 + 1]
|
||||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||||
doc():insert(line1, 1, text)
|
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
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:toggle-line-comments"] = function()
|
["doc:toggle-line-comments"] = function()
|
||||||
local comment = doc().syntax.comment
|
local comment = doc().syntax.comment
|
||||||
if not comment then return end
|
if not comment then return end
|
||||||
|
local indentation = get_indent_string()
|
||||||
local comment_text = comment .. " "
|
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 uncomment = true
|
||||||
for line = line1, line2 do
|
local start_offset = math.huge
|
||||||
local text = doc().lines[line]
|
for line = line1, line2 do
|
||||||
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
|
local text = doc().lines[line]
|
||||||
uncomment = false
|
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
|
||||||
|
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
|
|
||||||
if uncomment then
|
|
||||||
remove_from_start_of_selected_lines(comment_text, true)
|
|
||||||
else
|
|
||||||
insert_at_start_of_selected_lines(comment_text, true)
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -343,6 +363,17 @@ local commands = {
|
||||||
end
|
end
|
||||||
end, common.path_suggest)
|
end, common.path_suggest)
|
||||||
end,
|
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
|
end
|
||||||
|
|
||||||
commands["doc:move-to-previous-char"] = function()
|
commands["doc:move-to-previous-char"] = function()
|
||||||
if doc():has_selection() then
|
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||||
local line, col = doc():get_selection(true)
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
doc():set_selection(line, col)
|
doc():set_selections(idx, line1, col1)
|
||||||
else
|
end
|
||||||
doc():move_to(translate.previous_char)
|
|
||||||
end
|
end
|
||||||
|
doc():move_to(translate.previous_char)
|
||||||
end
|
end
|
||||||
|
|
||||||
commands["doc:move-to-next-char"] = function()
|
commands["doc:move-to-next-char"] = function()
|
||||||
if doc():has_selection() then
|
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||||
local _, _, line, col = doc():get_selection(true)
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
doc():set_selection(line, col)
|
doc():set_selections(idx, line2, col2)
|
||||||
else
|
end
|
||||||
doc():move_to(translate.next_char)
|
|
||||||
end
|
end
|
||||||
|
doc():move_to(translate.next_char)
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add("core.docview", commands)
|
command.add("core.docview", commands)
|
||||||
|
|
|
@ -90,13 +90,84 @@ local function has_selection()
|
||||||
and core.active_view.doc:has_selection()
|
and core.active_view.doc:has_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add(has_selection, {
|
local function has_unique_selection()
|
||||||
["find-replace:select-next"] = function()
|
if not doc() then return false end
|
||||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
local text = nil
|
||||||
local text = doc():get_text(l1, c1, l2, c2)
|
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||||
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
if line1 == line2 and col1 == col2 then return false end
|
||||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
local selection = doc():get_text(line1, col1, line2, col2)
|
||||||
|
if text ~= nil and text ~= selection then return false end
|
||||||
|
text = selection
|
||||||
end
|
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)
|
||||||
|
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", {
|
command.add("core.docview", {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
local common = {}
|
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)
|
function common.is_utf8_cont(char)
|
||||||
local byte = char:byte()
|
local byte = char:byte()
|
||||||
|
@ -29,6 +33,13 @@ function common.round(n)
|
||||||
end
|
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)
|
function common.lerp(a, b, t)
|
||||||
if type(a) ~= "table" then
|
if type(a) ~= "table" then
|
||||||
return a + (b - a) * t
|
return a + (b - a) * t
|
||||||
|
@ -61,6 +72,26 @@ function common.color(str)
|
||||||
end
|
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)
|
local function compare_score(a, b)
|
||||||
return a.score > b.score
|
return a.score > b.score
|
||||||
end
|
end
|
||||||
|
@ -144,4 +175,17 @@ function common.bench(name, fn, ...)
|
||||||
end
|
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
|
return common
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
config.project_scan_rate = 5000 --< @r-lyeh: 5 -> 5000: from 5ms to 5s
|
|
||||||
config.fps = 60
|
config.fps = 60
|
||||||
config.max_log_items = 80
|
config.max_log_items = 80
|
||||||
config.message_timeout = 3
|
config.message_timeout = 6 --< @r-lyeh 3>6
|
||||||
config.mouse_wheel_scroll = 50 * SCALE
|
config.mouse_wheel_scroll = 50 * SCALE
|
||||||
config.file_size_limit = 10
|
config.file_size_limit = 10
|
||||||
config.ignore_files = "^%."
|
config.ignore_files = "^%."
|
||||||
|
@ -16,7 +15,8 @@ config.line_height = 1.2
|
||||||
config.indent_size = 2
|
config.indent_size = 2
|
||||||
config.tab_type = "soft"
|
config.tab_type = "soft"
|
||||||
config.line_limit = 80
|
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.project_max_files_per_folder = 2000
|
||||||
config.blink_period = 1.3 --< https://github.com/rxi/lite/issues/235
|
config.blink_period = 1.3 --< https://github.com/rxi/lite/issues/235
|
||||||
config.tabs_allowed = true --< https://github.com/rxi/lite/issues/191
|
config.tabs_allowed = true --< https://github.com/rxi/lite/issues/191
|
||||||
|
|
|
@ -7,6 +7,7 @@ local common = require "core.common"
|
||||||
|
|
||||||
local Doc = Object:extend()
|
local Doc = Object:extend()
|
||||||
|
|
||||||
|
local unpack = table.unpack
|
||||||
|
|
||||||
local function split_lines(text)
|
local function split_lines(text)
|
||||||
local res = {}
|
local res = {}
|
||||||
|
@ -16,26 +17,6 @@ local function split_lines(text)
|
||||||
return res
|
return res
|
||||||
end
|
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)
|
function Doc:new(filename)
|
||||||
self:reset()
|
self:reset()
|
||||||
if filename then
|
if filename then
|
||||||
|
@ -46,7 +27,8 @@ end
|
||||||
|
|
||||||
function Doc:reset()
|
function Doc:reset()
|
||||||
self.lines = { "\n" }
|
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.undo_stack = { idx = 1 }
|
||||||
self.redo_stack = { idx = 1 }
|
self.redo_stack = { idx = 1 }
|
||||||
self.clean_change_id = 1
|
self.clean_change_id = 1
|
||||||
|
@ -65,10 +47,16 @@ function Doc:reset_syntax()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Doc:set_filename(filename)
|
||||||
|
self.filename = filename
|
||||||
|
self.abs_filename = system.absolute_path(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:load(filename)
|
function Doc:load(filename)
|
||||||
local fp = assert( io.open(filename, "rb") )
|
local fp = assert( io.open(filename, "rb") )
|
||||||
self:reset()
|
self:reset()
|
||||||
self.filename = filename
|
self:set_filename(filename)
|
||||||
self.lines = {}
|
self.lines = {}
|
||||||
for line in fp:lines() do
|
for line in fp:lines() do
|
||||||
if line:byte(-1) == 13 then
|
if line:byte(-1) == 13 then
|
||||||
|
@ -93,7 +81,9 @@ function Doc:save(filename)
|
||||||
fp:write(line)
|
fp:write(line)
|
||||||
end
|
end
|
||||||
fp:close()
|
fp:close()
|
||||||
self.filename = filename or self.filename
|
if filename then
|
||||||
|
self:set_filename(filename)
|
||||||
|
end
|
||||||
self:reset_syntax()
|
self:reset_syntax()
|
||||||
self:clean()
|
self:clean()
|
||||||
end
|
end
|
||||||
|
@ -118,45 +108,96 @@ function Doc:get_change_id()
|
||||||
return self.undo_stack.idx
|
return self.undo_stack.idx
|
||||||
end
|
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)
|
function Doc:get_selection_idx(idx, sort)
|
||||||
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
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
|
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||||
line1, col1 = self:sanitize_position(line1, col1)
|
line1, col1 = self:sanitize_position(line1, col1)
|
||||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
||||||
self.selection.a.line, self.selection.a.col = line1, col1
|
common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 })
|
||||||
self.selection.b.line, self.selection.b.col = line2, col2
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Doc:add_selection(line1, col1, line2, col2, swap)
|
||||||
local function sort_positions(line1, col1, line2, col2)
|
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
|
||||||
if line1 > line2
|
local target = #self.selections / 4 + 1
|
||||||
or line1 == line2 and col1 > col2 then
|
for idx, tl1, tc1 in self:get_selections(true) do
|
||||||
return line2, col2, line1, col1, true
|
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
|
||||||
|
target = idx
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return line1, col1, line2, col2, false
|
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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:get_selection(sort)
|
function Doc:merge_cursors(idx)
|
||||||
local a, b = self.selection.a, self.selection.b
|
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
|
||||||
if sort then
|
for j = 1, i - 4, 4 do
|
||||||
return sort_positions(a.line, a.col, b.line, b.col)
|
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
|
||||||
return a.line, a.col, b.line, b.col
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function selection_iterator(invariant, idx)
|
||||||
function Doc:has_selection()
|
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
||||||
local a, b = self.selection.a, self.selection.b
|
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
||||||
return not (a.line == b.line and a.col == b.col)
|
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
|
end
|
||||||
|
|
||||||
|
-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate.
|
||||||
function Doc:sanitize_selection()
|
-- If a number, runs for exactly that iteration.
|
||||||
self:set_selection(self:get_selection())
|
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
|
||||||
|
-- End of cursor seciton.
|
||||||
|
|
||||||
function Doc:sanitize_position(line, col)
|
function Doc:sanitize_position(line, col)
|
||||||
line = common.clamp(line, 1, #self.lines)
|
line = common.clamp(line, 1, #self.lines)
|
||||||
|
@ -233,7 +274,7 @@ local function push_undo(undo_stack, time, type, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function pop_undo(self, undo_stack, redo_stack)
|
local function pop_undo(self, undo_stack, redo_stack, modified)
|
||||||
-- pop command
|
-- pop command
|
||||||
local cmd = undo_stack[undo_stack.idx - 1]
|
local cmd = undo_stack[undo_stack.idx - 1]
|
||||||
if not cmd then return end
|
if not cmd then return end
|
||||||
|
@ -243,21 +284,24 @@ local function pop_undo(self, undo_stack, redo_stack)
|
||||||
if cmd.type == "insert" then
|
if cmd.type == "insert" then
|
||||||
local line, col, text = table.unpack(cmd)
|
local line, col, text = table.unpack(cmd)
|
||||||
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
||||||
|
|
||||||
elseif cmd.type == "remove" then
|
elseif cmd.type == "remove" then
|
||||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||||
|
|
||||||
elseif cmd.type == "selection" then
|
elseif cmd.type == "selection" then
|
||||||
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
|
self.selections = { unpack(cmd) }
|
||||||
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
modified = modified or (cmd.type ~= "selection")
|
||||||
|
|
||||||
-- if next undo command is within the merge timeout then treat as a single
|
-- if next undo command is within the merge timeout then treat as a single
|
||||||
-- command and continue to execute it
|
-- command and continue to execute it
|
||||||
local next = undo_stack[undo_stack.idx - 1]
|
local next = undo_stack[undo_stack.idx - 1]
|
||||||
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -274,11 +318,11 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||||
lines[#lines] = lines[#lines] .. after
|
lines[#lines] = lines[#lines] .. after
|
||||||
|
|
||||||
-- splice lines into line array
|
-- splice lines into line array
|
||||||
splice(self.lines, line, 1, lines)
|
common.splice(self.lines, line, 1, lines)
|
||||||
|
|
||||||
-- push undo
|
-- push undo
|
||||||
local line2, col2 = self:position_offset(line, col, #text)
|
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)
|
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||||
|
|
||||||
-- update highlighter and assure selection is in bounds
|
-- update highlighter and assure selection is in bounds
|
||||||
|
@ -290,7 +334,7 @@ end
|
||||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||||
-- push undo
|
-- push undo
|
||||||
local text = self:get_text(line1, col1, line2, col2)
|
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)
|
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||||
|
|
||||||
-- get line content before/after removed 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)
|
local after = self.lines[line2]:sub(col2)
|
||||||
|
|
||||||
-- splice line into line array
|
-- 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
|
-- update highlighter and assure selection is in bounds
|
||||||
self.highlighter:invalidate(line1)
|
self.highlighter:invalidate(line1)
|
||||||
|
@ -310,6 +354,7 @@ function Doc:insert(line, col, text)
|
||||||
self.redo_stack = { idx = 1 }
|
self.redo_stack = { idx = 1 }
|
||||||
line, col = self:sanitize_position(line, col)
|
line, col = self:sanitize_position(line, col)
|
||||||
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
||||||
|
self:on_text_change("insert")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -319,35 +364,34 @@ function Doc:remove(line1, col1, line2, col2)
|
||||||
line2, col2 = self:sanitize_position(line2, col2)
|
line2, col2 = self:sanitize_position(line2, col2)
|
||||||
line1, col1, line2, col2 = sort_positions(line1, col1, 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:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
|
||||||
|
self:on_text_change("remove")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:undo()
|
function Doc:undo()
|
||||||
pop_undo(self, self.undo_stack, self.redo_stack)
|
pop_undo(self, self.undo_stack, self.redo_stack, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:redo()
|
function Doc:redo()
|
||||||
pop_undo(self, self.redo_stack, self.undo_stack)
|
pop_undo(self, self.redo_stack, self.undo_stack, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:text_input(text)
|
function Doc:text_input(text, idx)
|
||||||
if self:has_selection() then
|
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||||
self:delete_to()
|
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
|
end
|
||||||
local line, col = self:get_selection()
|
|
||||||
self:insert(line, col, text)
|
|
||||||
self:move_to(#text)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:replace(fn)
|
function Doc:replace(fn)
|
||||||
local line1, col1, line2, col2, swap
|
local line1, col1, line2, col2 = self:get_selection(true)
|
||||||
local had_selection = self:has_selection()
|
if line1 == line2 and col1 == col2 then
|
||||||
if had_selection then
|
|
||||||
line1, col1, line2, col2, swap = self:get_selection(true)
|
|
||||||
else
|
|
||||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||||
end
|
end
|
||||||
local old_text = self:get_text(line1, col1, line2, col2)
|
local old_text = self:get_text(line1, col1, line2, col2)
|
||||||
|
@ -355,38 +399,108 @@ function Doc:replace(fn)
|
||||||
if old_text ~= new_text then
|
if old_text ~= new_text then
|
||||||
self:insert(line2, col2, new_text)
|
self:insert(line2, col2, new_text)
|
||||||
self:remove(line1, col1, line2, col2)
|
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)
|
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
|
||||||
end
|
end
|
||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:delete_to(...)
|
function Doc:delete_to_cursor(idx, ...)
|
||||||
local line, col = self:get_selection(true)
|
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||||
if self:has_selection() then
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
self:remove(self:get_selection())
|
self:remove(line1, col1, line2, col2)
|
||||||
else
|
else
|
||||||
local line2, col2 = self:position_offset(line, col, ...)
|
local l2, c2 = self:position_offset(line1, col1, ...)
|
||||||
self:remove(line, col, line2, col2)
|
self:remove(line1, col1, l2, c2)
|
||||||
line, col = sort_positions(line, col, line2, col2)
|
line1, col1 = sort_positions(line1, col1, l2, c2)
|
||||||
|
end
|
||||||
|
self:set_selections(sidx, line1, col1)
|
||||||
end
|
end
|
||||||
self:set_selection(line, col)
|
self:merge_cursors(idx)
|
||||||
|
end
|
||||||
|
function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end
|
||||||
|
|
||||||
|
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_cursor(idx, ...)
|
||||||
|
for sidx, line, col, line2, col2 in self:get_selections(false, idx) do
|
||||||
|
line, col = self:position_offset(line, col, ...)
|
||||||
|
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
|
end
|
||||||
|
|
||||||
|
-- returns the size of the original indent, and the indent
|
||||||
function Doc:move_to(...)
|
-- in your config format, rounded either up or down
|
||||||
local line, col = self:get_selection()
|
local function get_line_indent(line, rnd_up)
|
||||||
self:set_selection(self:position_offset(line, col, ...))
|
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
|
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
|
||||||
|
|
||||||
function Doc:select_to(...)
|
-- For plugins to add custom actions of document change
|
||||||
local line, col, line2, col2 = self:get_selection()
|
function Doc:on_text_change(type)
|
||||||
line, col = self:position_offset(line, col, ...)
|
|
||||||
self:set_selection(line, col, line2, col2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ function DocView:new(doc)
|
||||||
self.doc = assert(doc)
|
self.doc = assert(doc)
|
||||||
self.font = "code_font"
|
self.font = "code_font"
|
||||||
self.last_x_offset = {}
|
self.last_x_offset = {}
|
||||||
self.blink_timer = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,10 +225,14 @@ function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local line, col = self:resolve_screen_position(x, y)
|
local line, col = self:resolve_screen_position(x, y)
|
||||||
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
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 }
|
self.mouse_selecting = { line, col, clicks = clicks }
|
||||||
end
|
end
|
||||||
self.blink_timer = 0
|
core.blink_timer = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -246,7 +249,15 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||||
local l1, c1 = self:resolve_screen_position(x, y)
|
local l1, c1 = self:resolve_screen_position(x, y)
|
||||||
local l2, c2 = table.unpack(self.mouse_selecting)
|
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||||
local clicks = self.mouse_selecting.clicks
|
local clicks = self.mouse_selecting.clicks
|
||||||
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -269,18 +280,18 @@ function DocView:update()
|
||||||
if core.active_view == self then
|
if core.active_view == self then
|
||||||
self:scroll_to_make_visible(line, col)
|
self:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
self.blink_timer = 0
|
core.blink_timer = 0
|
||||||
self.last_line, self.last_col = line, col
|
self.last_line, self.last_col = line, col
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update blink timer
|
-- update blink timer
|
||||||
if self == core.active_view and not self.mouse_selecting then
|
if self == core.active_view and not self.mouse_selecting then
|
||||||
local n = config.blink_period / 2
|
local T, t0 = config.blink_period, core.blink_start
|
||||||
local prev = self.blink_timer
|
local ta, tb = core.blink_timer, system.get_time()
|
||||||
self.blink_timer = (self.blink_timer + 1 / config.fps) % config.blink_period
|
if ((tb - t0) % T < T / 2) ~= ((ta - t0) % T < T / 2) then
|
||||||
if (self.blink_timer > n) ~= (prev > n) then
|
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
|
core.blink_timer = tb
|
||||||
end
|
end
|
||||||
|
|
||||||
DocView.super.update(self)
|
DocView.super.update(self)
|
||||||
|
@ -304,45 +315,50 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_body(idx, x, y)
|
function DocView:draw_line_body(idx, x, y)
|
||||||
local line, col = self.doc:get_selection()
|
|
||||||
|
|
||||||
-- draw selection if it overlaps this line
|
-- 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
|
if idx >= line1 and idx <= line2 then
|
||||||
local text = self.doc.lines[idx]
|
local text = self.doc.lines[idx]
|
||||||
if line1 ~= idx then col1 = 1 end
|
if line1 ~= idx then col1 = 1 end
|
||||||
if line2 ~= idx then col2 = #text + 1 end
|
if line2 ~= idx then col2 = #text + 1 end
|
||||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||||
-- draw line highlight if caret is on this line
|
-- draw line highlight if caret is on this line
|
||||||
if config.highlight_current_line and not self.doc:has_selection()
|
if config.highlight_current_line and (line1 == line2 and col1 == col2)
|
||||||
and line == idx and core.active_view == self then
|
and line1 == idx and core.active_view == self then
|
||||||
self:draw_line_highlight(x + self.scroll.x, y)
|
self:draw_line_highlight(x + self.scroll.x, y)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw line's text
|
-- draw line's text
|
||||||
self:draw_line_text(idx, x, y)
|
self:draw_line_text(idx, x, y)
|
||||||
|
|
||||||
-- draw caret if it overlaps this line
|
-- draw caret if it overlaps this line
|
||||||
if line == idx and core.active_view == self
|
local T = config.blink_period
|
||||||
and self.blink_timer < config.blink_period / 2
|
for _, line, col in self.doc:get_selections() do
|
||||||
and system.window_has_focus() then
|
if line == idx and core.active_view == self
|
||||||
local lh = self:get_line_height()
|
and (core.blink_timer - core.blink_start) % T < T / 2
|
||||||
local x1 = x + self:get_col_x_offset(line, col)
|
and system.window_has_focus() then
|
||||||
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_gutter(idx, x, y)
|
function DocView:draw_line_gutter(idx, x, y)
|
||||||
local color = style.line_number
|
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
|
if idx >= line1 and idx <= line2 then
|
||||||
color = style.line_number2
|
color = style.line_number2
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local yoffset = self:get_line_text_y_offset()
|
local yoffset = self:get_line_text_y_offset()
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
|
|
|
@ -108,6 +108,8 @@ function core.init()
|
||||||
core.docs = {}
|
core.docs = {}
|
||||||
core.threads = setmetatable({}, { __mode = "k" })
|
core.threads = setmetatable({}, { __mode = "k" })
|
||||||
core.project_files = {}
|
core.project_files = {}
|
||||||
|
core.blink_start = system.get_time()
|
||||||
|
core.blink_timer = core.blink_start
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
|
|
||||||
core.root_view = RootView()
|
core.root_view = RootView()
|
||||||
|
@ -252,6 +254,7 @@ function core.add_thread(f, weak_ref)
|
||||||
local key = weak_ref or #core.threads + 1
|
local key = weak_ref or #core.threads + 1
|
||||||
local fn = function() return core.try(f) end
|
local fn = function() return core.try(f) end
|
||||||
core.threads[key] = { cr = coroutine.create(fn), wake = 0 }
|
core.threads[key] = { cr = coroutine.create(fn), wake = 0 }
|
||||||
|
return key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -493,6 +496,10 @@ function core.run()
|
||||||
end
|
end
|
||||||
--< @r-lyeh } split core.run() into core.run1()
|
--< @r-lyeh } split core.run() into core.run1()
|
||||||
|
|
||||||
|
function core.blink_reset()
|
||||||
|
core.blink_start = system.get_time()
|
||||||
|
end
|
||||||
|
|
||||||
function core.on_error(err)
|
function core.on_error(err)
|
||||||
-- write error to file
|
-- write error to file
|
||||||
local fp = io.open(EXEDIR .. "/error.txt", "wb")
|
local fp = io.open(EXEDIR .. "/error.txt", "wb")
|
||||||
|
|
|
@ -127,6 +127,8 @@ keymap.add {
|
||||||
["ctrl+x"] = "doc:cut",
|
["ctrl+x"] = "doc:cut",
|
||||||
["ctrl+c"] = "doc:copy",
|
["ctrl+c"] = "doc:copy",
|
||||||
["ctrl+v"] = "doc:paste",
|
["ctrl+v"] = "doc:paste",
|
||||||
|
["ctrl+insert"] = "doc:copy",
|
||||||
|
["shift+insert"] = "doc:paste",
|
||||||
["escape"] = { "command:escape", "doc:select-none" },
|
["escape"] = { "command:escape", "doc:select-none" },
|
||||||
["tab"] = { "command:complete", "doc:indent" },
|
["tab"] = { "command:complete", "doc:indent" },
|
||||||
["shift+tab"] = "doc:unindent",
|
["shift+tab"] = "doc:unindent",
|
||||||
|
@ -144,7 +146,7 @@ keymap.add {
|
||||||
["ctrl+shift+return"] = "doc:newline-above",
|
["ctrl+shift+return"] = "doc:newline-above",
|
||||||
["ctrl+j"] = "doc:join-lines",
|
["ctrl+j"] = "doc:join-lines",
|
||||||
["ctrl+a"] = "doc:select-all",
|
["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+l"] = "doc:select-lines",
|
||||||
["ctrl+/"] = "doc:toggle-line-comments",
|
["ctrl+/"] = "doc:toggle-line-comments",
|
||||||
["ctrl+up"] = "doc:move-lines-up",
|
["ctrl+up"] = "doc:move-lines-up",
|
||||||
|
@ -181,6 +183,8 @@ keymap.add {
|
||||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||||
["shift+pageup"] = "doc:select-to-previous-page",
|
["shift+pageup"] = "doc:select-to-previous-page",
|
||||||
["shift+pagedown"] = "doc:select-to-next-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
|
return keymap
|
||||||
|
|
|
@ -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
|
|
@ -5,378 +5,13 @@ local keymap = require "core.keymap"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
local Node = require "core.node"
|
||||||
local config = require "core.config"
|
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()
|
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)
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
if idx then
|
if idx then
|
||||||
node:set_active_view(node.views[idx])
|
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)
|
node:close_active_view(self.root_node)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -488,7 +123,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function RootView:update()
|
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()
|
||||||
self.root_node:update_layout()
|
self.root_node:update_layout()
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,6 +18,8 @@ function StatusView:new()
|
||||||
StatusView.super.new(self)
|
StatusView.super.new(self)
|
||||||
self.message_timeout = 0
|
self.message_timeout = 0
|
||||||
self.message = {}
|
self.message = {}
|
||||||
|
self.tooltip_mode = false
|
||||||
|
self.tooltip = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +41,17 @@ function StatusView:show_message(icon, icon_color, text)
|
||||||
end
|
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()
|
function StatusView:update()
|
||||||
self.size.y = style.font:get_height() + style.padding.y * 2
|
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 dv = core.active_view
|
||||||
local line, col = dv.doc:get_selection()
|
local line, col = dv.doc:get_selection()
|
||||||
local dirty = dv.doc:is_dirty()
|
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
|
--< https://github.com/rxi/lite/issues/300
|
||||||
col = common.utf8_len(dv.doc:get_text(line, 1, line, col)) + 1
|
col = common.utf8_len(dv.doc:get_text(line, 1, line, col)) + 1
|
||||||
|
@ -112,6 +128,8 @@ function StatusView:get_items()
|
||||||
self.separator,
|
self.separator,
|
||||||
string.format("%.f%%", line / #dv.doc.lines * 100), --< @r-lyeh: %d -> %.f
|
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.icon_font, "g",
|
||||||
style.font, style.dim, self.separator2, style.text,
|
style.font, style.dim, self.separator2, style.text,
|
||||||
#dv.doc.lines, " lines",
|
#dv.doc.lines, " lines",
|
||||||
|
@ -136,9 +154,13 @@ function StatusView:draw()
|
||||||
self:draw_items(self.message, false, self.size.y)
|
self:draw_items(self.message, false, self.size.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
local left, right = self:get_items()
|
if self.tooltip_mode then
|
||||||
self:draw_items(left)
|
self:draw_items(self.tooltip)
|
||||||
self:draw_items(right, true)
|
else
|
||||||
|
local left, right = self:get_items()
|
||||||
|
self:draw_items(left)
|
||||||
|
self:draw_items(right, true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -134,12 +134,12 @@ Plugins can be downloaded from the [plugins repository](https://github.com/rxi/l
|
||||||
|
|
||||||
## Color Themes
|
## Color Themes
|
||||||
Colors themes in lite are lua modules which overwrite the color fields of lite's
|
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.
|
directory.
|
||||||
|
|
||||||
A color theme can be set by requiring it in your user module:
|
A color theme can be set by requiring it in your user module:
|
||||||
```lua
|
```lua
|
||||||
require "user.colors.winter"
|
require "themes.winter"
|
||||||
```
|
```
|
||||||
|
|
||||||
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).
|
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||||
|
})
|
|
@ -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
|
|
@ -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
|
|
|
@ -1,25 +1,31 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
|
local Doc = require "core.doc"
|
||||||
local DocView = require "core.docview"
|
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)
|
local function lerp(a, b, t)
|
||||||
return a + (b - a) * t
|
return a + (b - a) * t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_caret_rect(dv)
|
local function get_caret_rect(dv, idx)
|
||||||
local line, col = dv.doc:get_selection()
|
local line1, col1, line2, col2 = doc():get_selection_idx(idx)
|
||||||
local x, y = dv:get_line_screen_position(line)
|
local x1, y1 = dv:get_line_screen_position(line1)
|
||||||
x = x + dv:get_col_x_offset(line, col)
|
x1 = x1 + dv:get_col_x_offset(line1, col1)
|
||||||
return x, y, style.caret_width, dv:get_line_height()
|
return x1, y1, style.caret_width, dv:get_line_height()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local last_x, last_y, last_view
|
local last_x = {}
|
||||||
|
local last_y = {}
|
||||||
|
local last_view = {}
|
||||||
|
|
||||||
local draw = DocView.draw
|
local draw = DocView.draw
|
||||||
|
|
||||||
|
@ -27,20 +33,24 @@ function DocView:draw(...)
|
||||||
draw(self, ...)
|
draw(self, ...)
|
||||||
if self ~= core.active_view then return end
|
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)
|
||||||
local lx = x
|
|
||||||
for i = 0, 1, 1 / config.motiontrail_steps do
|
if last_view[idx] == self and (x ~= last_x[idx] or y ~= last_y[idx]) then
|
||||||
local ix = lerp(x, last_x, i)
|
local lx = x
|
||||||
local iy = lerp(y, last_y, i)
|
for i = 0, 1, 1 / config.motiontrail_steps do
|
||||||
local iw = math.max(w, math.ceil(math.abs(ix - lx)))
|
local ix = lerp(x, last_x[idx], i)
|
||||||
renderer.draw_rect(ix, iy, iw, h, style.caret)
|
local iy = lerp(y, last_y[idx], i)
|
||||||
lx = ix
|
local iw = math.max(w, math.ceil(math.abs(ix - lx)))
|
||||||
|
renderer.draw_rect(ix, iy, iw, h, style.caret)
|
||||||
|
lx = ix
|
||||||
|
end
|
||||||
|
core.redraw = true
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
|
@ -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" }
|
||||||
|
|
|
@ -22,7 +22,7 @@ local TreeView = View:extend()
|
||||||
function TreeView:new()
|
function TreeView:new()
|
||||||
TreeView.super.new(self)
|
TreeView.super.new(self)
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
self.visible = true
|
self.visible = false --< @r-lyeh true>false
|
||||||
self.init_size = true
|
self.init_size = true
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
end
|
end
|
||||||
|
@ -185,7 +185,7 @@ end
|
||||||
-- init
|
-- init
|
||||||
local view = TreeView()
|
local view = TreeView()
|
||||||
local node = core.root_view:get_active_node()
|
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
|
-- register commands and keymap
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
|
@ -195,3 +195,100 @@ command.add(nil, {
|
||||||
})
|
})
|
||||||
|
|
||||||
keymap.add { ["ctrl+t"] = "treeview:toggle" } --< @r-lyeh ctrl+// > ctrl+t
|
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
|
||||||
|
|
|
@ -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
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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); }
|
char* boy_save(boy *self) { return obj_saveini(self); } // PACKMSG("ssf", "boy_v1", self->name, self->hp); }
|
||||||
|
|
||||||
AUTOTEST {
|
AUTOTEST {
|
||||||
obj_extend(orc, ctor);
|
EXTEND(orc, ctor,tick,save);
|
||||||
obj_extend(orc, tick);
|
EXTEND(boy, ctor,tick,save);
|
||||||
obj_extend(orc, save);
|
|
||||||
|
|
||||||
obj_extend(boy, ctor);
|
|
||||||
obj_extend(boy, tick);
|
|
||||||
obj_extend(boy, save);
|
|
||||||
|
|
||||||
// instance gameobjs
|
// instance gameobjs
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -410,13 +410,17 @@ void editor_inspect(obj *o) {
|
||||||
ui_label_icon_highlight = editor_changed(PTR); // @hack: remove ui_label_icon_highlight hack
|
ui_label_icon_highlight = editor_changed(PTR); // @hack: remove ui_label_icon_highlight hack
|
||||||
char *label = va(ICON_MD_UNDO "%s", NAME);
|
char *label = va(ICON_MD_UNDO "%s", NAME);
|
||||||
int changed = 0;
|
int changed = 0;
|
||||||
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
|
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
|
||||||
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
|
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
|
||||||
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
|
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
|
||||||
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(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,"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,"char*") ) changed = ui_string(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)?
|
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
|
||||||
if( changed ) {
|
if( changed ) {
|
||||||
editor_setchanged(PTR, 1);
|
editor_setchanged(PTR, 1);
|
||||||
|
@ -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_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;
|
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;
|
int ingame = !editor.active;
|
||||||
UI_MENU(14, \
|
static double clicked_titlebar = 0;
|
||||||
|
UI_MENU(14+is_borderless, \
|
||||||
if(ingame) ui_disable(); \
|
if(ingame) ui_disable(); \
|
||||||
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
|
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
|
||||||
if(ingame) ui_enable(); \
|
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_STOP, editor_send("stop")) \
|
||||||
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
|
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
|
||||||
UI_MENU_ITEM(STATS, stats_mode = (++stats_mode) % 3) \
|
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(); \
|
if(ingame) ui_disable(); \
|
||||||
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
|
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
|
||||||
UI_MENU_ITEM(ICON_MDI_SCRIPT_TEXT, editor_send("script")) \
|
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")) \
|
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;
|
if( !editor.active ) return;
|
||||||
|
|
||||||
// draw edit view (gizmos, position markers, etc).
|
// draw edit view (gizmos, position markers, etc).
|
||||||
|
|
|
@ -7,14 +7,18 @@
|
||||||
#include "3rd_lite.h"
|
#include "3rd_lite.h"
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
TODO("new: integrate with Art/ browser")
|
||||||
TODO("bug: lite key bindings are being sent to editor")
|
TODO("bug: lite key bindings are being sent to editor")
|
||||||
TODO("eval: https://github.com/Jipok/lite-plugins")
|
TODO("bug: not sending quit signal to lite neither at window close nor editor close (see: temporary files)")
|
||||||
TODO("eval: https://github.com/drmargarido/linters")
|
TODO("bug: missing search results window")
|
||||||
TODO("eval: https://github.com/monolifed/theme16")
|
TODO("bug: missing code completions popup")
|
||||||
TODO("eval: https://github.com/takase1121/lite-contextmenu ")
|
// TODO("eval: https://github.com/drmargarido/linters")
|
||||||
TODO("eval: https://github.com/drmargarido/TodoTreeView")
|
// TODO("eval: https://github.com/monolifed/theme16")
|
||||||
TODO("eval: https://github.com/takase1121/lite-nagbar")
|
// TODO("eval: https://github.com/vincens2005/lite-formatters")
|
||||||
TODO("eval: https://github.com/rxi/console")
|
// 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) {
|
int ui_texture_fit(texture_t t, struct nk_rect bounds) {
|
||||||
// allocate complete window space
|
// allocate complete window space
|
||||||
|
@ -56,7 +60,7 @@ int editor_scripted(int window_mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned lt_none = 0u;
|
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;
|
lt_events = lt_none;
|
||||||
|
|
||||||
int mouse_in_rect = 0;
|
int mouse_in_rect = 0;
|
||||||
|
@ -80,7 +84,7 @@ int editor_scripted(int window_mode) {
|
||||||
// fullscreen_quad_rgb( lt_getsurface(0)->t, 1.2f );
|
// fullscreen_quad_rgb( lt_getsurface(0)->t, 1.2f );
|
||||||
ui_texture_fit(lt_getsurface(0)->t, bounds);
|
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
|
lt_events &= ~(1<<31); // dont cursor shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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++ );
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ x=0.000000
|
||||||
y=0.052117
|
y=0.052117
|
||||||
w=0.209707
|
w=0.209707
|
||||||
h=0.946108
|
h=0.946108
|
||||||
visible=1
|
visible=0
|
||||||
[Nodes ]
|
[Nodes ]
|
||||||
x=0.000916
|
x=0.000916
|
||||||
y=0.053746
|
y=0.053746
|
||||||
|
|
Loading…
Reference in New Issue