// assets.h is auto-generated by prepare-shaders.py
#include "assets.h"
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mash.h"
//#define DEFAULT_FONT_PATH "content/RobotoMono-Regular.ttf"
#define DEFAULT_FONT_PATH "content/Monaco_Regular.ttf"
static Vulkan vk;
static uint32_t cursor_color = 0;
static Font_Handle font_face = nullptr;
static Font_Render font_render = {0};
static File file = {0};
static Grid grid = {0};
static Formatter formatter = {0};
static Input_State input_state = {0};
static bool was_vertical_movement = false;
static bool needs_resubmit = true;
const char **get_required_instance_extensions(uint32_t *n_inst_exts) {
return glfwGetRequiredInstanceExtensions(n_inst_exts);
}
VkResult create_window_surface(VkInstance& instance, void *window, VkSurfaceKHR *surface) {
return glfwCreateWindowSurface(instance, (GLFWwindow*)window, nullptr, surface);
}
void get_thumb_position(Grid *g, int64_t file_size, int& y, int& h) {
int64_t grid_length = g->end_grid_offset - g->grid_offset;
double pos = (double)g->grid_offset / (double)(file_size - grid_length);
h = (double)vk.wnd_height * THUMB_FRAC;
y = (int)(pos * (double)(vk.wnd_height - h) + 0.5);
}
int64_t get_file_offset_from_thumb(Grid *g, int64_t file_size) {
int64_t grid_length = g->end_grid_offset - g->grid_offset;
double total_length = file_size - grid_length;
double len_per_pixel = total_length / ((double)vk.wnd_height * (1.0 - THUMB_FRAC));
double y = input_state.y - input_state.thumb_inner_pos;
return (int64_t)(y * len_per_pixel);
}
int upload_glyphsets(Font_Handle fh, Font_Render *renders, int n_renders) {
if (!vk.glyphset_pool.size) {
vk.glyphset_pool = vk.allocate_gpu_memory(GLYPHSET_POOL_SIZE);
if (!vk.glyphset_pool.size)
return __LINE__;
}
renders[0].buf = vk.glyphset_pool.staging_area;
make_font_render(fh, renders[0]);
return vk.push_to_gpu(vk.glyphset_pool, 0, renders[0].total_size);
}
int render_and_upload_views(View *views, int n_views, Font_Render *renders) {
if (!vk.grids_pool.size) {
vk.grids_pool = vk.allocate_gpu_memory(GRIDS_POOL_SIZE);
if (!vk.grids_pool.size)
return __LINE__;
}
Cell *cells = (Cell*)vk.grids_pool.staging_area;
View& v = views[0];
v.grid->render_into(v.file, cells, v.formatter, input_state, vk.wnd_width, vk.wnd_height);
int res = vk.push_to_gpu(vk.grids_pool, 0, v.grid->rows * v.grid->cols * sizeof(Cell));
if (res != 0)
return __LINE__;
if (!vk.view_params || n_views > vk.view_param_cap) {
int cap = VIEW_PARAMS_INITIAL_CAP;
while (cap < n_views)
cap *= 2;
auto vps = new View_Params[cap];
if (vk.view_params)
delete[] vk.view_params;
vk.view_params = vps;
vk.view_param_cap = cap;
}
vk.n_view_params = n_views;
for (int i = 0; i < vk.n_view_params; i++) {
Font_Render *r = &renders[views[i].font_render_idx];
int thumb_y, thumb_h;
get_thumb_position(v.grid, v.file->total_size, thumb_y, thumb_h);
uint32_t thumb_color = v.formatter->inactive_thumb_color;
if (input_state.thumb_flags & 1)
thumb_color = v.formatter->active_thumb_color;
else if (input_state.thumb_flags & 2)
thumb_color = v.formatter->hovered_thumb_color;
vk.view_params[i] = {
.view_origin = {0, 0},
.view_size = {(uint32_t)vk.wnd_width, (uint32_t)vk.wnd_height},
.cell_size = {(uint32_t)r->glyph_w, (uint32_t)r->glyph_h},
.thumb_pos = {(uint32_t)thumb_y, (uint32_t)thumb_h},
.cursor = {v.grid->rel_caret_col + v.grid->last_line_num_gap, v.grid->rel_caret_row},
.thumb_color = thumb_color,
.cursor_color = cursor_color,
.columns = (uint32_t)v.grid->cols,
.grid_cell_offset = 0,
.glyphset_byte_offset = 0,
.glyph_overlap_w = (uint32_t)r->overlap_w,
.glyph_full_w = (uint32_t)r->glyph_img_w,
};
}
return 0;
}
int start_app(GLFWwindow *window) {
auto resize_grid = [](Grid& g) {
g.rows = (vk.wnd_height + font_render.glyph_h - 1) / font_render.glyph_h;
g.cols = (vk.wnd_width + font_render.glyph_w - 1) / font_render.glyph_w;
};
resize_grid(grid);
View view = {
.grid = &grid,
.file = &file,
.formatter = &formatter,
.font_render_idx = 0
};
int res = upload_glyphsets(font_face, &font_render, 1);
if (res != 0) return res;
res = render_and_upload_views(&view, 1, &font_render);
if (res != 0) return res;
res = vk.create_descriptor_set();
if (res != 0) return res;
res = vk.construct_pipeline();
if (res != 0) return res;
needs_resubmit = true;
while (!glfwWindowShouldClose(window)) {
glfwWaitEventsTimeout(0.5);
int w, h;
glfwGetFramebufferSize(window, &w, &h);
if (w != vk.wnd_width || h != vk.wnd_height) {
vk.recreate_swapchain(w, h);
needs_resubmit = true;
}
if (needs_resubmit) {
resize_grid(grid);
res = render_and_upload_views(&view, 1, &font_render);
if (res != 0) return res;
res = vk.update_command_buffers();
if (res != 0) return res;
needs_resubmit = false;
input_state.advance();
}
res = vk.render();
}
return res;
}
// TODO: Get font_render from font_renders[get_current_view()->font_render_idx] or something
// also Get grid from get_current_view()->grid or something
// This function **doesn't** get called from a different thread, so we can let it access globals
static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) {
bool is_action = true;
bool vertical = false;
int dir = 0;
bool shift_held = glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
input_state.mod_flags = shift_held ? 1 : 0;
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
if (key == GLFW_KEY_UP) {
vertical = true;
dir = -1;
}
else if (key == GLFW_KEY_DOWN) {
vertical = true;
dir = 1;
}
else if (key == GLFW_KEY_LEFT) {
dir = -1;
}
else if (key == GLFW_KEY_RIGHT) {
dir = 1;
}
else if (key == GLFW_KEY_PAGE_UP) {
grid.adjust_offsets(&file, -grid.rows, 0);
is_action = false;
}
else if (key == GLFW_KEY_PAGE_DOWN) {
grid.adjust_offsets(&file, grid.rows, 0);
is_action = false;
}
else
is_action = false;
}
else
is_action = false;
if (dir != 0) {
if (vertical) {
if (!was_vertical_movement) {
grid.target_column = grid.rel_caret_col;
was_vertical_movement = true;
}
grid.move_cursor_vertically(&file, dir, grid.target_column);
}
else {
int64_t cur = grid.primary_cursor + (int64_t)dir;
if (cur < 0) cur = 0;
if (cur > file.total_size) cur = file.total_size;
grid.primary_cursor = cur;
}
}
if (is_action) {
grid.primary_cursor = grid.jump_to_offset(&file, grid.primary_cursor, JUMP_FLAG_AFFECT_COLUMN);
if (!vertical || dir == 0)
was_vertical_movement = false;
if (!shift_held)
grid.secondary_cursor = grid.primary_cursor;
}
needs_resubmit = true;
}
static void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) {
if (xoffset == 0.0 && yoffset == 0.0)
return;
int64_t move_down = 0;
int64_t move_right = 0;
double dx, dy;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS) {
dx = yoffset;
dy = xoffset;
}
else {
dx = xoffset;
dy = yoffset;
}
if (dy > 0.0)
move_down = -1;
else if (dy < 0.0)
move_down = 1;
if (dx > 0.0)
move_right = -1;
else if (dx < 0.0)
move_right = 1;
if (move_down != 0 || move_right != 0)
grid.adjust_offsets(&file, move_down, move_right);
needs_resubmit = true;
}
static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) {
bool left_pressed = action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT;
bool right_pressed = action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_RIGHT;
input_state.left_flags = (input_state.left_flags & ~1) | (left_pressed & 1);
input_state.right_flags = (input_state.right_flags & ~1) | (right_pressed & 1);
if (!left_pressed)
input_state.thumb_flags &= ~1;
if ((input_state.left_flags & 3) == 1 && input_state.x >= vk.wnd_width - THUMB_WIDTH) {
int thumb_y, thumb_h;
get_thumb_position(&grid, file.total_size, thumb_y, thumb_h);
int pos = input_state.y - thumb_y;
bool should_jump = true;
if (input_state.y < thumb_y) {
pos = 0;
}
else if (input_state.y >= thumb_y + thumb_h) {
pos = thumb_h;
}
else {
should_jump = false;
}
input_state.thumb_inner_pos = pos;
input_state.thumb_flags |= 1;
if (should_jump) {
int64_t offset = get_file_offset_from_thumb(&grid, file.total_size);
grid.jump_to_offset(&file, offset, JUMP_FLAG_TOP);
}
}
was_vertical_movement = false;
needs_resubmit = true;
}
static void cursor_callback(GLFWwindow *window, double xpos, double ypos) {
input_state.x = (int)xpos;
input_state.y = (int)ypos;
input_state.column = input_state.x / font_render.glyph_w;
input_state.row = input_state.y / font_render.glyph_h;
if (input_state.thumb_flags & 1) {
int64_t offset = get_file_offset_from_thumb(&grid, file.total_size);
grid.jump_to_offset(&file, offset, JUMP_FLAG_TOP);
}
int was_hovered = input_state.thumb_flags & 2;
if (input_state.x >= vk.wnd_width - THUMB_WIDTH)
input_state.thumb_flags |= 2;
else
input_state.thumb_flags &= ~2;
if ((input_state.left_flags & 3) || was_hovered != (input_state.thumb_flags & 2))
needs_resubmit = true;
}
int main(int argc, char **argv) {
const char *file_name = "vulkan.cpp";
if (argc > 1)
file_name = argv[1];
atexit([](){ft_quit();});
font_face = load_font_face(DEFAULT_FONT_PATH);
if (!font_face)
return 1;
// TODO: Use system DPI
font_render = size_up_font_render(font_face, 10, 96, 96);
formatter.modes[0].fore_color_idx = 1;
formatter.modes[0].glyphset = 0; // italic
formatter.colors[0] = 0x080808ff;
formatter.colors[1] = 0xf0f0f0ff;
formatter.colors[2] = 0x202020ff;
formatter.colors[3] = 0xb0b0b0ff;
formatter.colors[4] = 0x141414ff;
formatter.active_thumb_color = 0x808080ff;
formatter.hovered_thumb_color = 0x303030ff;
formatter.inactive_thumb_color = 0x181818ff;
cursor_color = 0xf0f0f0ff;
grid.spaces_per_tab = 4;
VkShaderModuleCreateInfo vertex_buf = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = sizeof(vertex_spv_data),
.pCode = (uint32_t*)vertex_spv_data
};
VkShaderModuleCreateInfo fragment_buf = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = sizeof(fragment_spv_data),
.pCode = (uint32_t*)fragment_spv_data
};
if (file.open(file_name) < 0)
return 2;
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *vid_mode = glfwGetVideoMode(monitor);
//int width = vid_mode->width, height = vid_mode->height;
int width = 800, height = 600;
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
GLFWwindow *window = glfwCreateWindow(width, height, "Mash", nullptr, nullptr);
if (!window) {
fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate();
return 3;
}
glfwSetKeyCallback(window, key_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_callback);
vk.glfw_monitor = (void*)monitor;
vk.glfw_window = (void*)window;
int res = init_vulkan(vk, vertex_buf, fragment_buf, width, height);
if (res == 0)
res = start_app(window);
file.close();
vk.close();
glfwDestroyWindow(window);
glfwTerminate();
return res;
}