Files
text_editor/src/text_editor/window.cpp

430 lines
13 KiB
C++

WindowID AllocWindowID(Window *window) {
return {WindowIDs.id++, window};
}
Window *CreateWind() {
Allocator allocator = GetSystemAllocator();
Window *w = AllocType(allocator, Window);
w->font = &PrimaryFont;
w->visible = true;
w->primary = true;
w->draw_scrollbar = true;
w->draw_line_numbers = true;
w->draw_line_highlight = true;
w->jump_history = true;
w->id = AllocWindowID(w);
w->weight = 1.0;
Add(&Windows, w);
return w;
}
Window *GetWindow(WindowID id, Window *default_window = Windows[0]) {
For(Windows) {
if (it->id == id) return it;
}
return default_window;
}
void Close(WindowID id) {
Window *window = GetWindow(id);
if (window->id.id == 0) {
return;
}
window->close = true;
RunGCThisFrame = true;
}
Window *FindWindow(ViewID view_id, Window *default_window = NULL) {
For(Windows) {
if (it->active_view == view_id) {
return it;
}
}
return default_window;
}
Window *FindWindow(String buffer_name, Window *default_window = NULL) {
For(Windows) {
View *it_view = GetView(it->active_view);
Buffer *it_buffer = GetBuffer(it_view->active_buffer);
if (it_buffer->name == buffer_name) {
return it;
}
}
return default_window;
}
Window *FindWindow(BufferID buffer_id) {
For(Windows) {
View *view = GetView(it->active_view);
if (view->active_buffer == buffer_id) return it;
}
return NULL;
}
Window *GetActiveWind() {
return GetWindow(ActiveWindowID);
}
bool IsDocumentSelectionValid() {
if (DocumentSelected.id == -1) return false;
return true;
}
bool IsScrollbarSelectionValid() {
if (ScrollbarSelected.id == -1) return false;
return true;
}
Array<Window *> GetWindowZOrder(Allocator allocator) {
Array<Window *> order = {allocator};
For(Windows) if (it->z == 2) Add(&order, it);
For(Windows) if (it->z == 1) Add(&order, it);
For(Windows) if (it->z == 0) Add(&order, it);
return order;
}
Int GetExpandingBarSize(Window *window) {
View *view = GetView(window->active_view);
Buffer *buffer = GetBuffer(view->active_buffer);
float result = (float)buffer->line_starts.len * window->font->line_spacing;
return (Int)result;
}
View *WindowOpenBufferView(Window *new_parent_window, String name) {
View *view = FindView(name);
if (!view) {
View *result = OpenBufferView(name);
new_parent_window->active_view = result->id;
return result;
}
Window *window = FindWindow(view->id);
if (!window) {
new_parent_window->active_view = view->id;
return view;
}
if (window == new_parent_window) {
return view;
}
Assert(window->active_view.id == view->id.id);
View *result = OpenBufferView(name);
new_parent_window->active_view = result->id;
return result;
}
void CalcNiceties(Window *n) {
float scrollbar_size = (10.f * DPIScale);
float line_numbers_size = (float)n->font->char_spacing * 10.f;
if (DrawScrollbar && n->draw_scrollbar) n->scrollbar_rect = CutRight(&n->document_rect, (Int)scrollbar_size);
if (DrawLineNumbers && n->draw_line_numbers) n->line_numbers_rect = CutLeft(&n->document_rect, (Int)line_numbers_size);
}
double WindowCalcEvenResizerValue(Int screen_size_x, Int *out_count = NULL) {
double w = 0;
Int c = 0;
ForItem(n, Windows) {
if (n->primary) {
w += n->weight;
c += 1;
}
}
if (out_count) *out_count = c;
return (double)screen_size_x / w;
}
Window *GetOverlappingWindow(Vec2I p, Window *default_window = NULL) {
For(Windows) {
if (AreOverlapping(p, it->total_rect)) {
return it;
}
}
return default_window;
}
Vec2I GetSideOfWindow(Window *window, int direction) {
Vec2I p = {};
Rect2I rect = window->total_rect;
float resizer_size = (float)window->font->char_spacing*0.5f; // @check_codebase_when_changing
if (direction == DIR_LEFT) {
p.x = rect.min.x - (Int)(resizer_size + window->font->char_spacing);
p.y = rect.min.y + (rect.max.y / 2);
} else if (direction == DIR_RIGHT) {
p.x = rect.max.x + (Int)(resizer_size + window->font->char_spacing);
p.y = rect.min.y + (rect.max.y / 2);
} else if (direction == DIR_UP) {
p.x = rect.min.x + (rect.max.x / 2);
p.y = rect.min.y - (Int)(resizer_size + window->font->line_spacing);
} else {
Assert(direction == DIR_DOWN);
p.x = rect.min.x + (rect.max.x / 2);
p.y = rect.max.y + (Int)(resizer_size + window->font->line_spacing);
}
return p;
}
Window *SwitchWindow(int direction) {
Window *window = GetWindow(ActiveWindowID);
Vec2I p = GetSideOfWindow(window, direction);
Window *result = GetOverlappingWindow(p, window);
return result;
}
Int ScreenSpaceToBufferPos(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
Vec2I mworld = mouse - window->document_rect.min + view->scroll;
double px = (double)mworld.x / (double)window->font->char_spacing;
double py = (double)mworld.y / (double)window->font->line_spacing;
XY xy = {(Int)round(px), (Int)floor(py)};
Int result = XYToPosWithoutNL(buffer, xy);
return result;
}
Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
Vec2I mworld = mouse - window->document_rect.min + view->scroll;
double px = (double)mworld.x / (double)window->font->char_spacing;
double py = (double)mworld.y / (double)window->font->line_spacing;
XY xy = {(Int)round(px), (Int)floor(py)};
Int result = XYToPosErrorOutOfBounds(buffer, xy);
return result;
}
GotoCrumb PopCrumb(Array<GotoCrumb> *cr) {
for (; cr->len;) {
GotoCrumb c = Pop(cr);
View *view = FindView(c.view_id, NULL);
if (view) {
return c;
}
}
return {};
}
View *GetLastValidView(Window *window) {
For (IterateInReverse(&window->goto_redo)) {
if (it.view_id == window->active_view) continue;
View *view = FindView(it.view_id, NULL);
if (view) return view;
}
For (IterateInReverse(&window->goto_history)) {
if (it.view_id == window->active_view) continue;
View *view = FindView(it.view_id, NULL);
if (view) return view;
}
return Views[0];
}
View *JumpToLastValidView(Window *window) {
View *view = GetLastValidView(window);
window->active_view = view->id;
return view;
}
void JumpBack(Window *window) {
if (window->jump_history == false) return;
if (window->goto_history.len <= 0) return;
BSet set = GetBSet(window);
Add(&window->goto_redo, {set.view->id, set.view->carets[0], GetTimeSeconds()});
GotoCrumb c = PopCrumb(&window->goto_history);
window->active_view = c.view_id;
View *view = GetView(c.view_id);
view->carets[0] = c.caret;
CenterView(window->id);
if (window->goto_history.len) {
GotoCrumb *next = GetLast(window->goto_history);
if (c.view_id == next->view_id && ((c.time - next->time) <= JumpHistoryMergeTime)) {
JumpBack(window);
}
}
}
void JumpForward(Window *window) {
if (window->jump_history == false) return;
if (window->goto_redo.len <= 0) return;
BSet set = GetBSet(window);
Add(&window->goto_history, {set.view->id, set.view->carets[0], GetTimeSeconds()});
GotoCrumb c = PopCrumb(&window->goto_redo);
window->active_view = c.view_id;
View *view = GetView(c.view_id);
view->carets[0] = c.caret;
CenterView(window->id);
if (window->goto_redo.len) {
GotoCrumb *next = GetLast(window->goto_redo);
if ((c.view_id == next->view_id) && ((next->time - c.time) <= JumpHistoryMergeTime)) {
JumpForward(window);
}
}
}
void GotoNextInList(Window *window, Int line_offset = 1) {
Assert(line_offset == 1 || line_offset == -1);
View *active_view = GetView(window->active_view);
View *view_goto = GetView(window->active_goto_list);
window->active_view = view_goto->id;
Buffer *buffer_goto = GetBuffer(view_goto->active_buffer);
int64_t pos = window->goto_list_pos;
Int line = PosToLine(buffer_goto, pos);
bool opened = false;
for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) {
Range line_range = GetLineRangeWithoutNL(buffer_goto, i);
String16 line = GetString(buffer_goto, line_range);
{
Int idx = 0;
String16 delim = u"||>";
if (Seek(line, delim, &idx, SeekFlag_None)) {
line = Skip(line, idx + delim.len);
}
}
view_goto->carets[0] = MakeCaret(line_range.min);
window->goto_list_pos = line_range.min;
line = Trim(line);
MergeCarets(buffer_goto, &view_goto->carets);
IF_DEBUG(AssertRanges(view_goto->carets));
if (line.len == 0) {
continue;
}
Buffer *active_view_buffer = GetBuffer(active_view->active_buffer);
Int p = active_view->carets[0].range.min;
Int active_view_line = PosToLine(active_view_buffer, p);
BSet set = Open(line, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec);
if (set.window == NULL) {
continue;
}
if (set.view == active_view) {
Int new_line = PosToLine(set.buffer, set.view->carets[0].range.min);
if (active_view_line == new_line) {
continue;
}
}
opened = true;
break;
}
if (!opened) window->active_view = active_view->id;
}
void UpdateScroll(Window *window, bool update_caret_scrolling) {
ProfileFunction();
BSet set = GetBSet(window);
// Scrolling with caret
if (update_caret_scrolling) {
Caret c = set.view->carets[0];
Int front = GetFront(c);
XY xy = PosToXY(set.buffer, front);
Rect2I visible = GetVisibleCells(window);
Vec2I visible_cells = GetSize(visible);
Vec2I visible_size = visible_cells * Vec2I{window->font->char_spacing, window->font->line_spacing};
Vec2I rect_size = GetSize(window->document_rect);
if (xy.line >= visible.max.y - 2) {
Int set_view_at_line = xy.line - (visible_cells.y - 1);
Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y);
set.view->scroll.y = (set_view_at_line * window->font->line_spacing) + cut_off_y;
}
if (xy.line < visible.min.y + 1) {
set.view->scroll.y = xy.line * window->font->line_spacing;
}
if (xy.col >= visible.max.x - 1) {
Int set_view_at_line = xy.col - (visible_cells.x - 1);
Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x);
set.view->scroll.x = (set_view_at_line * window->font->char_spacing) + cut_off_x;
}
if (xy.col <= visible.min.x) {
set.view->scroll.x = xy.col * window->font->char_spacing;
}
}
// Clip scroll
{
Int last_line = LastLine(set.buffer);
set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * window->font->line_spacing));
// @note:
// GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for
// calculating this value incrementally but do we even need X scrollbar or x clipping?
set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0);
}
}
void CenterView(WindowID window) {
BSet set = GetBSet(window);
Caret c = set.view->carets[0];
Int front = GetFront(c);
XY xy = PosToXY(set.buffer, front);
Vec2I size = GetSize(set.window->document_rect);
Int y = size.y / 2 / set.window->font->line_spacing;
set.view->scroll.x = 0;
if (xy.line > y) {
set.view->scroll.y = (xy.line) * set.window->font->line_spacing - (size.y / 2);
}
}
BSet GetBSet(Window *window) {
BSet set = {window};
set.view = GetView(set.window->active_view);
set.buffer = GetBuffer(set.view->active_buffer);
return set;
}
BSet GetBSet(WindowID window_id) {
Window *window = GetWindow(window_id);
BSet result = GetBSet(window);
return result;
}
String GetPrimaryDirectory() {
BSet main = GetBSet(PrimaryWindowID);
return GetDirectory(main.buffer);
}
String GetDirectory(Window *window) {
BSet main = GetBSet(PrimaryWindowID);
return GetDirectory(main.buffer);
}
void MoveCursorByPageSize(Window *window, int direction, bool shift = false) {
Assert(direction == DIR_UP || direction == DIR_DOWN);
BSet set = GetBSet(window);
Rect2I visible_cells_rect = GetVisibleCells(window);
Int y = GetSize(visible_cells_rect).y - 2;
if (direction == DIR_UP) y = -y;
For(set.view->carets) {
XY xy = PosToXY(set.buffer, GetFront(it));
if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) {
Range line_range = GetLineRange(set.buffer, xy.line);
xy.col = line_range.max - line_range.min;
} else if (direction == DIR_UP && xy.line == 0) {
xy.col = 0;
}
xy.line += y;
Int pos = XYToPos(set.buffer, xy);
if (shift) {
it = SetFront(it, pos);
} else {
it = MakeCaret(pos);
}
}
}