276 lines
10 KiB
C
276 lines
10 KiB
C
// lite editor, platform details
|
|
// - rlyeh, public domain
|
|
|
|
#define LT_DATAPATH "/lite"
|
|
|
|
#define lt_assert(x) ASSERT(x)
|
|
|
|
#define lt_realpath(p, q) file_pathabs(p)
|
|
#define lt_realpath_free(p)
|
|
|
|
#define lt_malloc(n) MALLOC(n)
|
|
#define lt_calloc(n,m) CALLOC(n,m)
|
|
#define lt_free(p) FREE(p)
|
|
#define lt_memcpy(d,s,c) memcpy(d,s,c)
|
|
#define lt_memset(p,ch,c) memset(p,ch,c)
|
|
|
|
#define lt_time_ms() time_ms()
|
|
#define lt_sleep_ms(ms) sleep_ms(ms)
|
|
|
|
#define lt_getclipboard(w) window_clipboard()
|
|
#define lt_setclipboard(w,s) window_setclipboard(s)
|
|
|
|
#define lt_window() window_handle()
|
|
#define lt_setwindowmode(m) window_fullscreen(m == 2), (m < 2 && (window_maximize(m),1)) // 0:normal,1:maximized,2:fullscreen
|
|
#define lt_setwindowtitle(t) //window_title(t)
|
|
#define lt_haswindowfocus() window_has_focus()
|
|
#define lt_setcursor(shape) window_cursor_shape(lt_events & (1<<31) ? CURSOR_SW_AUTO : shape+1) // 0:arrow,1:ibeam,2:sizeh,3:sizev,4:hand
|
|
|
|
#define lt_prompt(msg,title) ifndef(win32, 0, (MessageBoxA(0, msg, title, MB_YESNO | MB_ICONWARNING) == IDYES))
|
|
|
|
unsigned lt_events = ~0u;
|
|
int lt_mx = 0, lt_my = 0, lt_wx = 0, lt_wy = 0, lt_ww = 0, lt_wh = 0;
|
|
|
|
typedef struct lt_surface {
|
|
unsigned w, h;
|
|
void *pixels;
|
|
texture_t t;
|
|
} lt_surface;
|
|
|
|
typedef struct lt_rect {
|
|
int x, y, width, height;
|
|
} lt_rect;
|
|
|
|
lt_surface *lt_getsurface(void *window) {
|
|
static lt_surface s = {0};
|
|
return &s;
|
|
}
|
|
void lt_updatesurfacerects(lt_surface *s, lt_rect* rects, unsigned count) {
|
|
if(0)
|
|
for( int i = 0; i < count; ++i ) {
|
|
memset( (unsigned*)s->pixels + (rects[i].x + rects[i].y * s->w), 0xFF, rects[i].width*4 );
|
|
memset( (unsigned*)s->pixels + (rects[i].x + (rects[i].y + (rects[i].height-1)) * s->w), 0xFF, rects[i].width*4 );
|
|
for( int y = 1; y < (rects[i].height-1); ++y ) {
|
|
((unsigned*)s->pixels)[ rects[i].x + y * s->w ] =
|
|
((unsigned*)s->pixels)[ rects[i].x + (rects[i].width-1) + y * s->w ] = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
// update contents
|
|
texture_update(&s->t, s->w, s->h, 4, s->pixels, TEXTURE_LINEAR|TEXTURE_BGRA);
|
|
}
|
|
|
|
void ren_set_clip_rect(struct lt_rect rect);
|
|
void rencache_invalidate(void);
|
|
int lt_resizesurface(lt_surface *s, int ww, int wh) {
|
|
s->w = ww, s->h = wh;
|
|
if( s->t.id == 0 || s->w != s->t.w || s->h != s->t.h ) {
|
|
// invalidate tiles
|
|
ren_set_clip_rect( (lt_rect) { 0, 0, s->w, s->h } );
|
|
rencache_invalidate();
|
|
|
|
// texture clear
|
|
if( !s->t.id ) s->t = texture_create(1, 1, 4, " ", TEXTURE_LINEAR|TEXTURE_RGBA|TEXTURE_BYTE );
|
|
s->pixels = REALLOC(s->pixels, s->w * s->h * 4);
|
|
memset(s->pixels, 0, s->w * s->h * 4);
|
|
|
|
// texture update
|
|
lt_updatesurfacerects(s,0,0);
|
|
return 1; // resized
|
|
}
|
|
return 0; // unchanged
|
|
}
|
|
|
|
void *lt_load_file(const char *filename, int *size) {
|
|
int datalen; char *data = file_load(filename, &datalen);
|
|
if( !data || !datalen ) {
|
|
filename = (const char *)file_normalize(filename);
|
|
if( strbegi(filename, app_path()) ) filename += strlen(app_path());
|
|
data = vfs_load(filename, &datalen);
|
|
}
|
|
if (size) *size = 0;
|
|
if (!data) { return NULL; }
|
|
if (size) *size = datalen;
|
|
// return permanent buffers here, as file_load() and vfs_load() do return temporaries
|
|
data = memcpy(MALLOC(datalen+1), data, datalen);
|
|
data[datalen] = 0;
|
|
return data;
|
|
}
|
|
|
|
const char* lt_button_name(int button) {
|
|
if(button == GLFW_MOUSE_BUTTON_LEFT) return "left";
|
|
if(button == GLFW_MOUSE_BUTTON_RIGHT) return "right";
|
|
if(button == GLFW_MOUSE_BUTTON_MIDDLE) return "middle";
|
|
return "?";
|
|
}
|
|
|
|
char* lt_key_name(char *dst, int key, int vk, int mods) {
|
|
// @todo: "altgr" -> left ctrl + right alt
|
|
|
|
if( key == GLFW_KEY_UP ) return "up";
|
|
if( key == GLFW_KEY_DOWN ) return "down";
|
|
if( key == GLFW_KEY_LEFT ) return "left";
|
|
if( key == GLFW_KEY_RIGHT ) return "right";
|
|
if( key == GLFW_KEY_LEFT_ALT ) return "left alt";
|
|
if( key == GLFW_KEY_RIGHT_ALT ) return "right alt";
|
|
if( key == GLFW_KEY_LEFT_SHIFT ) return "left shift";
|
|
if( key == GLFW_KEY_RIGHT_SHIFT ) return "right shift";
|
|
if( key == GLFW_KEY_LEFT_CONTROL ) return "left ctrl";
|
|
if( key == GLFW_KEY_RIGHT_CONTROL ) return "right ctrl";
|
|
if( key == GLFW_KEY_LEFT_SUPER ) return "left windows";
|
|
if( key == GLFW_KEY_RIGHT_SUPER ) return "left windows";
|
|
if( key == GLFW_KEY_MENU ) return "menu";
|
|
|
|
if( key == GLFW_KEY_ESCAPE ) return "escape";
|
|
if( key == GLFW_KEY_BACKSPACE ) return "backspace";
|
|
if( key == GLFW_KEY_ENTER ) return "return";
|
|
if( key == GLFW_KEY_KP_ENTER ) return "keypad enter";
|
|
if( key == GLFW_KEY_TAB ) return "tab";
|
|
if( key == GLFW_KEY_CAPS_LOCK ) return "capslock";
|
|
|
|
if( key == GLFW_KEY_HOME ) return "home";
|
|
if( key == GLFW_KEY_END ) return "end";
|
|
if( key == GLFW_KEY_INSERT ) return "insert";
|
|
if( key == GLFW_KEY_DELETE ) return "delete";
|
|
if( key == GLFW_KEY_PAGE_UP ) return "pageup";
|
|
if( key == GLFW_KEY_PAGE_DOWN ) return "pagedown";
|
|
|
|
if( key == GLFW_KEY_F1 ) return "f1";
|
|
if( key == GLFW_KEY_F2 ) return "f2";
|
|
if( key == GLFW_KEY_F3 ) return "f3";
|
|
if( key == GLFW_KEY_F4 ) return "f4";
|
|
if( key == GLFW_KEY_F5 ) return "f5";
|
|
if( key == GLFW_KEY_F6 ) return "f6";
|
|
if( key == GLFW_KEY_F7 ) return "f7";
|
|
if( key == GLFW_KEY_F8 ) return "f8";
|
|
if( key == GLFW_KEY_F9 ) return "f9";
|
|
if( key == GLFW_KEY_F10 ) return "f10";
|
|
if( key == GLFW_KEY_F11 ) return "f11";
|
|
if( key == GLFW_KEY_F12 ) return "f12";
|
|
|
|
const char *name = glfwGetKeyName(key, vk);
|
|
strcpy(dst, name ? name : "");
|
|
char *p = dst;
|
|
while (*p) {
|
|
*p = tolower(*p);
|
|
p++;
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
void lt_globpath(struct lua_State*L, const char *path) {
|
|
unsigned j = 0;
|
|
|
|
if(!strend(path, "/")) path = (const char *)va("%s/", path);
|
|
for( dir *d = dir_open(path, ""); d; dir_close(d), d = 0 ) {
|
|
for( unsigned i = 0, end = dir_count(d); i < end; ++i ) {
|
|
char *name = dir_name(d,i);
|
|
char *last = name + strlen(name) - 1;
|
|
if( *last == '/' ) *last = '\0';
|
|
name = file_name(name);
|
|
lua_pushstring(L, name);
|
|
lua_rawseti(L, -2, ++j);
|
|
}
|
|
}
|
|
|
|
for( const char *section = strstri(path, LT_DATAPATH); section && section[sizeof(LT_DATAPATH)-1] == '/'; section = 0) {
|
|
array(char*) list = vfs_list("**");
|
|
for( unsigned i = 0, end = array_count(list); i < end; ++i ) {
|
|
char *name = list[i];
|
|
if( !strstri(name, section+1) ) continue;
|
|
lua_pushstring(L, file_name(name));
|
|
lua_rawseti(L, -2, ++j);
|
|
}
|
|
if( array_count(list) ) return;
|
|
}
|
|
}
|
|
|
|
int lt_emit_event(lua_State *L, const char *event_name, const char *event_fmt, ...) {
|
|
int count = 0;
|
|
lua_pushstring(L, event_name);
|
|
if( event_fmt ) {
|
|
va_list va;
|
|
va_start(va, event_fmt);
|
|
for( ; event_fmt[count]; ++count ) {
|
|
/**/ if( event_fmt[count] == 'd' ) { int d = va_arg(va, int); lua_pushnumber(L, d); }
|
|
else if( event_fmt[count] == 'f' ) { double f = va_arg(va, double); lua_pushnumber(L, f); }
|
|
else if( event_fmt[count] == 's' ) { const char *s = va_arg(va, const char *); lua_pushstring(L, s); }
|
|
}
|
|
va_end(va);
|
|
}
|
|
return 1+count;
|
|
}
|
|
|
|
int printi(int i) {
|
|
// printf("clicks: %d\n", i);
|
|
return i;
|
|
}
|
|
|
|
static const char* codepoint_to_utf8_(unsigned c);
|
|
int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext
|
|
int rc = 0;
|
|
char buf[16];
|
|
static int prevx = 0, prevy = 0;
|
|
|
|
static unsigned clicks_time = 0, clicks = 0;
|
|
if( (lt_time_ms() - clicks_time) > 400 ) clicks = 0;
|
|
|
|
//
|
|
|
|
for( GLEQevent e; gleqNextEvent(&e); gleqFreeEvent(&e) )
|
|
if( lt_events & e.type )
|
|
switch (e.type) {
|
|
default:
|
|
break; case GLEQ_WINDOW_CLOSED: // it used to be ok. depends on window_swap() flow
|
|
rc += lt_emit_event(L, "quit", NULL);
|
|
return rc;
|
|
|
|
break; case GLEQ_WINDOW_MOVED:
|
|
lt_wx = e.pos.x;
|
|
lt_wy = e.pos.y;
|
|
|
|
break; case GLEQ_WINDOW_RESIZED:
|
|
rc += lt_emit_event(L, "resized", "dd", lt_ww = e.size.width, lt_wh = e.size.height);
|
|
lt_resizesurface(lt_getsurface(lt_window()), lt_ww, lt_wh);
|
|
|
|
break; case GLEQ_WINDOW_REFRESH:
|
|
rc += lt_emit_event(L, "exposed", NULL);
|
|
rencache_invalidate();
|
|
|
|
break; case GLEQ_FILE_DROPPED:
|
|
rc += lt_emit_event(L, "filedropped", "sdd", e.file.paths[0], lt_mx, lt_my);
|
|
|
|
break; case GLEQ_KEY_PRESSED:
|
|
case GLEQ_KEY_REPEATED:
|
|
rc += lt_emit_event(L, "keypressed", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
|
goto bottom;
|
|
|
|
break; case GLEQ_KEY_RELEASED:
|
|
rc += lt_emit_event(L, "keyreleased", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
|
goto bottom;
|
|
|
|
break; case GLEQ_CODEPOINT_INPUT:
|
|
rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint));
|
|
|
|
break; case GLEQ_BUTTON_PRESSED:
|
|
rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks));
|
|
|
|
break; case GLEQ_BUTTON_RELEASED:
|
|
clicks += e.mouse.button == GLFW_MOUSE_BUTTON_1;
|
|
clicks_time = lt_time_ms();
|
|
rc += lt_emit_event(L, "mousereleased", "sdd", lt_button_name(e.mouse.button), lt_mx, lt_my);
|
|
|
|
break; case GLEQ_CURSOR_MOVED:
|
|
lt_mx = e.pos.x - lt_wx, lt_my = e.pos.y - lt_wy;
|
|
rc += lt_emit_event(L, "mousemoved", "dddd", lt_mx, lt_my, lt_mx - prevx, lt_my - prevy);
|
|
prevx = lt_mx, prevy = lt_my;
|
|
|
|
break; case GLEQ_SCROLLED:
|
|
rc += lt_emit_event(L, "mousewheel", "f", e.scroll.y);
|
|
}
|
|
|
|
bottom:;
|
|
|
|
return rc;
|
|
}
|