refactored general purpose editor
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 6 Oct 2021 19:44:27 +0000 (14:44 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 6 Oct 2021 19:44:27 +0000 (14:44 -0500)
src/app/app.onyx
src/app/colors.onyx
src/app/editor.onyx [new file with mode: 0644]
src/app/settings.onyx
src/build.onyx
src/features/hex_editor/feature.onyx
src/features/hex_editor/hex_viewer.onyx
src/features/load_features.onyx
src/features/text_editor/feature.onyx
src/features/wasm/feature.onyx
src/ui/window.onyx

index 6ab1cc2cefad0a09df56100934c6d1d0ba34151b..01e9740c8552d8209612edb96edc014cf1335f5d 100644 (file)
@@ -1,13 +1,13 @@
 package app
 
 #private_file {
-    events :: package js_events
-    gl     :: package gl
-    gfx    :: package immediate_mode
-    ui     :: package ui
-    config :: package config
-    wasm   :: package wasm_utils
-    debug  :: package debug
+    events  :: package js_events
+    gl      :: package gl
+    gfx     :: package immediate_mode
+    ui      :: package ui
+    config  :: package config
+    wasm    :: package wasm_utils
+    debug   :: package debug
 
     use package core
 
@@ -81,7 +81,7 @@ init :: () {
     load_fonts();
 
     settings_load(^state.settings);
-    colorscheme_switch(state.settings.colorscheme);
+    settings_apply(^state.settings);
 
     search_buffer = string.buffer_make(memory.make_slice(u8, 256));
 
@@ -327,8 +327,8 @@ draw :: () {
         }
         defer ui.window_end();
 
-        window.window_state.background_color = config.Colors.background;
-        window.window_state.border_color = config.Colors.primary_dark;
+        // window.window_state.background_color = config.Colors.background;
+        // window.window_state.border_color = config.Colors.primary_dark;
 
         window.draw(window.draw_data, window);
 
@@ -423,6 +423,7 @@ open_tool_opener :: macro () {
 @Relocate // This should be moved to somewhere else when the code base.
 invoke_feature_function :: macro (name: str, Hook_Type := #type () -> void, call := #code f()) {
     use type_info;
+    use package feature { Feature }
 
     #persist features : [..] ^type_info.Type_Info_Struct;
     if features.data == null {
@@ -434,9 +435,14 @@ invoke_feature_function :: macro (name: str, Hook_Type := #type () -> void, call
             if type.kind != .Struct do continue;
 
             feature := cast(^Type_Info_Struct) type;
-            if !string.starts_with(feature.name, "Feature_") do continue;
-
-            features << feature;
+            
+            for ^tag: feature.tags {
+                if tag.type == Feature {
+                    debug_log(.Debug, "Found feature: {}", (cast(^Feature) tag.data).name);
+                    features << feature;
+                    break;
+                }
+            }
         }
     }
 
index 4553dc7af69679aa8995160ee8e9553ed478e26e..6a49d3504cbb264494ec22bbac090b4173759860 100644 (file)
@@ -107,4 +107,11 @@ update_ui_colors :: () {
     ui.default_slider_theme.box_border_color = config.Colors.primary;
     ui.default_slider_theme.bar_hover_color  = config.Colors.secondary;
     ui.default_slider_theme.bar_color        = config.Colors.primary_light;
+
+    for window: state.windows_sorted {
+        window.window_state.border_color     = config.Colors.primary_dark;
+        window.window_state.background_color = config.Colors.background;
+    }
+
+    invoke_feature_function("colorscheme_change");
 }
diff --git a/src/app/editor.onyx b/src/app/editor.onyx
new file mode 100644 (file)
index 0000000..5f6985d
--- /dev/null
@@ -0,0 +1,179 @@
+package editor
+
+#private_file {
+    app :: package app
+    ui :: package ui
+    config :: package config
+    use package core
+}
+
+Rename        :: struct { name: str; }
+Slider_Int    :: struct { min, max: i32; }
+Slider_Float  :: struct { min, max: f32; }
+Ignore_Option :: struct { option: str; }
+
+modify_window_draw :: (obj: any, win: ^app.Application_Window, site := #callsite) {
+    __counter = 0;
+
+    rect := win.window_state->get_internal_rectangle(); 
+
+    hash := ui.get_site_hash(site);
+    handle := ui.scrollable_region_start(rect, increment=hash, .{
+        minimum_x = 0, maximum_x = 0,
+        minimum_y = 0, maximum_y = 100000.0f,
+    });
+    defer ui.scrollable_region_stop();
+
+    win_rect := handle->get_visible_rectangle();
+
+    // rect = ui.Flow.padding(rect, left=8, right=8);
+    rect.y1 = 1000000.0f;
+
+    {
+        use type_info;
+
+        obj_info := get_type_info(obj.type);
+        if obj_info.kind == .Pointer {
+            obj.data = *cast(^rawptr) obj.data;
+            obj.type = (cast(^Type_Info_Pointer) obj_info).to;
+        }
+    }
+    
+    render_settings(^rect, win_rect, obj);
+}
+
+//
+// The nitty-gritty of how everything is rendered.
+//
+
+#private_file {
+    __counter : i32;
+
+    msg_buffer : [512] u8;
+    row_rect   : ui.Rectangle;
+    tmp_rect   : ui.Rectangle;
+
+    // Needs to be separate because this part is recursive
+    render_settings :: (rect: ^ui.Rectangle, win_rect: ui.Rectangle, obj: any) {
+        use type_info;
+
+        type := get_type_info(obj.type);
+        assert(type.kind == .Struct, "Expected a struct type");
+
+        settings_type := cast(^Type_Info_Struct) type;
+
+        heading_theme := ui.default_text_theme;
+        heading_theme.font_size = 1.25f;
+
+        background_color := config.Colors.background;
+
+        for ^member: settings_type.members {
+            __counter += 1;
+            member_type := get_type_info(member.type);
+
+            row_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=48);
+            name_rect, data_rect := ui.Flow.split_vertical(row_rect, right_width=ui.Rectangle.width(win_rect) * .6);
+
+            row_visible := ui.Rectangle.intersects(row_rect, win_rect);
+            if row_visible {
+                background_color = config.Colors.background if __counter % 2 == 0 else config.Colors.dark_background;
+                ui.draw_rect(.{ win_rect.x0, row_rect.y0, win_rect.x1, row_rect.y1 }, background_color);
+            }
+
+            {
+                // Draw the name of the variable, handling a "Rename" tag.
+                name := member.name;
+                if rename := array.first(member.tags, (x) => x.type == Rename); rename != null {
+                    name = (cast(^Rename) rename.data).name;
+                }
+
+                if row_visible do ui.draw_text(name_rect, name, theme=^heading_theme);
+            }
+
+            switch member_type.kind {
+                case .Enum {
+                    value := cast(^i32) (cast(^u8) obj.data + member.offset);
+
+                    enum_type := cast(^Type_Info_Enum) member_type;
+                    for ^enum_member: enum_type.members {
+                        for ^tag: member.tags do if tag.type == Ignore_Option {
+                            if (cast(^Ignore_Option) tag.data).option == enum_member.name {
+                                continue continue;
+                            }
+                        }
+
+                        __counter += 1;
+                        tmp_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=32);
+
+                        if ui.Rectangle.intersects(tmp_rect, win_rect) {
+                            ui.radio(ui.Flow.padding(tmp_rect, left=32),
+                                value, cast(i32) enum_member.value,
+                                enum_member.name, increment=__counter);
+                        }
+                    }
+                }
+
+                case .Struct {
+                    sub_object := cast(rawptr) (cast(^u8) obj.data + member.offset);
+
+                    y0 := row_rect.y1;
+
+                    rect.x0 += 32;
+                    render_settings(rect, win_rect, .{ sub_object, member.type });
+                    rect.x0 -= 32;
+
+                    y1 := rect.y0;
+
+                    ui.draw_rect(.{ rect.x0, y0, rect.x0 + 24, y1 }, config.Colors.primary_dark);
+                }
+
+                case .Basic do switch member.type {
+                    case i32 {
+                        value := cast(^i32) (cast(^u8) obj.data + member.offset);
+
+                        low, high : i32;
+                        low, high = 0, 10;
+
+                        if elem := array.first(member.tags, (x) => x.type == Slider_Int); elem != null {
+                            slider_int := cast(^Slider_Int) elem.data;
+                            low  = slider_int.min;
+                            high = slider_int.max;
+                        }
+
+                        if row_visible {
+                            ui.slider(data_rect, value, low, high, increment=__counter);
+                            ui.draw_text(data_rect, conv.str_format(msg_buffer, "   {}", *value));
+                        }
+                    }
+
+                    case f32 {
+                        value := cast(^f32) (cast(^u8) obj.data + member.offset);
+
+                        low, high : f32;
+                        low, high = 0, 10000;
+
+                        if elem := array.first(member.tags, (x) => x.type == Slider_Float); elem != null {
+                            slider_int := cast(^Slider_Float) elem.data;
+                            low  = slider_int.min;
+                            high = slider_int.max;
+                        }
+
+                        if row_visible {
+                            ui.slider(data_rect, value, low, high, increment=__counter);
+                            ui.draw_text(data_rect, conv.str_format(msg_buffer, "   {}", *value));
+                        }
+                    }
+
+                    case bool {
+                        value := cast(^bool) (cast(^u8) obj.data + member.offset);
+
+                        if row_visible {
+                            ui.checkbox(data_rect, value, "", increment=__counter);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
index 3e558583679b0f4cf52ce1eb89f733c76c47b537..15bdd3d4ca9e0a8a4fde2de4bec4d97ac52014d9 100644 (file)
@@ -1,6 +1,9 @@
 package app
 
 #private_file {
+    config :: package config
+    editor :: package editor
+
     json :: package json
     __initialize :: (package core.intrinsics.onyx).__initialize
     init :: (package core.intrinsics.onyx).init
@@ -11,12 +14,11 @@ package app
 }
 
 Application_Settings :: struct {
+    #tag(editor.Ignore_Option.{ "Undefined" })
+    #tag(editor.Rename.{ "Color scheme" })
     colorscheme := Colorscheme.Undefined;
 }
 
-Slider_Int   :: struct { min, max: i32; }
-Slider_Float :: struct { min, max: f32; }
-
 settings_save :: (settings: ^Application_Settings) {
     use type_info;
 
@@ -47,7 +49,9 @@ settings_load :: (settings: ^Application_Settings) {
     else                         do settings.colorscheme = .Dark;
 }
 
-
+settings_apply :: (settings: ^Application_Settings) {
+    colorscheme_switch(settings.colorscheme);
+}
 
 
 //
@@ -59,142 +63,30 @@ settings_load :: (settings: ^Application_Settings) {
     window_name :: "Settings"
 
     ui :: package ui
-
-    @Temporary
-    settings_any: any;
 }
 
-make_any :: macro (v: $T) => any.{ ^v, T };
-
 settings_window_show :: () {
-    settings_any = make_any(state.settings);
-
-    open_window(window_id, window_name, .{ 0, 0 }, settings_window_draw, draw_data=^settings_any);
+    open_window(window_id, window_name, .{ 0, 0 }, settings_window_draw);
     focus_window(window_id);
     move_window_to_top(window_id);
-
-    open_window("settings_dummy", "Settings Dummy", .{ 0, 0 }, (_, win) => {
-        buffer: [1024] u8;
-        msg := conv.str_format(buffer, "{*p}", ^state.settings);
-        ui.draw_text(.{ 0, 0, win.window_state.size.x, win.window_state.size.y }, msg);
-    });
 }
 
-settings_window_draw :: (obj: ^any, win: ^Application_Window) {
-    // Uniqueness counter
-    #persist counter: i32;
-    counter = 0;
+#private_file
+settings_window_draw :: (_, win) => {
+    editor.modify_window_draw(^state.settings, win);
 
-    rect := ui.Rectangle.{ 0, 0, win.window_state.size.x, win.window_state.size.y }; 
+    button_theme := ui.default_button_theme;
+    button_theme.border_width = 0;
 
-    #persist scrollable_region := ui.Scrollable_Region_State.{};
-    handle := ui.scrollable_region_start(rect, state=^scrollable_region, .{
-        minimum_x = 0, maximum_x = 0,
-        minimum_y = 0, maximum_y = 100000.0f,
-    });
-    defer ui.scrollable_region_stop();
+    w, h := win.window_state.size.x, win.window_state.size.y;
+    if ui.button(.{ 0, h - 40, w / 2, h }, "Apply", theme=^button_theme) {
+        settings_apply(^state.settings);
+    }
 
-    // Rectangle of the visible scroll region, transformed to have the top-left of
-    // the window at 0, 0
-    win_rect := handle->get_visible_rectangle();
+    if ui.button(.{ w / 2, h - 40, w, h }, "Save", theme=^button_theme) {
+        settings_apply(^state.settings);
+        settings_save(^state.settings);
 
-    rect = ui.Flow.padding(rect, left=8, right=8);
-    rect.y1 = 1000000.0f;
-    
-    render_settings(^rect, win_rect, obj);
-
-    // Needs to be separate because this part is recursive
-    render_settings :: (rect: ^ui.Rectangle, win_rect: ui.Rectangle, obj: ^any) {
-        use type_info;
-
-        #persist msg_buffer : [512] u8;
-        #persist tmp_rect   : ui.Rectangle;
-
-        type := get_type_info(obj.type);
-        assert(type.kind == .Struct, "Expected a struct type");
-
-        settings_type := cast(^Type_Info_Struct) type;
-
-        heading_theme := ui.default_text_theme;
-        heading_theme.font_size = 1.25f;
-
-        for ^member: settings_type.members {
-            counter += 1;
-            member_type := get_type_info(member.type);
-
-            tmp_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=48);
-            if ui.Rectangle.intersects(tmp_rect, win_rect) {
-                ui.draw_text(tmp_rect, member.name, theme=^heading_theme);
-            }
-
-            switch member_type.kind {
-                case .Enum {
-                    value := cast(^i32) (cast(^u8) obj.data + member.offset);
-
-                    enum_type := cast(^Type_Info_Enum) member_type;
-                    for ^enum_member: enum_type.members {
-                        counter += 1;
-                        tmp_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=32);
-
-                        if ui.Rectangle.intersects(tmp_rect, win_rect) {
-                            ui.radio(ui.Flow.padding(tmp_rect, left=32),
-                                value, cast(i32) enum_member.value,
-                                enum_member.name, increment=counter);
-                        }
-                    }
-                }
-
-                case .Struct {
-                    struct_type := cast(^Type_Info_Struct) member_type;
-                    sub_object := cast(rawptr) (cast(^u8) obj.data + member.offset);
-
-                    rect.x0 += 32;
-                    defer rect.x0 -= 32;
-
-                    sub_object_any := any.{ sub_object, member.type };
-                    render_settings(rect, win_rect, ^sub_object_any);
-                }
-
-                case .Basic do switch member.type {
-                    case i32 {
-                        value := cast(^i32) (cast(^u8) obj.data + member.offset);
-
-                        low, high : i32;
-                        low, high = 0, 10;
-
-                        if elem := array.first(member.tags, (x) => x.type == Slider_Int); elem != null {
-                            slider_int := cast(^Slider_Int) elem.data;
-                            low  = slider_int.min;
-                            high = slider_int.max;
-                        }
-
-                        tmp_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=32);
-                        if ui.Rectangle.intersects(tmp_rect, win_rect) {
-                            ui.slider(tmp_rect, value, low, high, increment=counter);
-                            ui.draw_text(tmp_rect, conv.str_format(msg_buffer, "   Current: {}", *value));
-                        }
-                    }
-
-                    case f32 {
-                        value := cast(^f32) (cast(^u8) obj.data + member.offset);
-
-                        low, high : f32;
-                        low, high = 0, 1;
-
-                        if elem := array.first(member.tags, (x) => x.type == Slider_Float); elem != null {
-                            slider_int := cast(^Slider_Float) elem.data;
-                            low  = slider_int.min;
-                            high = slider_int.max;
-                        }
-
-                        tmp_rect, *rect = ui.Flow.split_horizontal(*rect, top_height=32);
-                        if ui.Rectangle.intersects(tmp_rect, win_rect) {
-                            ui.slider(tmp_rect, value, low, high, increment=counter);
-                            ui.draw_text(tmp_rect, conv.str_format(msg_buffer, "   Current: {}", *value));
-                        }
-                    }
-                }
-            }
-        }
+        close_window(window_id);
     }
 }
index dbe2d2d0a6cccbdbddb5643d9885e14450aba547..357a3390154b8164ba59b12964870c34bc2ec070 100644 (file)
@@ -23,6 +23,7 @@
     #load "src/app/colors"
     #load "src/app/storage"
     #load "src/app/settings"
+    #load "src/app/editor"
 
     #load "src/features/load_features"
 
index d28262d9cf415dfc321731685c278ada71cb82be..ea974e6abf6679296059bdacfcc5026205ad05e0 100644 (file)
@@ -2,6 +2,12 @@ package feature.hex_viewer
 
 #load "./hex_viewer"
 
-Feature_Hex_Viewer :: struct {
+use package feature { Feature }
+
+_ :: struct #tag(Feature.{ "Hex Viewer" }) {
     setup := setup;
 }
+
+
+
+
index b8aacb8656e7991570ec4081addc2d94c306af8d..fe2f4c6c5a2d8617c76337eaececee9eee71d933 100644 (file)
@@ -2,6 +2,7 @@ package feature.hex_viewer
 
 #private_file {
     app :: package app
+    editor :: package editor
     ui :: package ui
     config :: package config
 }
@@ -15,14 +16,20 @@ window_name :: "Hex Viewer"
 Hex_Viewer_State :: struct {
     scrollable_region := ui.Scrollable_Region_State.{};
 
-    highlighted_line   : i32 = -1; // -1 is uninitialized
-    highlighted_column : i32 = -1;
+    #tag(editor.Slider_Int.{ -1, 40 })
+    highlighted_line   := -1;
+
+    #tag(editor.Slider_Int.{ -1, 16 })
+    highlighted_column := -1;
 }
 
 // Should this be global? Or should each viewer window get their own?
 #private global_viewer_state := Hex_Viewer_State.{};
 
+hex_window: ^app.Application_Window;
+
 window_draw :: (use viewer_state: ^Hex_Viewer_State, win: ^app.Application_Window) {
+    hex_window = win;
     orig_r: ui.Rectangle = .{ 0, 0, win.window_state.size.x, win.window_state.size.y };
     r := orig_r;
 
@@ -35,7 +42,6 @@ window_draw :: (use viewer_state: ^Hex_Viewer_State, win: ^app.Application_Windo
     line_height := em_height * 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);
@@ -144,13 +150,8 @@ open_window :: () {
     app.move_window_to_top(window_id);
     app.focus_window(window_id);
 
-    app.open_window("dummy", "DUMMY TESTING", .{ 0, 0 }, (_, win) => {
-        buffer: [1024] u8;
-        s := conv.str_format(buffer, "{p*}", ^global_viewer_state);
-        theme := ui.default_text_theme;
-        theme.font = config.Fonts.FiraCode.index;
-        ui.draw_text(.{0,0,0,0}, s, theme=^theme);
-    });
+    app.open_window("dummy", "DUMMY TESTING", .{ 0, 0 },
+        (_, win) => { (package editor).modify_window_draw(hex_window, win); });
 }
 
 setup :: () {
index 367ab17f4460ceea074dbf38bd30c0df6bbe508b..e8eb80e1d85b035d62b33ed47f65907f4a89e14b 100644 (file)
@@ -1,6 +1,11 @@
+package feature
+
+Feature :: struct {
+    name: str;
+}
 
 // Add #load statements for the features
 
 #load "./wasm/feature"
 #load "./hex_editor/feature"
-#load "./text_editor/feature"
\ No newline at end of file
+#load "./text_editor/feature"
index 8bb1fc4358e6f7cb9e0174b0c1ec4e138240af3b..a5ea2008826d6633cb0881b9c4fc77135fb50c38 100644 (file)
@@ -2,6 +2,8 @@ package feature.text_editor
 
 #load "./text_editor"
 
-Feature_Text_Editor :: struct {
+use package feature { Feature }
+
+_ :: struct #tag(Feature.{ "Text Editor" }) {
     setup := setup;
-}
\ No newline at end of file
+}
index cd52edff0894369b5260adac2d0ebe683426c606..3c3cf36893ac803eaaa75d723aa978f3f5a56f51 100644 (file)
@@ -2,7 +2,9 @@ package feature.wasm
 
 #load "./wasm"
 
-Feature_Wasm_Loader :: struct {
+use package feature { Feature }
+
+_ :: struct #tag(Feature.{ "WASM" }) {
     setup := setup;
     file_loaded := file_loaded;
 }
index 7913ca5f5a6f23b81e0db5340c3bbd018c323633..b114bafffbc21437e81c6954c5975aaad1aad49d 100644 (file)
@@ -2,7 +2,7 @@ package ui
 
 #private_file {
     map :: package core.map
-    // gfx :: package immediate_mode
+    editor :: package editor
 }
 
 // This will be stored by the end user's library.
@@ -14,10 +14,12 @@ Window_State :: struct {
 
     title := "(undefined)";
 
+    #tag(editor.Slider_Float.{ 0, 64 })
     border_width := 10.0f;
     border_color := gfx.Color4.{ 1, 0, 0 };
     background_color := gfx.Color4.{ 0.2, 0.2, 0.2 };
 
+    #tag(editor.Slider_Float.{ 0, 64 })
     bar_height := 28.0f;
 
     draggable := true;
@@ -35,6 +37,10 @@ Window_State :: struct {
                   position.x + size.x + bw, position.y + size.y + bw };
     }
 
+    get_internal_rectangle :: (use w: ^Window_State) -> Rectangle {
+        return .{ 0, 0, size.x, size.y };
+    }
+
     get_transformed_rectangle :: (use w: ^Window_State) -> Rectangle {
         bw := border_width;
         if dragging do bw *= 5;