460 lines
14 KiB
C++
460 lines
14 KiB
C++
bool AppIsRunning = true;
|
|
bool WaitForEvents = true;
|
|
|
|
enum {
|
|
MOUSE_NONE,
|
|
|
|
MOUSE_LEFT,
|
|
MOUSE_RIGHT,
|
|
MOUSE_MIDDLE,
|
|
|
|
MOUSE_LEFT_UP,
|
|
MOUSE_RIGHT_UP,
|
|
MOUSE_MIDDLE_UP,
|
|
};
|
|
|
|
enum EventKind {
|
|
EVENT_NONE,
|
|
EVENT_UPDATE,
|
|
EVENT_QUIT,
|
|
|
|
EVENT_MOUSE_LEFT,
|
|
EVENT_MOUSE_RIGHT,
|
|
EVENT_MOUSE_MIDDLE,
|
|
EVENT_MOUSE_LEFT_UP,
|
|
EVENT_MOUSE_RIGHT_UP,
|
|
EVENT_MOUSE_MIDDLE_UP,
|
|
EVENT_MOUSE_WHEEL,
|
|
|
|
EVENT_KEY_PRESS,
|
|
EVENT_TEXT_INPUT,
|
|
};
|
|
|
|
struct Event {
|
|
EventKind kind;
|
|
SDL_Keycode key;
|
|
int16_t xmouse;
|
|
int16_t ymouse;
|
|
int16_t xwindow;
|
|
int16_t ywindow;
|
|
struct {
|
|
uint8_t shift : 1;
|
|
uint8_t ctrl : 1;
|
|
uint8_t alt : 1;
|
|
uint8_t super : 1;
|
|
uint8_t mouse_double_click : 1;
|
|
};
|
|
Vec2 wheel;
|
|
const char *text;
|
|
};
|
|
|
|
#define Press(KEY) (event.key == KEY)
|
|
#define Ctrl(KEY) (event.key == KEY && event.ctrl)
|
|
#define Shift(KEY) (event.key == KEY && event.shift)
|
|
#define Alt(KEY) (event.key == KEY && event.alt)
|
|
#define CtrlShift(KEY) (event.key == KEY && event.ctrl && event.shift)
|
|
#define CtrlAlt(KEY) (event.key == KEY && event.ctrl && event.alt)
|
|
#define AltShift(KEY) (event.key == KEY && event.shift && event.alt)
|
|
#define MouseVec2() \
|
|
Vec2 { (float)event.xmouse, (float)event.ymouse }
|
|
#define MouseVec2I() \
|
|
Vec2I { (Int) event.xmouse, (Int)event.ymouse }
|
|
#define Mouse(x) (event.kind == EVENT_MOUSE_##x)
|
|
#define MousePress() (Mouse(LEFT) || Mouse(RIGHT) || Mouse(MIDDLE))
|
|
|
|
Int MoveOnWhitespaceBoundaryForward(Buffer &buffer, Int pos) {
|
|
pos = Clamp(pos, (Int)0, buffer.len);
|
|
bool standing_on_whitespace = IsWhitespace(buffer.str[pos]);
|
|
bool standing_on_symbol = IsSymbol(buffer.str[pos]);
|
|
bool standing_on_word = !standing_on_whitespace && !standing_on_symbol;
|
|
bool seek_whitespace = standing_on_whitespace == false;
|
|
bool seek_word = standing_on_whitespace || standing_on_symbol;
|
|
bool seek_symbol = standing_on_whitespace || standing_on_word;
|
|
|
|
Int result = buffer.len;
|
|
Int prev_pos = pos;
|
|
for (Int i = pos; i < buffer.len; i += 1) {
|
|
bool is_whitespace = IsWhitespace(buffer.str[i]);
|
|
bool is_symbol = IsSymbol(buffer.str[i]);
|
|
bool is_word = !is_whitespace && !is_symbol;
|
|
if (seek_word && is_word) {
|
|
result = i;
|
|
break;
|
|
}
|
|
if (seek_whitespace && is_whitespace) {
|
|
result = i;
|
|
break;
|
|
}
|
|
if (seek_symbol && is_symbol) {
|
|
result = i;
|
|
break;
|
|
}
|
|
prev_pos = i;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Int MoveOnWhitespaceBoundaryBackward(Buffer &buffer, Int pos) {
|
|
pos = Clamp(pos - 1, (Int)0, buffer.len);
|
|
bool standing_on_whitespace = IsWhitespace(buffer.str[pos]);
|
|
bool standing_on_symbol = IsSymbol(buffer.str[pos]);
|
|
bool standing_on_word = !standing_on_whitespace && !standing_on_symbol;
|
|
bool seek_whitespace = standing_on_whitespace == false;
|
|
bool seek_word = standing_on_whitespace || standing_on_symbol;
|
|
bool seek_symbol = standing_on_whitespace || standing_on_word;
|
|
|
|
Int result = 0;
|
|
Int prev_pos = pos;
|
|
for (Int i = pos; i >= 0; i -= 1) {
|
|
bool is_whitespace = IsWhitespace(buffer.str[i]);
|
|
bool is_symbol = IsSymbol(buffer.str[i]);
|
|
bool is_word = !is_whitespace && !is_symbol;
|
|
if (seek_word && is_word) {
|
|
result = prev_pos;
|
|
break;
|
|
}
|
|
if (seek_whitespace && is_whitespace) {
|
|
result = prev_pos;
|
|
break;
|
|
}
|
|
if (seek_symbol && is_symbol) {
|
|
result = prev_pos;
|
|
break;
|
|
}
|
|
prev_pos = i;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Int MoveOnWhitespaceBoundaryDown(Buffer &buffer, Int pos) {
|
|
Int result = pos;
|
|
Int next_line = PosToLine(buffer, pos) + 1;
|
|
for (Int line = next_line; line < buffer.line_starts.len; line += 1) {
|
|
Range line_range = GetLineRange(buffer, line);
|
|
result = line_range.min;
|
|
|
|
bool whitespace_line = true;
|
|
for (Int i = line_range.min; i < line_range.max; i += 1) {
|
|
if (!IsWhitespace(buffer.str[i])) {
|
|
whitespace_line = false;
|
|
break;
|
|
}
|
|
}
|
|
if (whitespace_line) break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Int MoveOnWhitespaceBoundaryUp(Buffer &buffer, Int pos) {
|
|
Int result = pos;
|
|
Int next_line = PosToLine(buffer, pos) - 1;
|
|
for (Int line = next_line; line >= 0; line -= 1) {
|
|
Range line_range = GetLineRange(buffer, line);
|
|
result = line_range.min;
|
|
|
|
bool whitespace_line = true;
|
|
for (Int i = line_range.min; i < line_range.max; i += 1) {
|
|
if (!IsWhitespace(buffer.str[i])) {
|
|
whitespace_line = false;
|
|
break;
|
|
}
|
|
}
|
|
if (whitespace_line) break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Int MovePosByXY(Buffer &buffer, Int pos, XY offset) {
|
|
XY xy = PosToXY(buffer, pos);
|
|
Int result = XYToPosWithoutNL(buffer, {xy.col + offset.col, xy.line + offset.line});
|
|
return result;
|
|
}
|
|
|
|
const int DIR_RIGHT = 0;
|
|
const int DIR_LEFT = 1;
|
|
const int DIR_DOWN = 2;
|
|
const int DIR_UP = 3;
|
|
|
|
const bool CTRL_PRESSED = true;
|
|
|
|
Int MovePos(Buffer &buffer, Int pos, int direction, bool ctrl_pressed = false) {
|
|
ProfileFunction();
|
|
Assert(direction >= 0 && direction <= 3);
|
|
if (ctrl_pressed) {
|
|
switch (direction) {
|
|
case DIR_RIGHT: return MoveOnWhitespaceBoundaryForward(buffer, pos);
|
|
case DIR_LEFT: return MoveOnWhitespaceBoundaryBackward(buffer, pos);
|
|
case DIR_DOWN: return MoveOnWhitespaceBoundaryDown(buffer, pos);
|
|
case DIR_UP: return MoveOnWhitespaceBoundaryUp(buffer, pos);
|
|
default: return pos;
|
|
}
|
|
} else {
|
|
switch (direction) {
|
|
case DIR_RIGHT: return Clamp(pos + 1, (Int)0, buffer.len);
|
|
case DIR_LEFT: return Clamp(pos - 1, (Int)0, buffer.len);
|
|
case DIR_DOWN: return MovePosByXY(buffer, pos, {0, 1});
|
|
case DIR_UP: return MovePosByXY(buffer, pos, {0, -1});
|
|
default: return pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
Range EncloseWord(Buffer &buffer, Int pos) {
|
|
Range result = {};
|
|
result.min = MoveOnWhitespaceBoundaryBackward(buffer, pos);
|
|
result.max = MoveOnWhitespaceBoundaryForward(buffer, pos);
|
|
return result;
|
|
}
|
|
|
|
Caret FindInBuffer(Buffer *buffer, String16 needle, Caret caret, bool find_next = false) {
|
|
Int pos = caret.range.min;
|
|
String16 medium = GetString(*buffer, {pos, INT64_MAX});
|
|
|
|
Caret result = {};
|
|
Int index = 0;
|
|
if (Seek(medium, needle, &index)) {
|
|
result = MakeCaret(pos + index + needle.len, pos + index);
|
|
} else {
|
|
medium = GetString(*buffer);
|
|
if (Seek(medium, needle, &index)) {
|
|
result = MakeCaret(index + needle.len, index);
|
|
}
|
|
}
|
|
|
|
if (find_next && AreEqual(result, caret)) {
|
|
caret.range.min = Clamp(*buffer, caret.range.min + 1);
|
|
caret.range.max = Clamp(*buffer, caret.range.max + 1);
|
|
result = FindInBuffer(buffer, needle, caret, false);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Array<Range> FindAllInBuffer(Allocator allocator, Buffer *buffer, String16 needle) {
|
|
Array<Range> result = {allocator};
|
|
String16 string_buffer = GetString(*buffer);
|
|
for (Int pos = 0;;) {
|
|
Int index = 0;
|
|
String16 medium = Skip(string_buffer, pos);
|
|
if (!Seek(medium, needle, &index)) {
|
|
break;
|
|
}
|
|
Add(&result, Rng(pos + index, pos + index + needle.len));
|
|
pos += needle.len;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ToggleFullscreen() {
|
|
if (IsInFullscreen) {
|
|
SDL_SetWindowSize(SDLWindow, FullScreenSizeX, FullScreenSizeY);
|
|
SDL_SetWindowPosition(SDLWindow, FullScreenPositionX, FullScreenPositionY);
|
|
} else {
|
|
SDL_GetWindowSize(SDLWindow, &FullScreenSizeX, &FullScreenSizeY);
|
|
SDL_GetWindowPosition(SDLWindow, &FullScreenPositionX, &FullScreenPositionY);
|
|
|
|
SDL_DisplayID display = SDL_GetDisplayForWindow(SDLWindow);
|
|
const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(display);
|
|
SDL_SetWindowSize(SDLWindow, dm->w, dm->h);
|
|
SDL_SetWindowPosition(SDLWindow, 0, 0);
|
|
}
|
|
|
|
IsInFullscreen = !IsInFullscreen;
|
|
}
|
|
|
|
bool GlobalCommand(Event event) {
|
|
ProfileFunction();
|
|
bool run_window_command = true;
|
|
{
|
|
Vec2I mouse = MouseVec2I();
|
|
Window *window = GetActiveWindow();
|
|
bool mouse_in_document = CheckCollisionPointRec(mouse, window->document_rect);
|
|
bool mouse_in_total = CheckCollisionPointRec(mouse, window->total_rect);
|
|
bool mouse_in_scrollbar = CheckCollisionPointRec(mouse, window->scrollbar_rect);
|
|
window->mouse_in_scrollbar = mouse_in_scrollbar;
|
|
|
|
static SDL_Cursor *SDL_MouseCursor;
|
|
if (SDL_MouseCursor) SDL_DestroyCursor(SDL_MouseCursor);
|
|
|
|
if (window->mouse_selecting || mouse_in_total) {
|
|
SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT);
|
|
SDL_SetCursor(SDL_MouseCursor);
|
|
} else if (mouse_in_scrollbar || window->mouse_selecting_scrollbar) {
|
|
SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
|
|
SDL_SetCursor(SDL_MouseCursor);
|
|
} else {
|
|
SDL_MouseCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER);
|
|
SDL_SetCursor(SDL_MouseCursor);
|
|
}
|
|
}
|
|
|
|
if (MousePress()) {
|
|
Scratch scratch;
|
|
Array<Int> order = GetWindowZOrder(scratch);
|
|
Vec2I mouse = MouseVec2I();
|
|
|
|
For(order) {
|
|
Window *window = &Windows[it];
|
|
if (!window->visible) continue;
|
|
bool mouse_in_window = CheckCollisionPointRec(mouse, window->total_rect);
|
|
if (mouse_in_window) {
|
|
SetActiveWindow(window->id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event.wheel.x || event.wheel.y) {
|
|
Scratch scratch;
|
|
Array<Int> order = GetWindowZOrder(scratch);
|
|
Vec2I mouse = MouseVec2I();
|
|
|
|
For(order) {
|
|
Window *window = &Windows[it];
|
|
if (!window->visible) continue;
|
|
|
|
bool mouse_in_window = CheckCollisionPointRec(mouse, window->total_rect);
|
|
if (mouse_in_window) {
|
|
View *view = GetView(window->active_view);
|
|
view->scroll.y -= (Int)(event.wheel.y * 48);
|
|
view->scroll.x += (Int)(event.wheel.x * 48);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Ctrl(SDLK_GRAVE)) {
|
|
Window *window = GetWindow(ConsoleWindowID);
|
|
if (ToggleVisibility(window)) {
|
|
SetActiveWindow(window->id);
|
|
} else {
|
|
SetActiveWindow(GetLastActiveWindow());
|
|
}
|
|
}
|
|
|
|
if (CtrlShift(SDLK_BACKSLASH)) {
|
|
AddRowWindow();
|
|
} else if (Ctrl(SDLK_BACKSLASH)) {
|
|
AddColumnWindow();
|
|
}
|
|
|
|
if (Ctrl(SDLK_0)) {
|
|
Window *window = GetWindow(DebugWindowID);
|
|
ToggleVisibility(window);
|
|
}
|
|
|
|
if (Press(SDLK_F5)) {
|
|
AppIsRunning = false;
|
|
run_window_command = false;
|
|
}
|
|
|
|
if (Press(SDLK_F11)) {
|
|
ToggleFullscreen();
|
|
}
|
|
|
|
if (Ctrl(SDLK_P)) {
|
|
Window *command_window = GetWindow(CommandWindowID);
|
|
if (command_window->visible) {
|
|
SetActiveWindow(GetLastActiveWindow());
|
|
} else {
|
|
SetActiveWindow(command_window->id);
|
|
}
|
|
run_window_command = false;
|
|
}
|
|
|
|
if (Ctrl(SDLK_F)) {
|
|
Window *search_window = GetWindow(SearchWindowID);
|
|
if (search_window->visible) {
|
|
SetActiveWindow(GetLastActiveWindow());
|
|
} else {
|
|
SetActiveWindow(search_window->id);
|
|
View *view = GetActiveView(search_window);
|
|
Command_SelectEntireBuffer(view);
|
|
Command_Replace(view, {});
|
|
}
|
|
run_window_command = false;
|
|
}
|
|
|
|
if (Ctrl(SDLK_1)) {
|
|
SetActiveWindow({0});
|
|
run_window_command = false;
|
|
}
|
|
if (Ctrl(SDLK_2)) {
|
|
SetActiveWindow({1});
|
|
run_window_command = false;
|
|
}
|
|
if (Ctrl(SDLK_3)) {
|
|
SetActiveWindow({2});
|
|
run_window_command = false;
|
|
}
|
|
|
|
if (Ctrl(SDLK_MINUS)) {
|
|
ReloadFont((int32_t)MainFont.size - 1);
|
|
run_window_command = false;
|
|
} else if (Ctrl(SDLK_EQUALS)) {
|
|
ReloadFont((int32_t)MainFont.size + 1);
|
|
run_window_command = false;
|
|
}
|
|
|
|
return run_window_command;
|
|
}
|
|
|
|
View *FindView(BufferID buffer_id) {
|
|
For(Views) {
|
|
if (it.active_buffer.id == buffer_id.id) {
|
|
return ⁢
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void AppendToConsole(String16 string) {
|
|
Buffer *buffer = GetBuffer("*console*");
|
|
|
|
// @todo: ?
|
|
View *view = FindView(buffer->id);
|
|
|
|
bool scroll_to_end = false;
|
|
if (view) {
|
|
Int line = PosToLine(*buffer, GetFront(view->carets[0]));
|
|
if (line == buffer->line_starts.len - 1) scroll_to_end = true;
|
|
}
|
|
ReplaceText(buffer, GetEndAsRange(*buffer), string);
|
|
ReplaceText(buffer, GetEndAsRange(*buffer), L"\n");
|
|
|
|
if (scroll_to_end) {
|
|
view->carets[0] = MakeCaret(GetEndAsRange(*buffer).min);
|
|
}
|
|
}
|
|
|
|
void AppendToConsole(String string) {
|
|
Scratch scratch;
|
|
String16 string16 = ToString16(scratch, string);
|
|
AppendToConsole(string16);
|
|
}
|
|
|
|
void ReportErrorf(const char *fmt, ...) {
|
|
Scratch scratch;
|
|
STRING_FORMAT(scratch, fmt, string);
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error!", string.data, NULL);
|
|
AppendToConsole(string);
|
|
}
|
|
|
|
void ReportConsolef(const char *fmt, ...) {
|
|
Scratch scratch;
|
|
STRING_FORMAT(scratch, fmt, string);
|
|
AppendToConsole(string);
|
|
}
|
|
|
|
void ReportWarningf(const char *fmt, ...) {
|
|
Scratch scratch;
|
|
STRING_FORMAT(scratch, fmt, string);
|
|
String16 string16 = ToString16(scratch, string);
|
|
AppendToConsole(string16);
|
|
|
|
{
|
|
Buffer *buffer = GetBuffer("*popup*");
|
|
ReplaceText(buffer, GetRange(*buffer), string16);
|
|
SetActiveWindow(PopupWindowID);
|
|
}
|
|
} |