From: Brendan Hansen Date: Tue, 10 Aug 2021 19:09:31 +0000 (-0500) Subject: actually making this something interesting X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=06632c99a611c980190aea415d054a1fd9819048;p=onyx-wasm-analyzer.git actually making this something interesting --- diff --git a/res/colors_dark.json b/res/colors_dark.json index 439f912..3d4f29e 100644 --- a/res/colors_dark.json +++ b/res/colors_dark.json @@ -7,9 +7,9 @@ "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 ], diff --git a/site/js/decompiler.js b/site/js/decompiler.js index 3027307..169dbd3 100644 --- a/site/js/decompiler.js +++ b/site/js/decompiler.js @@ -27,5 +27,13 @@ window.ONYX_MODULES.push({ 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; } }); diff --git a/src/app.onyx b/src/app.onyx deleted file mode 100644 index 2d18130..0000000 --- a/src/app.onyx +++ /dev/null @@ -1,407 +0,0 @@ -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; -} diff --git a/src/app/app.onyx b/src/app/app.onyx new file mode 100644 index 0000000..b9c8c1d --- /dev/null +++ b/src/app/app.onyx @@ -0,0 +1,439 @@ +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(); +} diff --git a/src/app/colors.onyx b/src/app/colors.onyx new file mode 100644 index 0000000..86784bf --- /dev/null +++ b/src/app/colors.onyx @@ -0,0 +1,104 @@ +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; +} diff --git a/src/app/debug_log.onyx b/src/app/debug_log.onyx new file mode 100644 index 0000000..72b1c81 --- /dev/null +++ b/src/app/debug_log.onyx @@ -0,0 +1,139 @@ +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; +} + diff --git a/src/app/window_management.onyx b/src/app/window_management.onyx new file mode 100644 index 0000000..273f512 --- /dev/null +++ b/src/app/window_management.onyx @@ -0,0 +1,106 @@ +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 diff --git a/src/app/window_switcher.onyx b/src/app/window_switcher.onyx new file mode 100644 index 0000000..e54e90b --- /dev/null +++ b/src/app/window_switcher.onyx @@ -0,0 +1,124 @@ +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(); + } + } +} diff --git a/src/build.onyx b/src/build.onyx index 4cc582d..a1c1150 100644 --- a/src/build.onyx +++ b/src/build.onyx @@ -11,15 +11,20 @@ #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 { diff --git a/src/config.onyx b/src/config.onyx index 8f11ac6..cd3f1e0 100644 --- a/src/config.onyx +++ b/src/config.onyx @@ -6,7 +6,8 @@ use package immediate_mode { Color4 } 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; diff --git a/src/debug_log.onyx b/src/debug_log.onyx deleted file mode 100644 index c014014..0000000 --- a/src/debug_log.onyx +++ /dev/null @@ -1,125 +0,0 @@ -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; -} - diff --git a/src/features/hex_editor/feature.onyx b/src/features/hex_editor/feature.onyx new file mode 100644 index 0000000..6b8548e --- /dev/null +++ b/src/features/hex_editor/feature.onyx @@ -0,0 +1,7 @@ +package feature.hex_viewer + +#load "./hex_viewer" + +Feature_Hex_Viewer :: struct { + setup := setup; +} \ No newline at end of file diff --git a/src/features/hex_editor/hex_viewer.onyx b/src/features/hex_editor/hex_viewer.onyx new file mode 100644 index 0000000..17cf620 --- /dev/null +++ b/src/features/hex_editor/hex_viewer.onyx @@ -0,0 +1,131 @@ +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 diff --git a/src/features/load_features.onyx b/src/features/load_features.onyx index 6e111df..367ab17 100644 --- a/src/features/load_features.onyx +++ b/src/features/load_features.onyx @@ -1,4 +1,6 @@ // 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 diff --git a/src/features/text_editor/feature.onyx b/src/features/text_editor/feature.onyx new file mode 100644 index 0000000..8bb1fc4 --- /dev/null +++ b/src/features/text_editor/feature.onyx @@ -0,0 +1,7 @@ +package feature.text_editor + +#load "./text_editor" + +Feature_Text_Editor :: struct { + setup := setup; +} \ No newline at end of file diff --git a/src/features/text_editor/text_editor.onyx b/src/features/text_editor/text_editor.onyx new file mode 100644 index 0000000..8948eb8 --- /dev/null +++ b/src/features/text_editor/text_editor.onyx @@ -0,0 +1,37 @@ +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 diff --git a/src/features/wasm/feature.onyx b/src/features/wasm/feature.onyx index 3fd0983..cf47133 100644 --- a/src/features/wasm/feature.onyx +++ b/src/features/wasm/feature.onyx @@ -1,4 +1,4 @@ -package feature_wasm +package feature.wasm #load "./wasm" diff --git a/src/features/wasm/wasm.onyx b/src/features/wasm/wasm.onyx index 7447238..cac42be 100644 --- a/src/features/wasm/wasm.onyx +++ b/src/features/wasm/wasm.onyx @@ -1,8 +1,8 @@ -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 diff --git a/src/ui/cursor.onyx b/src/ui/cursor.onyx new file mode 100644 index 0000000..70ead9d --- /dev/null +++ b/src/ui/cursor.onyx @@ -0,0 +1,17 @@ +// 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 diff --git a/src/ui/window.onyx b/src/ui/window.onyx index db54a63..b147c7a 100644 --- a/src/ui/window.onyx +++ b/src/ui/window.onyx @@ -6,18 +6,33 @@ Window_State :: struct { 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); @@ -28,17 +43,66 @@ window_start :: (use state: ^Window_State, site := #callsite, increment := 0) { } 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); @@ -46,9 +110,11 @@ window_start :: (use state: ^Window_State, site := #callsite, increment := 0) { 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 :: () {