"value": [ 0.8, 1.0, 0.8 ],
"jumppoint": [ 1, 0.5, 0.5 ],
- "primary": [ 0.051, 0.278, 0.631 ],
+ "primary": [ 0.032, 0.178, 0.431 ],
"primary_light": [ 0.329, 0.451, 0.827 ],
- "primary_dark": [ 0, 0.129, 0.443 ],
+ "primary_dark": [ 0, 0.065, 0.233 ],
"primary_text": [ 1.0, 1.0, 1.0 ],
"secondary": [ 0, 0.376, 0.392 ],
refresh: function() {
window.location.reload(true);
+ },
+
+ set_cursor: function(curptr, curlen) {
+ const decoder = new TextDecoder();
+ const data = new Uint8Array(window.ONYX_MEMORY.buffer, curptr, curlen);
+ const str = decoder.decode(data);
+
+ document.getElementById("main_canvas").style.cursor = str;
}
});
+++ /dev/null
-package app
-
-use package core
-
-#private_file events :: package js_events
-#private_file gl :: package gl
-#private_file gfx :: package immediate_mode
-#private_file ui :: package ui
-#private_file config :: package config
-#private_file wasm :: package wasm_utils
-#private_file debug :: package debug
-
-use package debug { init as debug_init, debug_log, draw_debug_log }
-use package core.intrinsics.onyx { __initialize }
-
-@Relocate search_buffer: string.String_Buffer;
-
-background_tile_texture : gfx.Texture;
-
-on_file_load_callbacks : map.Map(u32, (file_event: ^events.Event) -> void);
-
-// The global application state.
-state : Application_State;
-
-Application_State :: struct {
- has_active_file := false;
- file := Active_File.{};
-}
-
-Active_File :: struct {
- name := (#type [] u8).{ null, 0 };
- data := (#type [] u8).{ null, 0 };
-}
-
-init :: () {
- debug_init();
-
- __initialize(^state);
-
- gl.init("main_canvas");
- events.init();
- gfx.immediate_renderer_init();
-
- ui.init_ui();
-
- map.init(^on_file_load_callbacks);
-
- color_file := events.request_file(config.color_scheme_file);
- map.put(^on_file_load_callbacks, color_file, load_colors);
-
- load_background_tile_texture();
- load_fonts();
-
- search_buffer = string.buffer_make(memory.make_slice(u8, 256));
-
- gl.enable(gl.BLEND);
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
-
- {
- use type_info;
-
- // Look through all the types in the program
- for type: type_table {
- if type.kind != .Struct do continue;
-
- ts := cast(^Type_Info_Struct) type;
- if !string.starts_with(ts.name, "Feature_") do continue;
-
- // Any types that are a structure and start with "Feature_" will be dynamically loaded
-
- debug_log(.Info, "Found feature '{}'", string.advance(ts.name, 8));
-
- for ^member: ts.members {
- Hook_Function_Type :: #type (^Application_State) -> void;
-
- if member.name == "setup" {
- assert(member.type == Hook_Function_Type, "setup has the wrong type.");
- assert(member.default != null, "setup has no default function.");
-
- (*(cast(^Hook_Function_Type) member.default))(null);
- }
-
- if member.name == "work" {
- assert(member.type == Hook_Function_Type, "work has the wrong type.");
- assert(member.default != null, "work has no default function.");
-
- (*(cast(^Hook_Function_Type) member.default))(null);
- }
- }
- }
- }
-
- load_background_tile_texture :: () {
- background_tile_texture = gfx.load_texture(32, 32, #file_contents "res/images/background_tile.data", gl.RGB, gl.RGB);
- gl.bindTexture(gl.TEXTURE_2D, background_tile_texture.texture);
-
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
-
- gl.bindTexture(gl.TEXTURE_2D, -1);
- }
-
- load_colors :: (event: ^events.Event) {
- json :: package json
-
- assert(event.kind == .FileRequest, "Bad event type");
- assert(event.file.status == .Success, "Failed to load color file");
-
- color_data := memory.make_slice(u8, event.file.size);
- defer if color_data.count > 0 do cfree(color_data.data);
- events.get_requested_file_data(event.file.file_id, color_data);
-
- arena := alloc.arena.make(context.allocator, 4096);
- defer alloc.arena.free(^arena);
- colors := json.decode(color_data, alloc.arena.make_allocator(^arena));
- defer json.free(colors);
-
- config.Colors.dark_background = decode_color(colors.root["dark_background"]);
- config.Colors.background = decode_color(colors.root["background"]);
- config.Colors.foreground = decode_color(colors.root["foreground"]);
-
- config.Colors.keyword = decode_color(colors.root["keyword"]);
- config.Colors.value = decode_color(colors.root["value"]);
- config.Colors.jumppoint = decode_color(colors.root["jumppoint"]);
-
- config.Colors.primary = decode_color(colors.root["primary"]);
- config.Colors.primary_light = decode_color(colors.root["primary_light"]);
- config.Colors.primary_dark = decode_color(colors.root["primary_dark"]);
- config.Colors.primary_text = decode_color(colors.root["primary_text"]);
-
- config.Colors.secondary = decode_color(colors.root["secondary"]);
- config.Colors.secondary_light = decode_color(colors.root["secondary_light"]);
- config.Colors.secondary_dark = decode_color(colors.root["secondary_dark"]);
- config.Colors.secondary_text = decode_color(colors.root["secondary_text"]);
-
- update_ui_colors();
-
- decode_color :: (v: ^json.Value) -> gfx.Color4 {
- return .{
- r = ~~v[0]->as_float(),
- g = ~~v[1]->as_float(),
- b = ~~v[2]->as_float(),
- };
- }
-
- debug_log(.Info, "Successfully loaded colorscheme.", null);
- }
-
- load_fonts :: () {
- use type_info;
-
- // Dumb check to see if the array is uninitialized
- if fonts_loading.capacity == 0 do array.init(^fonts_loading, 4);
-
- fonts_info := cast(^Type_Info_Struct) get_type_info(config.Fonts_Container);
- for ^member: fonts_info.members {
- info := cast(^Type_Info_Struct) get_type_info(member.type);
-
- font_name := member.name;
-
- font_index := *cast(^i32) info.parameters[0].data;
- fnt_file_name := *cast(^str) info.parameters[1].data;
- tex_file_name := *cast(^str) info.parameters[2].data;
-
- debug_log(.Info, "Loading font '{}' with index {} from '{}' and '{}'\n", font_name, font_index, fnt_file_name, tex_file_name);
-
- fnt_file_id := events.request_file(fnt_file_name);
- tex_file_id := events.request_file(tex_file_name);
-
- map.put(^on_file_load_callbacks, fnt_file_id, font_file_loaded);
- map.put(^on_file_load_callbacks, tex_file_id, font_file_loaded);
-
- array.push(^fonts_loading, .{ font_index, font_name, fnt_file_id, tex_file_id });
- }
-
- Loading_Font :: struct {
- font_index : u32;
- font_name : str;
-
- fnt_file_id : u32;
- tex_file_id : u32;
-
- fnt_file_size := cast(u32) 0;
- tex_file_size := cast(u32) 0;
- }
- #persist fonts_loading : [..] Loading_Font;
-
- font_file_loaded :: (ev: ^events.Event) {
- lf: ^Loading_Font = null;
-
- for ^entry: fonts_loading {
- if entry.fnt_file_id == ev.file.file_id { entry.fnt_file_size = ev.file.size; lf = entry; }
- if entry.tex_file_id == ev.file.file_id { entry.tex_file_size = ev.file.size; lf = entry; }
- }
-
- assert(lf != null, "Loaded a file for a font that was not registered.");
-
- if lf.fnt_file_size > 0 && lf.tex_file_size > 0 {
- fnt_data := memory.make_slice(u8, lf.fnt_file_size);
- tex_data := memory.make_slice(u8, lf.tex_file_size);
- defer {
- cfree(fnt_data.data);
- cfree(tex_data.data);
- }
-
- @ErrorHandling
- assert(events.get_requested_file_data(lf.fnt_file_id, fnt_data), "Failed to get bmfont file data.");
- assert(events.get_requested_file_data(lf.tex_file_id, tex_data), "Failed to get texture data.");
-
- font := ui.create_font(fnt_data, tex_data);
- ui.register_font(lf.font_index, font);
-
- debug_log(.Info, "Successfully loaded font '{}'.", lf.font_name);
-
- ui.use_font(lf.font_index);
- }
- }
- }
-}
-
-handle_event :: (event: ^events.Event) {
- switch event.kind {
- case .MouseDown do switch event.mouse.button {
- case .Left do ui.button_pressed(.Left);
- case .Right do ui.button_pressed(.Right);
- case .Middle do ui.button_pressed(.Middle);
- }
-
- case .MouseUp do switch event.mouse.button {
- case .Left do ui.button_released(.Left);
- case .Right do ui.button_released(.Right);
- case .Middle do ui.button_released(.Middle);
- }
-
- case .MouseMove do ui.update_mouse_position(~~ event.mouse.pos_x, ~~ event.mouse.pos_y);
-
- case .MouseWheel do switch event.mouse.button {
- case .WheelUp do ui.button_pressed(.WheelUp);
- case .WheelDown do ui.button_pressed(.WheelDown);
- }
-
- case .KeyDown, .KeyUp {
- modifiers : ui.Keyboard_State.Key_Event.Modifiers;
- if event.keyboard.modifiers & .CTRL do modifiers |= .CTRL;
- if event.keyboard.modifiers & .ALT do modifiers |= .ALT;
- if event.keyboard.modifiers & .META do modifiers |= .META;
- if event.keyboard.modifiers & .SHIFT do modifiers |= .SHIFT;
-
- if event.kind == .KeyDown {
- ui.key_down(event.keyboard.keycode, modifiers); @KeycodeIsWrong // .keycode is apparently not browser independent...
-
- if event.keyboard->get_name() == "F5" {
- refresh :: () -> void #foreign "decompiler" "refresh" ---
- refresh();
- break;
- }
-
- if event.keyboard->get_name() == "F7" {
- debug.debug_log_toggle();
- break;
- }
-
- if event.keyboard->get_name() == "Tab" {
- // toggle_sidebar(^analyzer_state);
- break;
- }
-
- } else {
- ui.key_up(event.keyboard.keycode, modifiers); @KeycodeIsWrong // see above
- }
- }
-
- case .Resize {
- gl.setSize(event.resize.width, event.resize.height);
- gfx.set_window_size(event.resize.width, event.resize.height);
- }
-
- case .FileRequest {
- if f := map.get(^on_file_load_callbacks, event.file.file_id); f != null_proc {
- f(event);
-
- } else {
- printf("Warning: No callback set for file id {}.\n", event.file.file_id);
- }
- }
-
- case .FileDropped {
- data := memory.make_slice(u8, event.file.size);
- name := memory.make_slice(u8, event.file.name_length);
- events.get_requested_file_data(event.file.file_id, data, name);
-
- debug_log(.Info, "File with size {} and name {} was dropped.\n", event.file.size, name);
-
- if state.has_active_file {
- memory.free_slice(^state.file.name);
- memory.free_slice(^state.file.data);
- }
-
- state.has_active_file = true;
- state.file.name = name;
- state.file.data = data;
-
- // This transfers ownership of wasm_data to the analyzer_state
- // load_wasm_binary(^analyzer_state, wasm_data);
- }
- }
-}
-
-needs_redraw :: () -> bool {
- return ui.has_active_animation() || debug.debug_log_transitioning();
-}
-
-update :: (dt: f32) {
- debug.debug_log_update(dt);
-}
-
-draw :: () {
- bg_color := config.Colors.background;
- gl.clearColor(bg_color.r, bg_color.g, bg_color.b, bg_color.a);
- gl.clear(gl.COLOR_BUFFER_BIT);
-
- window_width, window_height := gfx.get_window_size();
- window_rectangle := ui.Rectangle.{ 0, 0, ~~window_width, ~~window_height };
- menu_bar, main_area := ui.Flow.split_horizontal(window_rectangle, top_height=32);
-
- ui.workspace_start(main_area);
- draw_background_lines(~~window_width, ~~window_height, line_color=config.Colors.background);
-
- ui.draw_rect(0, 0, 20, 20, color=.{1,0,0});
-
- {
- #persist window_state := ui.Window_State.{ position=gfx.Vector2.{100, 100}, size=gfx.Vector2.{800, 600} };
- ui.window_start(^window_state);
- defer ui.window_end();
-
- if state.has_active_file {
- buffer: [512] u8;
- name_text := conv.str_format("File name: {}", ~~buffer, state.file.name);
- ui.draw_text(.{ 0, 0, 300, 200 }, name_text);
-
- size_text := conv.str_format("File size: {} bytes", ~~buffer, state.file.data.count);
- ui.draw_text(.{ 0, 32, 300, 200 }, size_text);
-
- } else {
- ui.draw_text(.{ 0, 0, 300, 200 }, "No file loaded.");
- }
- }
-
- ui.workspace_end();
-
- // draw_sidebar(^analyzer_state);
-
- // Menu bar drawing
- {
- #insert gfx.save_matrix;
-
- gfx.identity();
- ui.menubar(menu_bar, ^search_buffer, ~~ui.Menu_Bar_Option.[
- .{ label = "File" },
- .{ label = "Test" },
- ]);
- }
-
- // Debug log drawing
- draw_debug_log(window_rectangle);
-
- gfx.flush();
- ui.end_frame();
-
- draw_background_lines :: (width: f32, height: f32, line_color := gfx.Color4.{0.2, 0.2, 0.2}, line_spacing := 32.0f) {
- gl :: package gl
-
- #insert gfx.save_matrix;
-
- trans := gfx.global_renderer->get_transform();
- sx := trans.scale.x * line_spacing;
- sy := trans.scale.y * line_spacing;
- tx := -trans.translation.x / sx;
- ty := -trans.translation.y / sy;
-
- gfx.identity();
- gfx.set_texture(^background_tile_texture);
- gfx.textured_rect(.{ 0, 0 }, .{ width, height }, .{ tx, ty }, .{ width / sx, height / sy }, color=line_color);
- gfx.set_texture();
- }
-}
-
-#private_file background_tile_texture : gfx.Texture;
-
-
-update_ui_colors :: () {
- ui.default_text_theme.text_color = config.Colors.foreground;
-
- ui.default_button_theme.text_color = config.Colors.primary_text;
- ui.default_button_theme.background_color = config.Colors.primary_dark;
- ui.default_button_theme.hover_color = config.Colors.primary;
- ui.default_button_theme.click_color = config.Colors.primary_light;
- ui.default_button_theme.border_color = config.Colors.primary;
-
- ui.default_textbox_theme.text_color = config.Colors.primary_text;
- ui.default_textbox_theme.background_color = config.Colors.primary_dark;
- ui.default_textbox_theme.hover_color = config.Colors.primary;
- ui.default_textbox_theme.click_color = config.Colors.primary_light;
- ui.default_textbox_theme.border_color = config.Colors.primary;
-}
--- /dev/null
+package app
+
+use package core
+
+#private_file events :: package js_events
+#private_file gl :: package gl
+#private_file gfx :: package immediate_mode
+#private_file ui :: package ui
+#private_file config :: package config
+#private_file wasm :: package wasm_utils
+#private_file debug :: package debug
+
+use package debug { init as debug_init, debug_log, draw_debug_log }
+use package core.intrinsics.onyx { __initialize }
+
+@Relocate search_buffer: string.String_Buffer;
+on_file_load_callbacks : map.Map(u32, (file_event: ^events.Event) -> void);
+
+@Relocate
+Tool :: struct {
+ id: str;
+ name: str;
+ open: () -> void;
+}
+@Relocate registered_tools : [..] Tool;
+
+// The global application state.
+state : Application_State;
+
+Application_State :: struct {
+ colorscheme: Colorscheme;
+
+ has_active_file := false;
+ file := Active_File.{};
+
+ windows_store : alloc.pool.PoolAllocator(Application_Window);
+ windows_map : map.Map(str, ^Application_Window);
+ windows_sorted : [..] ^Application_Window;
+
+ window_switcher_state : Window_Switcher_State;
+ workspace_state : ui.Workspace_State = .{};
+}
+
+Active_File :: struct {
+ name := null_str;
+ data := (#type [] u8).{ null, 0 };
+}
+
+#private_file MAXIMUM_WINDOWS :: 128
+Application_Window :: struct {
+ id: str;
+
+ window_state : ui.Window_State;
+
+ draw_data : rawptr;
+ draw : (data: rawptr, window: ^Application_Window) -> void;
+}
+
+init :: () {
+ debug_init();
+
+ __initialize(^state);
+ window_buffer := memory.make_slice(Application_Window, MAXIMUM_WINDOWS);
+ state.windows_store = alloc.pool.make(window_buffer);
+ map.init(^state.windows_map, hash_count=16);
+ array.init(^state.windows_sorted);
+ state.window_switcher_state->init();
+
+ gl.init("main_canvas");
+ events.init();
+ gfx.immediate_renderer_init();
+
+ ui.init_ui();
+
+ array.init(^registered_tools);
+ map.init(^on_file_load_callbacks);
+
+ colorscheme_switch(.Dark);
+
+ load_background_tile_texture();
+ load_fonts();
+
+ search_buffer = string.buffer_make(memory.make_slice(u8, 256));
+
+ gl.enable(gl.BLEND);
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+ array.push(^registered_tools, .{
+ id = "load_file_info",
+ name = "File Info",
+ open = () {
+ open_window("load_file_info", "Loaded file info", .{ 0, 0 }, (_: rawptr, win: ^Application_Window) {
+ if state.has_active_file {
+ buffer: [512] u8;
+ name_text := conv.str_format("File name: {}", ~~buffer, state.file.name);
+ ui.draw_text(.{ 0, 0, 300, 200 }, name_text);
+
+ size_text := conv.str_format("File size: {} bytes", ~~buffer, state.file.data.count);
+ ui.draw_text(.{ 0, 32, 300, 200 }, size_text);
+
+ } else {
+ ui.draw_text(.{ 0, 0, 300, 200 }, "No file loaded.");
+ }
+ });
+
+ move_window_to_top("load_file_info");
+ focus_window("load_file_info");
+ }
+ });
+
+ // Dynamically load things in the binary
+ {
+ use type_info;
+
+ // Look through all the types in the program
+ for type: type_table {
+ if type.kind != .Struct do continue;
+
+ ts := cast(^Type_Info_Struct) type;
+ if !string.starts_with(ts.name, "Feature_") do continue;
+
+ // Any types that are a structure and start with "Feature_" will be dynamically loaded
+
+ debug_log(.Info, "Found feature '{}'", string.advance(ts.name, 8));
+
+ for ^member: ts.members {
+ Hook_Function_Type :: #type () -> void;
+
+ if member.name == "setup" {
+ assert(member.type == Hook_Function_Type, "setup has the wrong type.");
+ assert(member.default != null, "setup has no default function.");
+
+ (*(cast(^Hook_Function_Type) member.default))();
+ }
+ }
+ }
+ }
+
+ load_background_tile_texture :: () {
+ background_tile_texture = gfx.load_texture(32, 32, #file_contents "res/images/background_tile.data", gl.RGB, gl.RGB);
+ gl.bindTexture(gl.TEXTURE_2D, background_tile_texture.texture);
+
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
+
+ gl.bindTexture(gl.TEXTURE_2D, -1);
+ }
+
+ load_fonts :: () {
+ use type_info;
+
+ // Dumb check to see if the array is uninitialized
+ if fonts_loading.capacity == 0 do array.init(^fonts_loading, 4);
+
+ fonts_info := cast(^Type_Info_Struct) get_type_info(config.Fonts_Container);
+ for ^member: fonts_info.members {
+ info := cast(^Type_Info_Struct) get_type_info(member.type);
+
+ font_name := member.name;
+
+ font_index := *cast(^i32) info.parameters[0].data;
+ fnt_file_name := *cast(^str) info.parameters[1].data;
+ tex_file_name := *cast(^str) info.parameters[2].data;
+
+ debug_log(.Info, "Loading font '{}' with index {} from '{}' and '{}'\n", font_name, font_index, fnt_file_name, tex_file_name);
+
+ fnt_file_id := events.request_file(fnt_file_name);
+ tex_file_id := events.request_file(tex_file_name);
+
+ map.put(^on_file_load_callbacks, fnt_file_id, font_file_loaded);
+ map.put(^on_file_load_callbacks, tex_file_id, font_file_loaded);
+
+ array.push(^fonts_loading, .{ font_index, font_name, fnt_file_id, tex_file_id });
+ }
+
+ Loading_Font :: struct {
+ font_index : u32;
+ font_name : str;
+
+ fnt_file_id : u32;
+ tex_file_id : u32;
+
+ fnt_file_size := cast(u32) 0;
+ tex_file_size := cast(u32) 0;
+ }
+ #persist fonts_loading : [..] Loading_Font;
+
+ font_file_loaded :: (ev: ^events.Event) {
+ lf: ^Loading_Font = null;
+
+ for ^entry: fonts_loading {
+ if entry.fnt_file_id == ev.file.file_id { entry.fnt_file_size = ev.file.size; lf = entry; }
+ if entry.tex_file_id == ev.file.file_id { entry.tex_file_size = ev.file.size; lf = entry; }
+ }
+
+ assert(lf != null, "Loaded a file for a font that was not registered.");
+
+ if lf.fnt_file_size > 0 && lf.tex_file_size > 0 {
+ fnt_data := memory.make_slice(u8, lf.fnt_file_size);
+ tex_data := memory.make_slice(u8, lf.tex_file_size);
+ defer {
+ cfree(fnt_data.data);
+ cfree(tex_data.data);
+ }
+
+ @ErrorHandling
+ assert(events.get_requested_file_data(lf.fnt_file_id, fnt_data), "Failed to get bmfont file data.");
+ assert(events.get_requested_file_data(lf.tex_file_id, tex_data), "Failed to get texture data.");
+
+ font := ui.create_font(fnt_data, tex_data);
+ ui.register_font(lf.font_index, font);
+
+ debug_log(.Info, "Successfully loaded font '{}'.", lf.font_name);
+
+ ui.use_font(lf.font_index);
+ }
+ }
+ }
+}
+
+handle_event :: (event: ^events.Event) {
+ switch event.kind {
+ case .MouseDown do switch event.mouse.button {
+ case .Left do ui.button_pressed(.Left) ;
+ case .Right do ui.button_pressed(.Right);
+ case .Middle do ui.button_pressed(.Middle);
+ }
+
+ case .MouseUp do switch event.mouse.button {
+ case .Left do ui.button_released(.Left);
+ case .Right do ui.button_released(.Right);
+ case .Middle do ui.button_released(.Middle);
+ }
+
+ case .MouseMove do ui.update_mouse_position(~~ event.mouse.pos_x, ~~ event.mouse.pos_y);
+
+ case .MouseWheel do switch event.mouse.button {
+ case .WheelUp do ui.button_pressed(.WheelUp);
+ case .WheelDown do ui.button_pressed(.WheelDown);
+ }
+
+ case .KeyDown, .KeyUp {
+ modifiers : ui.Keyboard_State.Key_Event.Modifiers;
+ if event.keyboard.modifiers & .CTRL do modifiers |= .CTRL;
+ if event.keyboard.modifiers & .ALT do modifiers |= .ALT;
+ if event.keyboard.modifiers & .META do modifiers |= .META;
+ if event.keyboard.modifiers & .SHIFT do modifiers |= .SHIFT;
+
+ if event.kind == .KeyDown {
+ ui.key_down(event.keyboard.keycode, modifiers); @KeycodeIsWrong // .keycode is apparently not browser independent...
+
+ if event.keyboard->get_name() == "F5" {
+ refresh :: () -> void #foreign "decompiler" "refresh" ---
+ refresh();
+ break;
+ }
+
+ if event.keyboard->get_name() == "F7" {
+ debug.debug_log_toggle();
+ break;
+ }
+
+ if event.keyboard->get_name() == "Tab" {
+ state.window_switcher_state->toggle();
+ break;
+ }
+
+ @Temporary // This should be a part of the workspace?
+ if event.keyboard->get_name() == "Space" {
+ open_tool_opener();
+ }
+
+ } else {
+ ui.key_up(event.keyboard.keycode, modifiers); @KeycodeIsWrong // see above
+ }
+ }
+
+ case .Resize {
+ gl.setSize(event.resize.width, event.resize.height);
+ gfx.set_window_size(event.resize.width, event.resize.height);
+ }
+
+ case .FileRequest {
+ if f := map.get(^on_file_load_callbacks, event.file.file_id); f != null_proc {
+ f(event);
+
+ } else {
+ printf("Warning: No callback set for file id {}.\n", event.file.file_id);
+ }
+ }
+
+ case .FileDropped {
+ data := memory.make_slice(u8, event.file.size);
+ name := memory.make_slice(u8, event.file.name_length);
+ events.get_requested_file_data(event.file.file_id, data, name);
+
+ debug_log(.Info, "File with size {} and name {} was dropped.\n", event.file.size, name);
+
+ if state.has_active_file {
+ memory.free_slice(^state.file.name);
+ memory.free_slice(^state.file.data);
+ }
+
+ state.has_active_file = true;
+ state.file.name = name;
+ state.file.data = data;
+ }
+ }
+}
+
+needs_redraw :: () -> bool {
+ return ui.has_active_animation()
+ || debug.debug_log_transitioning()
+ || state.window_switcher_state->is_animating();
+}
+
+update :: (dt: f32) {
+ debug.debug_log_update(dt);
+}
+
+draw :: () {
+ bg_color := config.Colors.background;
+ gl.clearColor(bg_color.r, bg_color.g, bg_color.b, bg_color.a);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ ui.set_cursor(ui.Cursors.Default);
+
+ window_width, window_height := gfx.get_window_size();
+ window_rectangle := ui.Rectangle.{ 0, 0, ~~window_width, ~~window_height };
+ menu_bar, main_area := ui.Flow.split_horizontal(window_rectangle, top_height=32);
+
+ ui.workspace_start(main_area, state=^state.workspace_state);
+ draw_background_lines(~~window_width, ~~window_height, line_color=config.Colors.background);
+
+ // Used for detecting when a window is completely off-screen and shouldn't be rendered.
+ transformed_window_rect := window_rectangle;
+ {
+ trans := gfx.global_renderer->get_transform();
+ transformed_window_rect.x0 -= trans.translation.x / trans.scale.x;
+ transformed_window_rect.y0 -= trans.translation.y / trans.scale.y;
+ transformed_window_rect.x1 = transformed_window_rect.x0 + (~~window_width / trans.scale.x);
+ transformed_window_rect.y1 = transformed_window_rect.y0 + (~~window_height / trans.scale.y);
+ }
+
+ i := 0;
+ new_top: ^Application_Window = null;
+ for window: state.windows_sorted {
+ defer i += 1;
+
+ if !(window.window_state->get_rectangle() |> ui.Rectangle.intersects(transformed_window_rect)) do continue;
+
+ if ui.window_start(^window.window_state, increment=hash.to_u32(window.id)) {
+ new_top = window;
+ }
+ defer ui.window_end();
+
+ window.window_state.background_color = config.Colors.background;
+ window.window_state.border_color = config.Colors.primary_dark;
+
+ window.draw(window.draw_data, window);
+
+ if window.window_state.should_close {
+ close_window(window.id);
+ }
+ }
+
+ if new_top != null {
+ move_window_to_top(new_top.id);
+ }
+
+ ui.workspace_end();
+
+ state.window_switcher_state->draw(window_rectangle);
+
+ // Menu bar drawing
+ {
+ #insert gfx.save_matrix;
+
+ gfx.identity();
+ ui.menubar(menu_bar, ^search_buffer, ~~ui.Menu_Bar_Option.[
+ .{ label = "File" },
+ .{ label = "Test" },
+ ]);
+ }
+
+ // Debug log drawing
+ draw_debug_log(window_rectangle);
+
+ gfx.flush();
+ ui.end_frame();
+}
+
+open_tool_opener :: () {
+ mouse_pos := gfx.transform_point(^state.workspace_state.transform, .{ ui.mouse_state.x_, ui.mouse_state.y_ });
+
+ close_window("tool_opener");
+ open_window("tool_opener", "Tool Opener", .{ mouse_pos.x, mouse_pos.y }, init_size=.{ 200, 400 },
+ draw=(_: rawptr, win: ^Application_Window) {
+ window_rect := ui.Rectangle.{ 0, 0, win.window_state.size.x, win.window_state.size.y };
+
+ ui.scrollable_region_start(window_rect, maximum_y=~~(registered_tools.count - 1) * 40.0f);
+ defer ui.scrollable_region_stop();
+
+ button_rect : ui.Rectangle;
+ button_theme := ui.default_button_theme;
+
+ for ^entry: registered_tools {
+ button_theme.background_color = config.Colors.secondary if window_is_open(entry.id) else config.Colors.primary_dark;
+
+ button_rect, window_rect = ui.Flow.split_horizontal(window_rect, top_height=40);
+ if ui.button(button_rect, entry.name, theme=^button_theme, increment=hash.to_u32(entry.id)) {
+ entry.open();
+
+ @Setting
+ close_window("tool_opener");
+ }
+ }
+ });
+}
+
+#private_file background_tile_texture : gfx.Texture;
+#private_file draw_background_lines :: (width: f32, height: f32, line_color := gfx.Color4.{0.2, 0.2, 0.2}, line_spacing := 32.0f) {
+ gl :: package gl
+
+ #insert gfx.save_matrix;
+
+ trans := gfx.global_renderer->get_transform();
+ sx := trans.scale.x * line_spacing;
+ sy := trans.scale.y * line_spacing;
+ tx := -trans.translation.x / sx;
+ ty := -trans.translation.y / sy;
+
+ gfx.identity();
+ gfx.set_texture(^background_tile_texture);
+ gfx.textured_rect(.{ 0, 0 }, .{ width, height }, .{ tx, ty }, .{ width / sx, height / sy }, color=line_color);
+ gfx.set_texture();
+}
--- /dev/null
+package app
+
+use package core
+
+#private_file events :: package js_events
+#private_file gl :: package gl
+#private_file gfx :: package immediate_mode
+#private_file ui :: package ui
+#private_file config :: package config
+#private_file wasm :: package wasm_utils
+#private_file debug :: package debug
+
+use package debug { init as debug_init, debug_log, draw_debug_log }
+use package core.intrinsics.onyx { __initialize }
+
+Colorscheme :: enum {
+ Undefined;
+ Dark;
+ Light;
+}
+
+colorscheme_switch :: (new_scheme: Colorscheme) {
+ if state.colorscheme == new_scheme do return;
+
+ state.colorscheme = new_scheme;
+
+ colors_scheme_file: str;
+ switch state.colorscheme {
+ case .Dark do colors_scheme_file = config.dark_color_scheme_file;
+ case .Light do colors_scheme_file = config.light_color_scheme_file;
+ case #default do assert(false, "Bad colorscheme setting.");
+ }
+
+ color_file := events.request_file(colors_scheme_file);
+ map.put(^on_file_load_callbacks, color_file, load_colors);
+}
+
+load_colors :: (event: ^events.Event) {
+ json :: package json
+
+ assert(event.kind == .FileRequest, "Bad event type");
+ assert(event.file.status == .Success, "Failed to load color file");
+
+ color_data := memory.make_slice(u8, event.file.size);
+ defer if color_data.count > 0 do cfree(color_data.data);
+ events.get_requested_file_data(event.file.file_id, color_data);
+
+ arena := alloc.arena.make(context.allocator, 4096);
+ defer alloc.arena.free(^arena);
+ colors := json.decode(color_data, alloc.arena.make_allocator(^arena));
+ defer json.free(colors);
+
+ config.Colors.dark_background = decode_color(colors.root["dark_background"]);
+ config.Colors.background = decode_color(colors.root["background"]);
+ config.Colors.foreground = decode_color(colors.root["foreground"]);
+
+ config.Colors.keyword = decode_color(colors.root["keyword"]);
+ config.Colors.value = decode_color(colors.root["value"]);
+ config.Colors.jumppoint = decode_color(colors.root["jumppoint"]);
+
+ config.Colors.primary = decode_color(colors.root["primary"]);
+ config.Colors.primary_light = decode_color(colors.root["primary_light"]);
+ config.Colors.primary_dark = decode_color(colors.root["primary_dark"]);
+ config.Colors.primary_text = decode_color(colors.root["primary_text"]);
+
+ config.Colors.secondary = decode_color(colors.root["secondary"]);
+ config.Colors.secondary_light = decode_color(colors.root["secondary_light"]);
+ config.Colors.secondary_dark = decode_color(colors.root["secondary_dark"]);
+ config.Colors.secondary_text = decode_color(colors.root["secondary_text"]);
+
+ update_ui_colors();
+
+ decode_color :: (v: ^json.Value) -> gfx.Color4 {
+ return .{
+ r = ~~v[0]->as_float(),
+ g = ~~v[1]->as_float(),
+ b = ~~v[2]->as_float(),
+ };
+ }
+
+ debug_log(.Info, "Successfully loaded colorscheme '{}'.", state.colorscheme);
+}
+
+update_ui_colors :: () {
+ ui.default_text_theme.text_color = config.Colors.foreground;
+
+ ui.default_button_theme.text_color = config.Colors.primary_text;
+ ui.default_button_theme.background_color = config.Colors.primary_dark;
+ ui.default_button_theme.hover_color = config.Colors.primary;
+ ui.default_button_theme.click_color = config.Colors.primary_light;
+ ui.default_button_theme.border_color = config.Colors.primary;
+
+ ui.default_textbox_theme.text_color = config.Colors.primary_text;
+ ui.default_textbox_theme.background_color = config.Colors.primary_dark;
+ ui.default_textbox_theme.hover_color = config.Colors.primary;
+ ui.default_textbox_theme.click_color = config.Colors.primary_light;
+ ui.default_textbox_theme.border_color = config.Colors.primary;
+
+ ui.default_radio_theme.text_color = config.Colors.foreground;
+ ui.default_radio_theme.radio_color = config.Colors.primary_dark;
+ ui.default_radio_theme.selected_color = config.Colors.secondary;
+ ui.default_radio_theme.hover_color = config.Colors.primary;
+ ui.default_radio_theme.click_color = config.Colors.primary_light;
+}
--- /dev/null
+package debug
+
+use package core
+#private_file ui :: package ui
+#private_file gfx :: package immediate_mode
+#private_file config :: package config
+
+#private y_scroll := 0.0f;
+#private debug_log_y_offset := 0.0f;
+#private debug_log_y_offset_target := 0.0f;
+
+init :: () {
+ log_buffer.line_arena = alloc.arena.make(context.allocator, 4096);
+ log_buffer.lines = array.make(str);
+
+ debug_log(.Debug, "Debug system initialized.", null);
+}
+
+Severity :: enum {
+ Debug;
+ Info;
+ Warning;
+ Error;
+}
+
+minimum_severity := Severity.Debug;
+
+debug_log_toggle :: () {
+ debug_log_y_offset_target = 1 - debug_log_y_offset_target;
+}
+
+debug_log_transitioning :: () -> bool {
+ return debug_log_y_offset != debug_log_y_offset_target;
+}
+
+debug_log_update :: (dt: f32) {
+ ui.move_towards(^debug_log_y_offset, debug_log_y_offset_target, 4 * dt);
+}
+
+debug_log_clear :: () {
+ array.clear(^log_buffer.lines);
+ alloc.arena.free(^log_buffer.line_arena);
+
+ log_buffer.line_arena = alloc.arena.make(context.allocator, 4096);
+}
+
+debug_log :: (severity: Severity, format: str, args: ..any) {
+ if severity < minimum_severity do return;
+
+ buffer1: [4096] u8;
+ s := conv.str_format_va(format, ~~buffer1, ~~args);
+ lines := string.split(s, #char "\n", context.temp_allocator);
+
+ line_alloc := alloc.arena.make_allocator(^log_buffer.line_arena);
+ for l: lines {
+ if l.count == 0 do continue;
+
+ buffer2: [4096] u8;
+ s = conv.str_format("[{}] {}\n", ~~buffer2, severity, l);
+ line := string.alloc_copy(s, line_alloc);
+
+ array.push(^log_buffer.lines, line);
+ }
+}
+
+draw_debug_log :: (window_rectangle: ui.Rectangle, site := #callsite) {
+ if debug_log_y_offset == 0.0f do return;
+
+ r, _ := ui.Flow.split_horizontal(window_rectangle, top_percent=.5);
+ height := ui.Rectangle.height(r);
+ r.y0 -= height * (1 - debug_log_y_offset);
+ r.y1 -= height * (1 - debug_log_y_offset);
+
+ hash := ui.get_site_hash(site);
+
+ mx, my := ui.get_mouse_position();
+ if ui.Rectangle.contains(r, mx, my) {
+ ui.set_hot_item(hash, false);
+ }
+
+ line_spacing := 28.0f;
+
+ if ui.is_hot_item(hash) {
+ if ui.mouse_state.dwheel > 0 do y_scroll -= 20.0f;
+ if ui.mouse_state.dwheel < 0 do y_scroll += 20.0f;
+
+ if ui.is_key_down(38) do y_scroll -= 20.0f;
+ if ui.is_key_down(40) do y_scroll += 20.0f;
+ }
+
+ x, y := ui.Rectangle.bottom_left(r);
+ w, h := ui.Rectangle.dimensions(r);
+
+ gfx.push_scissor(x, y - h, w, h);
+ defer gfx.pop_scissor();
+
+ background_color := config.Colors.background;
+ background_color.a = 0.8;
+ ui.draw_rect(r, color=background_color);
+
+ scale :: 0.75f;
+ y_offset := line_spacing * scale + y_scroll;
+
+ while i := log_buffer.lines.count - 1; cast(i32) i >= 0 {
+ defer i -= 1;
+
+ ui.draw_text_raw(log_buffer.lines[i], x, y - y_offset, color=.{ 0.2, 0.7, 0.2 }, font=1, size=scale);
+ y_offset += line_spacing * scale;
+ }
+
+ clear_button_rect := ui.Rectangle.{
+ x0 = r.x0, x1 = r.x0 + 200,
+ y0 = r.y0, y1 = r.y0 + 50,
+ };
+
+ if ui.button(clear_button_rect, "Clear log") {
+ debug_log_clear();
+ }
+
+ app :: package app
+ colorscheme := app.state.colorscheme;
+
+ clear_button_rect.x0 += 220;
+ clear_button_rect.x1 += 220;
+ switched := ui.radio(clear_button_rect, ^colorscheme, .Dark, "Dark mode");
+ clear_button_rect.x0 += 200;
+ clear_button_rect.x1 += 200;
+ switched = switched || ui.radio(clear_button_rect, ^colorscheme, .Light, "Light mode");
+
+ if switched {
+ app.colorscheme_switch(colorscheme);
+ }
+}
+
+#private_file log_buffer : struct {
+ line_arena : alloc.arena.ArenaState;
+ lines : [..] str;
+}
+
--- /dev/null
+package app
+
+use package core
+
+#private_file events :: package js_events
+#private_file gl :: package gl
+#private_file gfx :: package immediate_mode
+#private_file ui :: package ui
+#private_file config :: package config
+#private_file wasm :: package wasm_utils
+#private_file debug :: package debug
+
+use package debug { init as debug_init, debug_log, draw_debug_log }
+use package core.intrinsics.onyx { __initialize }
+
+open_window :: (id: str,
+ title: str,
+ init_position: gfx.Vector2,
+ draw: (rawptr, ^Application_Window) -> void,
+ init_size := gfx.Vector2.{ 600, 600 }) {
+
+ if map.has(^state.windows_map, id) {
+ debug_log(.Warning, "Window with id '{}' is already registered.", id);
+ return;
+ }
+
+ debug_log(.Info, "Registering window with id '{}'.", id);
+
+ win := alloc.pool.pool_alloc(^state.windows_store);
+ __initialize(win);
+ __initialize(^win.window_state);
+
+ win.id = id;
+
+ win.window_state.background_color = config.Colors.background;
+ win.window_state.border_color = config.Colors.primary_dark;
+ win.window_state.position = init_position;
+ win.window_state.size = init_size;
+ win.window_state.title = title;
+
+ win.draw = draw;
+
+ map.put(^state.windows_map, id, win);
+ array.push(^state.windows_sorted, win);
+}
+
+move_window_to_top :: (id: str) {
+ if !map.has(^state.windows_map, id) {
+ debug_log(.Warning, "Window '{}' does not exist when trying to move to top.", id);
+ return;
+ }
+
+ win := map.get(^state.windows_map, id);
+
+ index := array.find(^state.windows_sorted, win);
+ assert(index >= 0, "Window not found.");
+
+ array.transplant(^state.windows_sorted, index, state.windows_sorted.count - 1);
+}
+
+focus_window :: (id: str) {
+ if !map.has(^state.windows_map, id) {
+ debug_log(.Warning, "Window '{}' does not exist when trying to focus.", id);
+ return;
+ }
+
+ win := map.get(^state.windows_map, id);
+
+ transform: gfx.Transform;
+ gfx.transform_identity(^transform);
+
+ entire_width, entire_height := gfx.get_window_size();
+ hw := cast(f32) entire_width / 2;
+ hh := cast(f32) entire_height / 2;
+
+ win_hw := win.window_state.size.x / 2;
+ win_hh := win.window_state.size.y / 2;
+
+ scale_x, scale_y := state.workspace_state.transform.scale.x, state.workspace_state.transform.scale.y;
+
+ gfx.transform_translate(^transform, .{hw, hh});
+ gfx.transform_scale(^transform, .{ scale_x, scale_y });
+ gfx.transform_apply(^transform, .{
+ translation = .{ -win.window_state.position.x - win_hw, -win.window_state.position.y - win_hh },
+ scale = .{ 1, 1 }
+ });
+
+ state.workspace_state.target_transform = transform;
+ state.workspace_state.transform_transition = 1.0f;
+}
+
+close_window :: (id: str) {
+ if !map.has(^state.windows_map, id) {
+ return;
+ }
+
+ win := map.get(^state.windows_map, id);
+
+ alloc.pool.pool_free(^state.windows_store, win);
+ map.delete(^state.windows_map, id);
+ array.remove(^state.windows_sorted, win);
+}
+
+window_is_open :: (id: str) -> bool {
+ return map.has(^state.windows_map, id);
+}
\ No newline at end of file
--- /dev/null
+package app
+
+#private_file ui :: package ui
+#private_file gfx :: package immediate_mode
+#private_file config :: package config
+#private_file math :: package core.math
+#private_file string :: package core.string
+#private_file memory :: package core.memory
+use package core.intrinsics.onyx { __initialize }
+
+Window_Switcher_State :: struct {
+ visibility := 0.0f;
+ visibility_target := 0.0f;
+
+ window := ui.Window_State.{
+ gfx.Vector2.zero, gfx.Vector2.zero,
+ resizable = false, draggable = false,
+ };
+
+ search_buffer: string.String_Buffer;
+ x_scroll := 0.0f;
+ y_scroll := 0.0f;
+
+ selected_index: i32 = 0;
+
+ init :: (use state: ^Window_Switcher_State) {
+ __initialize(state);
+ search_buffer = string.buffer_make(memory.make_slice(u8, 256));
+ }
+
+ toggle :: (use this: ^Window_Switcher_State) {
+ visibility_target = 1 - visibility_target;
+ if visibility_target == 0 do this->reset();
+ }
+
+ reset :: (use this: ^Window_Switcher_State) {
+ selected_index = state.windows_sorted.count - 1;
+ string.buffer_clear(^search_buffer);
+ }
+
+ is_animating :: (use state: ^Window_Switcher_State) -> bool {
+ return visibility_target != visibility;
+ }
+
+ select_window :: (use state: ^Window_Switcher_State) {
+ window := (package app).state.windows_sorted[selected_index];
+ state->toggle();
+
+ (package app).move_window_to_top(window.id);
+ (package app).focus_window(window.id);
+ }
+
+ draw :: (use s: ^Window_Switcher_State, window_rectangle: ui.Rectangle) {
+ ui.move_towards(^visibility, visibility_target, 0.08f);
+ if visibility == 0.0f do return;
+
+ #insert gfx.save_matrix;
+ gfx.identity();
+
+ quater_width := ui.Rectangle.width(window_rectangle) / 4;
+ quater_height := ui.Rectangle.width(window_rectangle) / 4;
+
+ wv := math.lerp(visibility, quater_width, 100);
+ hv := math.lerp(visibility, quater_height, 100);
+ switcher_rect := ui.Flow.padding(window_rectangle, wv, hv, wv, hv);
+ window.position = .{ switcher_rect.x0, switcher_rect.y0 };
+ window.size = .{ switcher_rect.x1 - switcher_rect.x0, switcher_rect.y1 - switcher_rect.y0 };
+ window.title = "Window switcher";
+
+ window.background_color = config.Colors.background;
+ window.border_color = config.Colors.primary_dark;
+
+ ui.window_start(^window);
+ // Enter is pressed.
+ if ui.is_key_just_down(13) do s->select_window();
+
+ if ui.is_key_just_down(38) do selected_index += 1;
+ if ui.is_key_just_down(40) do selected_index -= 1;
+
+ selected_index = math.clamp(selected_index, 0, state.windows_sorted.count - 1);
+
+ window_rect := ui.Rectangle.{ 0, 0, window.size.x, window.size.y };
+
+ textbox_rect : ui.Rectangle;
+ textbox_rect, window_rect = ui.Flow.split_horizontal(window_rect, top_height=48);
+
+ textbox_theme := ui.default_textbox_theme;
+ textbox_theme.border_color = config.Colors.primary_dark;
+
+ ui.textbox(textbox_rect, ^search_buffer, "Search for a window...", theme=^textbox_theme);
+
+ text_rect : ui.Rectangle;
+ mx, my := ui.get_mouse_position();
+
+ i := cast(i32) state.windows_sorted.count - 1;
+ while i >= 0 {
+ window := state.windows_sorted[i];
+ defer i -= 1;
+
+ text_rect, window_rect = ui.Flow.split_horizontal(window_rect, top_height=48);
+
+ if ui.Rectangle.contains(text_rect, mx, my) {
+ selected_index = i;
+ #if #defined(ui.set_cursor) { ui.set_cursor(ui.Cursors.Pointer); }
+ }
+
+ if selected_index == i {
+ ui.draw_rect(text_rect, color=config.Colors.primary_light);
+ }
+
+ text_rect = ui.Flow.padding(text_rect, left=24);
+ ui.draw_text(text_rect, window.window_state.title);
+ }
+
+ ui.window_end();
+
+ if window.should_close {
+ window.should_close = false;
+ visibility_target = 0.0f;
+
+ s->reset();
+ }
+ }
+}
#load "modules/ui/module"
#load "modules/js_events/module"
#load "modules/bmfont/module"
-
- #load "src/main"
- #load "src/app"
- #load "src/features/load_features"
-
#load "src/ui/window"
#load "src/ui/menubar"
- #load "src/debug_log"
+ #load "src/ui/cursor"
+
+ #load "src/app/app"
+ #load "src/app/debug_log"
+ #load "src/app/window_switcher"
+ #load "src/app/window_management"
+ #load "src/app/colors"
+
+ #load "src/features/load_features"
+
+ #load "src/main"
}
#if (package runtime).Runtime == (package runtime).Runtime_Wasi {
ONLY_REDRAW_ON_EVENTS :: true
-color_scheme_file :: "/res/colors_dark.json"
+light_color_scheme_file :: "/res/colors_light.json"
+dark_color_scheme_file :: "/res/colors_dark.json"
Colors : struct {
dark_background : Color4;
+++ /dev/null
-package debug
-
-use package core
-#private_file ui :: package ui
-#private_file gfx :: package immediate_mode
-#private_file config :: package config
-
-#private y_scroll := 0.0f;
-#private debug_log_y_offset := 0.0f;
-#private debug_log_y_offset_target := 0.0f;
-
-init :: () {
- log_buffer.line_arena = alloc.arena.make(context.allocator, 4096);
- log_buffer.lines = array.make(str);
-
- debug_log(.Debug, "Debug system initialized.", null);
-}
-
-Severity :: enum {
- Debug;
- Info;
- Warning;
- Error;
-}
-
-minimum_severity := Severity.Debug;
-
-debug_log_toggle :: () {
- debug_log_y_offset_target = 1 - debug_log_y_offset;
-}
-
-debug_log_transitioning :: () -> bool {
- return debug_log_y_offset != debug_log_y_offset_target;
-}
-
-debug_log_update :: (dt: f32) {
- ui.move_towards(^debug_log_y_offset, debug_log_y_offset_target, 4 * dt);
-}
-
-debug_log_clear :: () {
- array.clear(^log_buffer.lines);
- alloc.arena.free(^log_buffer.line_arena);
-
- log_buffer.line_arena = alloc.arena.make(context.allocator, 4096);
-}
-
-debug_log :: (severity: Severity, format: str, args: ..any) {
- if severity < minimum_severity do return;
-
- buffer1: [4096] u8;
- s := conv.str_format_va(format, ~~buffer1, ~~args);
- lines := string.split(s, #char "\n", context.temp_allocator);
-
- line_alloc := alloc.arena.make_allocator(^log_buffer.line_arena);
- for l: lines {
- if l.count == 0 do continue;
-
- buffer2: [4096] u8;
- s = conv.str_format("[{}] {}\n", ~~buffer2, severity, l);
- line := string.alloc_copy(s, line_alloc);
-
- array.push(^log_buffer.lines, line);
- }
-}
-
-draw_debug_log :: (window_rectangle: ui.Rectangle, site := #callsite) {
- if debug_log_y_offset == 0.0f do return;
-
- r, _ := ui.Flow.split_horizontal(window_rectangle, top_percent=.5);
- height := ui.Rectangle.height(r);
- r.y0 -= height * (1 - debug_log_y_offset);
- r.y1 -= height * (1 - debug_log_y_offset);
-
- hash := ui.get_site_hash(site);
-
- mx, my := ui.get_mouse_position();
- if ui.Rectangle.contains(r, mx, my) {
- ui.set_hot_item(hash, false);
- }
-
- line_spacing := 28.0f;
-
- if ui.is_hot_item(hash) {
- if ui.mouse_state.dwheel > 0 do y_scroll -= 20.0f;
- if ui.mouse_state.dwheel < 0 do y_scroll += 20.0f;
-
- if ui.is_key_down(38) do y_scroll -= 20.0f;
- if ui.is_key_down(40) do y_scroll += 20.0f;
- }
-
- x, y := ui.Rectangle.bottom_left(r);
- w, h := ui.Rectangle.dimensions(r);
-
- gfx.push_scissor(x, y - h, w, h);
- defer gfx.pop_scissor();
-
- background_color := config.Colors.background;
- background_color.a = 0.8;
- ui.draw_rect(r, color=background_color);
-
- scale :: 0.75f;
- y_offset := line_spacing * scale + y_scroll;
-
- while i := log_buffer.lines.count - 1; cast(i32) i >= 0 {
- defer i -= 1;
-
- ui.draw_text_raw(log_buffer.lines[i], x, y - y_offset, color=.{ 0.2, 0.7, 0.2 }, font=1, size=scale);
- y_offset += line_spacing * scale;
- }
-
- clear_button_rect := ui.Rectangle.{
- x0 = r.x0, x1 = r.x0 + 200,
- y0 = r.y0, y1 = r.y0 + 50,
- };
-
- if ui.button(clear_button_rect, "Clear log") {
- debug_log_clear();
- }
-}
-
-#private_file log_buffer : struct {
- line_arena : alloc.arena.ArenaState;
- lines : [..] str;
-}
-
--- /dev/null
+package feature.hex_viewer
+
+#load "./hex_viewer"
+
+Feature_Hex_Viewer :: struct {
+ setup := setup;
+}
\ No newline at end of file
--- /dev/null
+package feature.hex_viewer
+
+#private_file app :: package app
+#private_file ui :: package ui
+#private_file config :: package config
+
+use package core
+use package debug { debug_log }
+
+window_id :: "hex_viewer"
+window_name :: "Hex Viewer"
+
+Hex_Viewer_State :: struct {
+ scrollable_region := ui.Scrollable_Region_State.{};
+}
+
+// Should this be global? Or should each viewer window get their own?
+#private viewer_state := Hex_Viewer_State.{};
+
+window_draw :: (_: rawptr, win: ^app.Application_Window) {
+ orig_r: ui.Rectangle = .{ 0, 0, win.window_state.size.x, win.window_state.size.y };
+ r := orig_r;
+
+ text_theme := ui.default_text_theme;
+ text_theme.font = config.Fonts.FiraCode.index;
+ text_theme.font_size = 1.0f;
+
+ ui.use_font(text_theme.font);
+ em_height := ui.get_text_height("M", text_theme.font_size) / 2;
+
+ win.window_state.max_size.x = (10 + 1 + 3 * 16 + 2 + 16) * em_height;
+ win.window_state.background_color = config.Colors.dark_background;
+
+ if !app.state.has_active_file {
+ ui.draw_text(r, "No loaded file.", theme=^text_theme);
+ return;
+ }
+
+ file_data := app.state.file.data;
+
+ {
+ // This is a hack to get the region to be "scrollable" without actually applying any of the scrolling effects
+ ui.scrollable_region_start(r, maximum_y=em_height*~~(file_data.count / 8 - 1), state=^viewer_state.scrollable_region);
+ ui.scrollable_region_stop();
+ }
+
+ line_data: [128] u8;
+ line := string.buffer_make(~~line_data);
+
+ first_line: i32 = ~~math.floor(-viewer_state.scrollable_region.transform.translation.y / (2*em_height));
+ line_count: i32 = ~~math.ceil(win.window_state.size.y / (em_height));
+
+ visible_lines := first_line .. (first_line + line_count);
+
+ for i: visible_lines {
+ if i * 16 >= file_data.count do break;
+
+ offset_data: [16] u8;
+ output := conv.str_format("{w8b16} ", ~~offset_data, i * 16);
+ ui.draw_text(r, output, theme=^text_theme);
+ r = ui.Flow.padding(r, top=32);
+ }
+
+ r = ui.Flow.padding(orig_r, left=10*em_height);
+
+ for i: visible_lines {
+ if i * 16 >= file_data.count do break;
+
+ string.buffer_clear(^line);
+
+ for ch: 16 {
+ index := i * 16 + ch;
+ if index >= file_data.count do break;
+
+ ch_data: [8] u8;
+ output := conv.str_format("{w2b16} ", ~~ch_data, cast(u32) file_data[index]);
+ string.buffer_append(^line, output);
+ }
+
+ ui.draw_text(r, string.buffer_to_str(^line), theme=^text_theme);
+ r = ui.Flow.padding(r, top=32);
+ }
+
+ r = ui.Flow.padding(orig_r, left=3*em_height*16 + 10*em_height + 32);
+
+ for i: visible_lines {
+ if i * 16 >= file_data.count do break;
+
+ string.buffer_clear(^line);
+
+ for ch: 16 {
+ index := i * 16 + ch;
+ if index >= file_data.count do break;
+
+ char := file_data[index];
+
+ data: [1] u8;
+ if char >= 32 && char <= 126 do data[0] = char;
+ else do data[0] = #char ".";
+
+ string.buffer_append(^line, ~~data);
+ }
+
+ ui.draw_text(r, string.buffer_to_str(^line), theme=^text_theme);
+ r = ui.Flow.padding(r, top=32);
+ }
+}
+
+open_window :: () {
+ app.open_window(window_id, window_name, .{ 0, 0 }, window_draw, .{ 1300, 650 });
+ app.move_window_to_top(window_id);
+ app.focus_window(window_id);
+
+/*
+ app.open_window("dummy", "DUMMY TESTING", .{ 0, 0 }, (_: rawptr, _: ^app.Application_Window) {
+ buffer: [1024] u8;
+ s := conv.str_format("{p}", ~~buffer, viewer_state);
+ ui.draw_text(.{0,0,0,0}, s);
+ });
+*/
+}
+
+setup :: () {
+ debug_log(.Debug, "Initializing hex viewer...", 0);
+
+ array.push(^app.registered_tools, .{
+ id = window_id,
+ name = window_name,
+ open = open_window,
+ });
+}
\ No newline at end of file
// Add #load statements for the features
-#load "./wasm/feature"
\ No newline at end of file
+#load "./wasm/feature"
+#load "./hex_editor/feature"
+#load "./text_editor/feature"
\ No newline at end of file
--- /dev/null
+package feature.text_editor
+
+#load "./text_editor"
+
+Feature_Text_Editor :: struct {
+ setup := setup;
+}
\ No newline at end of file
--- /dev/null
+package feature.text_editor
+
+#private_file app :: package app
+#private_file ui :: package ui
+#private_file config :: package config
+
+use package core
+use package debug { debug_log }
+
+window_id :: "text_editor"
+window_name :: "Text Editor"
+
+open_text_editor :: () {
+ app.open_window(window_id, window_name, .{ 0, 0 }, (_: rawptr, win: ^app.Application_Window) {
+ if !app.state.has_active_file do return;
+
+ ui.scrollable_region_start(.{ 0, 0, win.window_state.size.x, win.window_state.size.y });
+ defer ui.scrollable_region_stop();
+
+ text_theme := ui.default_text_theme;
+ text_theme.font = config.Fonts.FiraCode.index;
+ ui.draw_text(.{ 0, 0, 200, 200 }, app.state.file.data, theme=^text_theme);
+ });
+
+ app.move_window_to_top(window_id);
+ app.focus_window(window_id);
+}
+
+setup :: () {
+ debug_log(.Debug, "Initializing text editor...", 0);
+
+ array.push(^app.registered_tools, .{
+ id = window_id,
+ name = window_name,
+ open = open_text_editor,
+ });
+}
\ No newline at end of file
-package feature_wasm
+package feature.wasm
#load "./wasm"
-package feature_wasm
+package feature.wasm
use package app { Application_State }
use package debug { debug_log }
-setup :: (use app: ^Application_State) {
+setup :: () {
debug_log(.Info, "Wasm Loader Loaded from {}", #file);
}
\ No newline at end of file
--- /dev/null
+// Maybe this should become part of the core ui library?
+
+package ui
+
+Cursors :: struct {
+ Default :: "default";
+ Text :: "text";
+ Pointer :: "pointer";
+
+ NS_Resize :: "ns-resize";
+ EW_Resize :: "ew-resize";
+ NWSE_Resize :: "nwse-resize";
+
+ Move :: "move";
+}
+
+set_cursor :: (cursor_name: str) -> void #foreign "decompiler" "set_cursor" ---
\ No newline at end of file
position: gfx.Vector2;
size: gfx.Vector2;
- border_width := 4.0f;
+ max_size := gfx.Vector2.{ 100000.0f, 100000.0f };
+
+ title := "(undefined)";
+
+ border_width := 10.0f;
border_color := gfx.Color4.{ 1, 0, 0 };
background_color := gfx.Color4.{ 0.2, 0.2, 0.2 };
- active_color := gfx.Color4.{ 0.3, 0.3, 0.3 };
+
+ bar_height := 34.0f;
+
+ draggable := true;
+ resizable := true;
+
+ should_close := false;
+ dragging := false;
+ resizing := false;
get_rectangle :: (use w: ^Window_State) -> Rectangle {
- return .{ position.x, position.y, position.x + size.x, position.y + size.y };
+ bw := border_width;
+ if dragging do bw *= 5;
+
+ return .{ position.x - bw, position.y - bw - bar_height,
+ position.x + size.x + bw, position.y + size.y + bw };
}
}
-// I don't know if this needs the site and increment parameters
-window_start :: (use state: ^Window_State, site := #callsite, increment := 0) {
+window_start :: (use state: ^Window_State, site := #callsite, increment := 0) -> bool {
hash := get_site_hash(site, increment);
animation_state := map.get(^animation_states, hash);
}
if is_hot_item(hash) {
- move_towards(^animation_state.hover_time, 1.0f, 0.06);
+ if mouse_state.left_button_just_down && !state.dragging && state.draggable {
+ state.dragging = true;
+ set_active_item(hash);
+ }
+
+ if mouse_state.right_button_just_down && !state.dragging && state.resizable {
+ state.resizing = true;
+ set_active_item(hash);
+ }
+
+ if state.dragging {
+ if !mouse_state.left_button_down {
+ state.dragging = false;
+ set_active_item(0);
+
+ } else {
+ dx, dy := get_mouse_delta();
+ state.position.x -= dx;
+ state.position.y -= dy;
+ }
+ }
+
+ if state.resizing {
+ if !mouse_state.right_button_down {
+ state.resizing = false;
+ set_active_item(0);
+
+ } else {
+ state.size.x = mx - state.position.x;
+ state.size.y = my - state.position.y;
+ }
+ }
+
} else {
- move_towards(^animation_state.hover_time, 0.0f, 0.06);
+ state.dragging = false;
+ state.resizing = false;
+ }
+
+ #if #defined(set_cursor) {
+ if state.dragging do set_cursor(Cursors.Move);
+ if state.resizing do set_cursor(Cursors.NWSE_Resize);
}
- x, y := position.x + border_width, position.y + border_width;
- w, h := size.x - border_width * 2, size.y - border_width * 2;
+ size.x = math.clamp(size.x, 300, max_size.x);
+ size.y = math.clamp(size.y, 100, max_size.y);
+
+ x, y := position.x, position.y;
+ w, h := size.x, size.y;
+
+ draw_rect(x - border_width, y - border_width - bar_height, w + border_width * 2, h + border_width * 2 + bar_height, color=border_color);
+ draw_rect(x, y, w, h, color=state.background_color);
- bg_color := color_lerp(animation_state.hover_time, background_color, active_color);
- draw_rect(position.x, position.y, size.x, size.y, color=border_color);
- draw_rect(x, y, w, h, bg_color);
+ title_theme := default_text_theme;
+ title_theme.font_size = 1.0f;
+ title_theme.text_color = .{ 1, 1, 1 }; @ThemeConfiguration
+ draw_text(.{ x, y - bar_height, x + w, y }, state.title, theme=^title_theme);
+
+ if button(.{ x + w - 48, y - bar_height - border_width, x + w + border_width, y }, "X", increment=increment + hash) {
+ state.should_close = true;
+ }
if animation_state.click_time > 0 || animation_state.hover_time > 0 {
map.put(^animation_states, hash, animation_state);
map.delete(^animation_states, hash);
}
- gfx.push_scissor(x, y, w, h);
gfx.push_matrix();
+ gfx.push_scissor(x, y, w, h);
gfx.apply_transform(.{ translation = .{ x, y }, scale = .{ 1, 1 } });
+
+ return is_active_item(hash);
}
window_end :: () {