diff --git a/Makefile b/Makefile index 5926359..ab9378d 100644 --- a/Makefile +++ b/Makefile @@ -9,5 +9,5 @@ odin_highlighter: odin build plugins/highlighter/src/ -build-mode:dll -no-entry-point -out:bin/highlighter grep: - cargo b --manifest-path=plugins/grep/Cargo.toml + nightly-cargo b --manifest-path=plugins/grep/Cargo.toml cp plugins/grep/target/debug/libgrep_plugin.dylib bin/ diff --git a/flake.lock b/flake.lock index 6b846bc..7dbe085 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { @@ -87,11 +87,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1703013332, - "narHash": "sha256-+tFNwMvlXLbJZXiMHqYq77z/RfmpfpiI3yjL6o/Zo9M=", + "lastModified": 1705856552, + "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "54aac082a4d9bb5bbc5c4e899603abfb76a3f6d6", + "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d", "type": "github" }, "original": { @@ -131,11 +131,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1704075545, - "narHash": "sha256-L3zgOuVKhPjKsVLc3yTm2YJ6+BATyZBury7wnhyc8QU=", + "lastModified": 1706235145, + "narHash": "sha256-3jh5nahTlcsX6QFcMPqxtLn9p9CgT9RSce5GLqjcpi4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a0df72e106322b67e9c6e591fe870380bd0da0d5", + "rev": "3a57c4e29cb2beb777b2e6ae7309a680585b8b2f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2dbf713..c456029 100755 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,13 @@ local-rust = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain).override { extensions = [ "rust-analysis" ]; }; + local-nightly-rust = (pkgs.rust-bin.fromRustupToolchainFile ./plugins/grep/rust-toolchain.toml).override { + extensions = [ "rust-analysis" ]; + }; + nightly-cargo = pkgs.writeShellScriptBin "nightly-cargo" '' + export RUSTC="${local-nightly-rust}/bin/rustc"; + exec "${local-nightly-rust}/bin/cargo" "$@" + ''; fixed-odin = pkgs.odin.overrideAttrs (finalAttrs: prevAttr: rec { src = pkgs.fetchFromGitHub { owner = "pcleavelin"; @@ -58,6 +65,7 @@ buildInputs = with pkgs; (if pkgs.system == "aarch64-darwin" || pkgs.system == "x86_64-darwin" then [ fixed-odin local-rust + nightly-cargo rust-analyzer SDL2 SDL2_ttf diff --git a/plugin-rs-bindings/src/lib.rs b/plugin-rs-bindings/src/lib.rs index 7e4a78a..8b919af 100644 --- a/plugin-rs-bindings/src/lib.rs +++ b/plugin-rs-bindings/src/lib.rs @@ -166,6 +166,137 @@ pub struct IteratorVTable { pub until_end_of_word: *const c_void, } +#[repr(C)] +pub struct UiInteraction { + pub hovering: bool, + pub clicked: bool, +} + +#[repr(C)] +struct InternalUiSemanticSize { + kind: isize, + value: isize, +} + +#[repr(isize)] +pub enum UiAxis { + Horizontal = 0, + Vertical, +} + +pub enum UiSemanticSize { + FitText, + Exact(isize), + ChildrenSum, + Fill, + PercentOfParent(isize), +} + +impl From for InternalUiSemanticSize { + fn from(value: UiSemanticSize) -> Self { + let (kind, value) = match value { + UiSemanticSize::FitText => (0, 0), + UiSemanticSize::Exact(value) => (1, value), + UiSemanticSize::ChildrenSum => (2, 0), + UiSemanticSize::Fill => (3, 0), + UiSemanticSize::PercentOfParent(value) => (4, value), + }; + + Self { kind, value } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UiContext(*const c_void); + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UiBox(*const c_void); + +type UiPushParentProc = extern "C" fn(ui_context: UiContext, ui_box: UiBox); +type UiPopParentProc = extern "C" fn(ui_context: UiContext); +type UiFloatingProc = + extern "C" fn(ui_context: UiContext, label: *const i8, pos: [isize; 2]) -> UiBox; +type UiRectProc = extern "C" fn( + ui_context: UiContext, + label: *const i8, + border: bool, + axis: UiAxis, + size: [InternalUiSemanticSize; 2], +) -> UiBox; +type UiSimpleProc = extern "C" fn(ui_context: UiContext, label: *const i8) -> UiInteraction; +type UiBufferProc = extern "C" fn(ui_context: UiContext, buffer: Buffer, show_line_numbers: bool); + +#[repr(C)] +pub struct UiVTable { + ui_context: UiContext, + + push_parent: UiPushParentProc, + pop_parent: UiPopParentProc, + + floating: UiFloatingProc, + rect: UiRectProc, + + button: UiSimpleProc, + label: UiSimpleProc, + + buffer: UiBufferProc, + buffer_from_index: UiBufferProc, +} + +impl UiVTable { + pub fn push_parent(&self, ui_box: UiBox) { + (self.push_parent)(self.ui_context, ui_box); + } + pub fn pop_parent(&self) { + (self.pop_parent)(self.ui_context); + } + + pub fn push_rect( + &self, + label: &CStr, + show_border: bool, + axis: UiAxis, + horizontal_size: UiSemanticSize, + vertical_size: UiSemanticSize, + inner: impl FnOnce(&UiVTable), + ) { + let rect = (self.rect)( + self.ui_context, + label.as_ptr(), + show_border, + axis, + [horizontal_size.into(), vertical_size.into()], + ); + self.push_parent(rect); + + inner(self); + + self.pop_parent(); + } + + pub fn push_floating(&self, label: &CStr, x: isize, y: isize, inner: impl FnOnce(&UiVTable)) { + let floating = (self.floating)(self.ui_context, label.as_ptr(), [x, y]); + self.push_parent(floating); + + inner(self); + + self.pop_parent(); + } + + pub fn label(&self, label: &CStr) -> UiInteraction { + (self.label)(self.ui_context, label.as_ptr()) + } + pub fn button(&self, label: &CStr) -> UiInteraction { + (self.button)(self.ui_context, label.as_ptr()) + } + + pub fn buffer(&self, buffer: Buffer, show_line_numbers: bool) { + (self.buffer)(self.ui_context, buffer, show_line_numbers) + } +} + type OnColorBufferProc = extern "C" fn(plugin: Plugin, buffer: *const c_void); type OnHookProc = extern "C" fn(plugin: Plugin, buffer: Buffer); type InputGroupProc = extern "C" fn(plugin: Plugin, input_map: InputMap); @@ -176,8 +307,10 @@ type WindowGetBufferProc = extern "C" fn(plugin: Plugin, window: *const c_void) #[repr(C)] pub struct Plugin { state: *const c_void, + pub iter_table: IteratorVTable, pub buffer_table: BufferVTable, + pub ui_table: UiVTable, pub register_hook: extern "C" fn(hook: Hook, on_hook: OnHookProc), pub register_highlighter: diff --git a/plugins/buffer_search/plugin.odin b/plugins/buffer_search/plugin.odin index 3bc992c..b0f7f9a 100644 --- a/plugins/buffer_search/plugin.odin +++ b/plugins/buffer_search/plugin.odin @@ -98,11 +98,58 @@ buffer_list_iter :: proc(plugin: Plugin, buffer_index: ^int) -> (int, int, bool) draw_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) { context = runtime.default_context(); + runtime.free_all(context.temp_allocator); + win := cast(^BufferListWindow)win; if win == nil { return; } + screen_width := plugin.get_screen_width(); + screen_height := plugin.get_screen_height(); + directory := string(plugin.get_current_directory()); + + canvas := plugin.ui.floating(plugin.ui.ui_context, "buffer search canvas", {screen_width/8, screen_height/8}); + + plugin.ui.push_parent(plugin.ui.ui_context, canvas); + { + defer plugin.ui.pop_parent(plugin.ui.ui_context); + + ui_window := plugin.ui.rect(plugin.ui.ui_context, "buffer search window", true, .Horizontal, {{4, 75}, {4, 75}}); + plugin.ui.push_parent(plugin.ui.ui_context, ui_window); + { + defer plugin.ui.pop_parent(plugin.ui.ui_context); + + buffer_list_view := plugin.ui.rect(plugin.ui.ui_context, "buffer list view", false, .Vertical, {{4, 60}, {3, 0}}); + plugin.ui.push_parent(plugin.ui.ui_context, buffer_list_view); + { + defer plugin.ui.pop_parent(plugin.ui.ui_context); + + _buffer_index := 0; + for index in buffer_list_iter(plugin, &_buffer_index) { + buffer := plugin.buffer.get_buffer_info_from_index(index); + relative_file_path, _ := filepath.rel(directory, string(buffer.file_path), context.temp_allocator) + text := fmt.ctprintf("%s:%d", relative_file_path, buffer.cursor.line+1); + + if index == win.selected_index { + plugin.ui.button(plugin.ui.ui_context, text); + } else { + plugin.ui.label(plugin.ui.ui_context, text); + } + } + } + + buffer_preview := plugin.ui.rect(plugin.ui.ui_context, "buffer preview", false, .Horizontal, {{3, 0}, {3, 0}}); + plugin.ui.push_parent(plugin.ui.ui_context, buffer_preview); + { + defer plugin.ui.pop_parent(plugin.ui.ui_context); + + plugin.ui.buffer_from_index(plugin.ui.ui_context, win.selected_index, false); + } + } + } + + /* screen_width := plugin.get_screen_width(); screen_height := plugin.get_screen_height(); source_font_width := plugin.get_font_width(); @@ -173,4 +220,5 @@ draw_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) { runtime.free_all(context.temp_allocator); } + */ } diff --git a/plugins/grep/rust-toolchain.toml b/plugins/grep/rust-toolchain.toml new file mode 100644 index 0000000..98d1e3f --- /dev/null +++ b/plugins/grep/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2024-01-24" + diff --git a/plugins/grep/src/lib.rs b/plugins/grep/src/lib.rs index 74e5c3b..d51859d 100644 --- a/plugins/grep/src/lib.rs +++ b/plugins/grep/src/lib.rs @@ -1,6 +1,6 @@ use std::{ error::Error, - ffi::OsString, + ffi::{CString, OsString}, path::Path, str::FromStr, sync::mpsc::{Receiver, Sender}, @@ -11,7 +11,7 @@ use grep::{ regex::RegexMatcherBuilder, searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError}, }; -use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, PaletteColor, Plugin}; +use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, Plugin, UiAxis, UiSemanticSize}; use std::sync::mpsc::channel; use walkdir::WalkDir; @@ -273,109 +273,142 @@ extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) { let screen_width = (plugin.get_screen_width)() as i32; let screen_height = (plugin.get_screen_height)() as i32; - let font_width = (plugin.get_font_width)() as i32; + let font_height = (plugin.get_font_height)() as i32; - let x = screen_width / 8; - let y = screen_height / 8; - let width = screen_width - screen_width / 4; let height = screen_height - screen_height / 4; - let buffer_prev_width = (width - font_width * 2) / 2; - - let glyph_buffer_width = buffer_prev_width / font_width - 1; - let glyph_buffer_height = 1; - let dir = plugin.get_current_directory(); let directory = Path::new(dir.as_ref()); - (plugin.draw_rect)(x, y, width, height, PaletteColor::Background4); - (plugin.draw_rect)( - x + font_width, - y + font_height, - width - font_width * 2, - height - font_height * 3, - PaletteColor::Background3, - ); + plugin.ui_table.push_floating( + c"grep canvas", + (screen_width as isize) / 8, + (screen_height as isize) / 8, + |ui_table| { + ui_table.push_rect( + c"grep window", + true, + UiAxis::Vertical, + UiSemanticSize::PercentOfParent(75), + UiSemanticSize::PercentOfParent(75), + |ui_table| { + if let Ok(sink) = window.rx.try_recv() { + window.sink = Some(sink); + } - if let Some(buffer) = window.input_buffer { - (plugin.draw_rect)( - x + font_width, - y + height - font_height * 2, - buffer_prev_width, - font_height, - PaletteColor::Background2, - ); - (plugin.draw_buffer)( - buffer, - (x + font_width) as isize, - (y + height - font_height * 2) as isize, - (glyph_buffer_width) as isize, - (glyph_buffer_height) as isize, - false, - ); - } + ui_table.push_rect( + c"results list", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| match &window.sink { + Some(sink) if !sink.matches.is_empty() => { + let num_mats_to_draw = std::cmp::min( + (sink.matches.len() - window.top_index) as i32, + (height - font_height) / (font_height), + ); - if let Ok(sink) = window.rx.try_recv() { - window.sink = Some(sink); - } + for (i, mat) in sink.matches[window.top_index..].iter().enumerate() + { + let index = i + window.top_index; + if i as i32 >= num_mats_to_draw { + break; + } - if let Some(sink) = &window.sink { - if !sink.matches.is_empty() { - let num_mats_to_draw = std::cmp::min( - (sink.matches.len() - window.top_index) as i32, - (height - font_height * 2) / (font_height) - 1, - ); - let max_mat_length = (width - font_width * 2) / font_width; + let path = Path::new(&mat.path); + let relative_file_path = path + .strip_prefix(directory) + .unwrap_or(path) + .to_str() + .unwrap_or(""); - for (i, mat) in sink.matches[window.top_index..].iter().enumerate() { - let index = i + window.top_index; - if i as i32 >= num_mats_to_draw { - break; - } + let matched_text = String::from_utf8_lossy(&mat.text); + let text = match mat.line_number { + Some(line_number) => format!( + "{}:{}:{}: {}", + relative_file_path, + line_number, + mat.column, + matched_text + ), + None => format!( + "{}:{}: {}", + relative_file_path, mat.column, matched_text + ), + }; - let path = Path::new(&mat.path); - let relative_file_path = path - .strip_prefix(directory) - .unwrap_or(path) - .to_str() - .unwrap_or(""); - - let matched_text = String::from_utf8_lossy(&mat.text); - let text = match mat.line_number { - Some(line_number) => format!( - "{}:{}:{}: {}", - relative_file_path, line_number, mat.column, matched_text - ), - None => format!("{}:{}: {}", relative_file_path, mat.column, matched_text), - }; - let text = if text.len() > max_mat_length as usize { - text.as_str().split_at(max_mat_length as usize).0 - } else { - &text - }; - - let text = format!("{text}\0"); - - if index == window.selected_match { - (plugin.draw_rect)( - x + font_width, - y + font_height + ((index - window.top_index) as i32) * font_height, - (text.chars().count() as i32) * font_width, - font_height, - PaletteColor::Background2, + if index == window.selected_match { + ui_table.button(&CString::new(text).expect("valid text")); + } else { + ui_table.label(&CString::new(text).expect("valid text")); + } + } + } + Some(_) | None => { + ui_table.push_rect( + c"top spacer", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| {}, + ); + ui_table.push_rect( + c"centered text container", + false, + UiAxis::Horizontal, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| { + ui_table.push_rect( + c"left spacer", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| {}, + ); + ui_table.label(c"no results"); + ui_table.push_rect( + c"right spacer", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| {}, + ); + }, + ); + ui_table.push_rect( + c"bottom spacer", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Fill, + |ui_table| {}, + ); + } + }, ); - } - (plugin.draw_text)( - text.as_ptr() as *const i8, - (x + font_width) as f32, - (y + font_height + ((index - window.top_index) as i32) * font_height) as f32, - PaletteColor::Foreground2, - ); - } - } - } + ui_table.push_rect( + c"grep window", + false, + UiAxis::Vertical, + UiSemanticSize::Fill, + UiSemanticSize::Exact(font_height as isize), + |ui_table| { + if let Some(buffer) = window.input_buffer { + ui_table.buffer(buffer, false); + } + }, + ); + }, + ); + }, + ); } extern "C" fn on_buffer_input(plugin: Plugin, buffer: Buffer) { diff --git a/plugins/highlighter/src/plugin.odin b/plugins/highlighter/src/plugin.odin index 1b50204..fa98094 100644 --- a/plugins/highlighter/src/plugin.odin +++ b/plugins/highlighter/src/plugin.odin @@ -277,6 +277,9 @@ is_rust_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> ( return; } +// TODO: split logic into single line coloring, and multi-line coloring. +// single line coloring can be done directly on the glyph buffer +// (with some edge cases, literally, the edge of the screen) color_buffer_odin :: proc "c" (plugin: Plugin, buffer: rawptr) { context = runtime.default_context(); diff --git a/src/core/gfx.odin b/src/core/gfx.odin index 1cb2a67..e176698 100644 --- a/src/core/gfx.odin +++ b/src/core/gfx.odin @@ -6,7 +6,7 @@ import "vendor:sdl2/ttf" import "../theme" -scale :: 2; +scale :: 1; start_char :: ' '; end_char :: '~'; diff --git a/src/main.odin b/src/main.odin index 266dbfd..650ae39 100644 --- a/src/main.odin +++ b/src/main.odin @@ -162,6 +162,7 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) { // Scale font size { core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { + fmt.print("You pressed -MINUS", state.source_font_height, " "); if state.source_font_height > 16 { state.source_font_height -= 2; state.source_font_width = state.source_font_height / 2; @@ -170,8 +171,11 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) { //state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0); //raylib.SetTextureFilter(state.font.texture, .BILINEAR); } + fmt.println(state.source_font_height); }, "increase font size"); core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State) { + fmt.println("You pressed -EQUAL"); + state.source_font_height += 2; state.source_font_width = state.source_font_height / 2; @@ -185,6 +189,7 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) { { core.register_key_action(input_map, .I, proc(state: ^State) { state.mode = .Insert; + sdl2.StartTextInput(); }, "enter insert mode"); core.register_key_action(input_map, .A, proc(state: ^State) { core.move_cursor_right(&state.buffers[state.current_buffer], false); @@ -240,19 +245,12 @@ draw :: proc(state_with_ui: ^StateWithUi) { sdl2.SetRenderDrawColor(state_with_ui.state.sdl_renderer, render_color.r, render_color.g, render_color.b, render_color.a); sdl2.RenderClear(state_with_ui.state.sdl_renderer); - // raylib.ClearBackground(theme.get_palette_raylib_color(.Background)); - - // core.draw_file_buffer(state_with_ui.state, buffer, 32, state_with_ui.state.source_font_height); + // if state_with_ui.state.window != nil && state_with_ui.state.window.draw != nil { + // state_with_ui.state.window.draw(state_with_ui.state.plugin_vtable, state_with_ui.state.window.user_data); + // } ui.compute_layout(state_with_ui.ui_context, { state_with_ui.state.screen_width, state_with_ui.state.screen_height }, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); ui.draw(state_with_ui.ui_context, state_with_ui.state, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); - //ui.draw_menu_bar(&state_with_ui.state, &menu_bar_state_with_ui.state, 0, 0, i32(state_with_ui.state.screen_width), i32(state_with_ui.state.screen_height), state_with_ui.state.source_font_height); - - //raylib.DrawRectangle(0, i32(state_with_ui.state.screen_height - state_with_ui.state.source_font_height), i32(state_with_ui.state.screen_width), i32(state_with_ui.state.source_font_height), theme.get_palette_raylib_color(.Background2)); - - if state_with_ui.state.window != nil && state_with_ui.state.window.draw != nil { - state_with_ui.state.window.draw(state_with_ui.state.plugin_vtable, state_with_ui.state.window.user_data); - } if state_with_ui.state.current_input_map != &state_with_ui.state.input_map { longest_description := 0; @@ -375,20 +373,8 @@ ui_file_buffer :: proc(ctx: ^ui.Context, buffer: ^FileBuffer) -> ui.Interaction return interaction; } -main :: proc() { - state = State { - ctx = context, - source_font_width = 8 + 2 * 3, - source_font_height = 16 + 2 * 3, - input_map = core.new_input_map(), - window = nil, - directory = os.get_current_directory(), - plugins = make([dynamic]plugin.Interface), - highlighters = make(map[string]plugin.OnColorBufferProc), - hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc), - }; - - state.plugin_vtable = plugin.Plugin { +init_plugin_vtable :: proc(ui_context: ^ui.Context) -> plugin.Plugin { + return plugin.Plugin { state = cast(rawptr)&state, register_hook = proc "c" (hook: plugin.Hook, on_hook: plugin.OnHookProc) { context = state.ctx; @@ -507,6 +493,7 @@ main :: proc() { }, enter_insert_mode = proc "c" () { state.mode = .Insert; + sdl2.StartTextInput(); }, draw_rect = proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor) { context = state.ctx; @@ -864,8 +851,109 @@ main :: proc() { free(buffer); } }, - } + }, + ui = plugin.Ui { + ui_context = ui_context, + + push_parent = proc "c" (ui_context: rawptr, box: plugin.UiBox) { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + box := transmute(^ui.Box)box; + + ui.push_parent(ui_context, box); + }, + + pop_parent = proc "c" (ui_context: rawptr) { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + + ui.pop_parent(ui_context); + }, + + // TODO: allow this to have more flags sent to it + floating = proc "c" (ui_context: rawptr, label: cstring, pos: [2]int) -> plugin.UiBox { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + label := strings.clone(string(label), context.temp_allocator); + + return ui.push_floating(ui_context, label, pos); + }, + rect = proc "c" (ui_context: rawptr, label: cstring, border: bool, axis: plugin.UiAxis, size: [2]plugin.UiSemanticSize) -> plugin.UiBox { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + label := strings.clone(string(label), context.temp_allocator); + + size := [2]ui.SemanticSize { + ui.SemanticSize { + kind = ui.SemanticSizeKind(size.x.kind), + value = size.x.value, + }, + ui.SemanticSize { + kind = ui.SemanticSizeKind(size.y.kind), + value = size.y.value, + }, + }; + + return ui.push_rect(ui_context, label, border, ui.Axis(axis), size); + }, + + label = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + label := strings.clone(string(label), context.temp_allocator); + + interaction := ui.label(ui_context, label); + + return plugin.UiInteraction { + hovering = interaction.hovering, + clicked = interaction.clicked, + }; + }, + button = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + label := strings.clone(string(label), context.temp_allocator); + + interaction := ui.button(ui_context, label); + + return plugin.UiInteraction { + hovering = interaction.hovering, + clicked = interaction.clicked, + }; + }, + buffer = proc "c" (ui_context: rawptr, buffer: rawptr, show_line_numbers: bool) { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + buffer := transmute(^FileBuffer)buffer; + + ui_file_buffer(ui_context, buffer); + }, + + buffer_from_index = proc "c" (ui_context: rawptr, buffer: int, show_line_numbers: bool) { + context = state.ctx; + ui_context := transmute(^ui.Context)ui_context; + + buffer := &state.buffers[buffer]; + + ui_file_buffer(ui_context, buffer); + }, + }, }; +} + +main :: proc() { + state = State { + ctx = context, + source_font_width = 8 + 2 * 3, + source_font_height = 16 + 2 * 3, + input_map = core.new_input_map(), + window = nil, + directory = os.get_current_directory(), + plugins = make([dynamic]plugin.Interface), + highlighters = make(map[string]plugin.OnColorBufferProc), + hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc), + }; + state.current_input_map = &state.input_map; register_default_input_actions(&state.input_map); @@ -879,16 +967,6 @@ main :: proc() { runtime.append(&state.buffers, buffer); } - // Load plugins - // TODO(pcleavelin): Get directory of binary instead of shells current working directory - filepath.walk(filepath.join({ os.get_current_directory(), "bin" }), load_plugin, transmute(rawptr)&state); - - for plugin in state.plugins { - if plugin.on_initialize != nil { - plugin.on_initialize(state.plugin_vtable); - } - } - if sdl2.Init({.VIDEO}) < 0 { fmt.eprintln("SDL failed to initialize:", sdl2.GetError()); return; @@ -936,18 +1014,22 @@ main :: proc() { } } + sdl2.StartTextInput(); + sdl2.StopTextInput(); + ui_context := ui.init(state.sdl_renderer); - sdl2.AddEventWatch(expose_event_watcher, &StateWithUi { &state, &ui_context }); + state.plugin_vtable = init_plugin_vtable(&ui_context); - // raylib.InitWindow(640, 480, "odin_editor - [now with more ui]"); - // raylib.SetWindowState({ .WINDOW_RESIZABLE, .VSYNC_HINT }); - // raylib.SetTargetFPS(144); - // raylib.SetExitKey(.KEY_NULL); + // Load plugins + // TODO(pcleavelin): Get directory of binary instead of shells current working directory + filepath.walk(filepath.join({ os.get_current_directory(), "bin" }), load_plugin, transmute(rawptr)&state); - // TODO: don't just hard code a MacOS font path - // state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height), nil, 0); - // raylib.SetTextureFilter(state.font.texture, .BILINEAR); + for plugin in state.plugins { + if plugin.on_initialize != nil { + plugin.on_initialize(state.plugin_vtable); + } + } control_key_pressed: bool; @@ -958,7 +1040,7 @@ main :: proc() { { buffer := &state.buffers[state.current_buffer]; - ui.push_parent(&ui_context, ui.push_box(&ui_context, "main", {}, .Vertical, semantic_size = {ui.make_semantic_size(.PercentOfParent, 100), ui.make_semantic_size(.PercentOfParent, 100)})); + ui.push_parent(&ui_context, ui.push_box(&ui_context, "main", {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill, 100), ui.make_semantic_size(.Fill, 100)})); defer ui.pop_parent(&ui_context); { @@ -1012,7 +1094,7 @@ main :: proc() { defer ui.pop_parent(&ui_context); { - if ui_file_buffer(&ui_context, &state.buffers[0+3]).clicked { + if ui_file_buffer(&ui_context, &state.buffers[state.current_buffer]).clicked { state.current_buffer = 3; } } @@ -1065,6 +1147,10 @@ main :: proc() { } } + if state.window != nil && state.window.draw != nil { + state.window.draw(state.plugin_vtable, state.window.user_data); + } + { ui_context.last_mouse_left_down = ui_context.mouse_left_down; ui_context.last_mouse_right_down = ui_context.mouse_right_down; @@ -1091,40 +1177,98 @@ main :: proc() { } } - if sdl_event.type == .KEYDOWN { - key := plugin.Key(sdl_event.key.keysym.sym); - if key == .LCTRL { - control_key_pressed = true; - } else if state.current_input_map != nil { - if control_key_pressed { - if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { - switch value in action.action { - case core.PluginEditorAction: - value(state.plugin_vtable); - case core.EditorAction: - value(&state); - case core.InputMap: - state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputMap) - } + switch state.mode { + case .Normal: { + if sdl_event.type == .KEYDOWN { + key := plugin.Key(sdl_event.key.keysym.sym); + if key == .ESCAPE { + core.request_window_close(&state); } - } else { - if action, exists := state.current_input_map.key_actions[key]; exists { - switch value in action.action { - case core.PluginEditorAction: - value(state.plugin_vtable); - case core.EditorAction: - value(&state); - case core.InputMap: - state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputMap) + + if key == .LCTRL { + control_key_pressed = true; + } else if state.current_input_map != nil { + if control_key_pressed { + if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { + switch value in action.action { + case core.PluginEditorAction: + value(state.plugin_vtable); + case core.EditorAction: + value(&state); + case core.InputMap: + state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputMap) + } + } + } else { + if action, exists := state.current_input_map.key_actions[key]; exists { + switch value in action.action { + case core.PluginEditorAction: + value(state.plugin_vtable); + case core.EditorAction: + value(&state); + case core.InputMap: + state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputMap) + } + } } } } + if sdl_event.type == .KEYUP { + key := plugin.Key(sdl_event.key.keysym.sym); + if key == .LCTRL { + control_key_pressed = false; + } + } } - } - if sdl_event.type == .KEYUP { - key := plugin.Key(sdl_event.key.keysym.sym); - if key == .LCTRL { - control_key_pressed = false; + case .Insert: { + buffer: ^FileBuffer; + + if state.window != nil && state.window.get_buffer != nil { + buffer = transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data)); + } else { + buffer = &state.buffers[state.current_buffer]; + } + + if sdl_event.type == .KEYDOWN { + key := plugin.Key(sdl_event.key.keysym.sym); + + #partial switch key { + case .ESCAPE: { + state.mode = .Normal; + + core.insert_content(buffer, buffer.input_buffer[:]); + runtime.clear(&buffer.input_buffer); + + sdl2.StopTextInput(); + } + case .BACKSPACE: { + core.delete_content(buffer, 1); + + for hook_proc in state.hooks[plugin.Hook.BufferInput] { + hook_proc(state.plugin_vtable, buffer); + } + } + case .ENTER: { + append(&buffer.input_buffer, '\n'); + } + } + } + + if sdl_event.type == .TEXTINPUT { + for char in sdl_event.text.text { + if char < 1 { + break; + } + + if char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1 { + append(&buffer.input_buffer, u8(char)); + + for hook_proc in state.hooks[plugin.Hook.BufferInput] { + hook_proc(state.plugin_vtable, buffer); + } + } + } + } } } } diff --git a/src/plugin/plugin.odin b/src/plugin/plugin.odin index d9684b8..1053fcc 100644 --- a/src/plugin/plugin.odin +++ b/src/plugin/plugin.odin @@ -84,6 +84,47 @@ Iterator :: struct { until_end_of_word: rawptr, } +UiInteraction :: struct { + hovering: bool, + clicked: bool +} + +UiAxis :: enum { + Horizontal = 0, + Vertical, +} + +UiSemanticSize :: struct { + kind: int, + value: int, +} + +UiBox :: rawptr; + +UiPushParentProc :: proc "c" (ui_context: rawptr, box: UiBox); +UiPopParentProc :: proc "c" (ui_context: rawptr); +UiFloatingProc :: proc "c" (ui_context: rawptr, label: cstring, pos: [2]int) -> UiBox; +UiCreateBoxProc :: proc "c" (ui_context: rawptr, label: cstring) -> UiBox; +UiRectProc :: proc "c" (ui_context: rawptr, label: cstring, border: bool, axis: UiAxis, size: [2]UiSemanticSize) -> UiBox; +UiSimpleProc :: proc "c" (ui_context: rawptr, label: cstring) -> UiInteraction; +UiBufferProc :: proc "c" (ui_context: rawptr, buffer: rawptr, show_line_numbers: bool); +UiBufferIndexProc :: proc "c" (ui_context: rawptr, buffer: int, show_line_numbers: bool); +Ui :: struct { + ui_context: rawptr, + + push_parent: UiPushParentProc, + pop_parent: UiPopParentProc, + + floating: UiFloatingProc, + rect: UiRectProc, + + button: UiSimpleProc, + label: UiSimpleProc, + + buffer: UiBufferProc, + buffer_from_index: UiBufferIndexProc, +} + OnColorBufferProc :: proc "c" (plugin: Plugin, buffer: rawptr); InputGroupProc :: proc "c" (plugin: Plugin, input_map: rawptr); InputActionProc :: proc "c" (plugin: Plugin); @@ -97,6 +138,7 @@ Plugin :: struct { state: rawptr, iter: Iterator, buffer: Buffer, + ui: Ui, register_hook: proc "c" (hook: Hook, on_hook: OnHookProc), register_highlighter: proc "c" (extension: cstring, on_color_buffer: OnColorBufferProc), diff --git a/src/ui/imm.odin b/src/ui/imm.odin index e1148d7..ffe06b1 100644 --- a/src/ui/imm.odin +++ b/src/ui/imm.odin @@ -49,11 +49,12 @@ Flag :: enum { DrawText, DrawBorder, DrawBackground, + Floating, CustomDrawFunc, } SemanticSizeKind :: enum { - FitText, + FitText = 0, Exact, ChildrenSum, Fill, @@ -126,6 +127,7 @@ gen_key :: proc(ctx: ^Context, label: string, value: int) -> Key { }; } +@(private) make_box :: proc(ctx: ^Context, key: Key, label: string, flags: bit_set[Flag], axis: Axis, semantic_size: [2]SemanticSize) -> ^Box { box: ^Box = nil; @@ -193,6 +195,15 @@ ChildrenSum :[2]SemanticSize: { } }; +Fill :[2]SemanticSize: { + SemanticSize { + kind = .Fill, + }, + SemanticSize { + kind = .Fill, + } +}; + push_box :: proc(ctx: ^Context, label: string, flags: bit_set[Flag], axis: Axis = .Horizontal, semantic_size: [2]SemanticSize = FitText, value: int = 0) -> ^Box { key := gen_key(ctx, label, value); box := make_box(ctx, key, label, flags, axis, semantic_size); @@ -227,10 +238,6 @@ test_box :: proc(ctx: ^Context, box: ^Box) -> Interaction { box.hot = 0; } - if hovering && mouse_is_clicked { - fmt.println("hot", box.hot); - } - return Interaction { hovering = hovering, clicked = hovering && mouse_is_clicked, @@ -265,16 +272,21 @@ prune :: proc(ctx: ^Context) { } } + computed_pos := ctx.root.computed_pos; + computed_size := ctx.root.computed_size; root_key := ctx.root.key; - ctx.root^ = { - key = root_key, - }; + + ctx.root.first = nil; + ctx.root.last = nil; + ctx.root.next = nil; + ctx.root.prev = nil; + ctx.root.parent = nil; ctx.current_parent = ctx.root; } // TODO: consider not using `ctx` here ancestor_size :: proc(ctx: ^Context, box: ^Box, axis: Axis) -> int { - if box == nil || box.parent == nil { + if box == nil || box.parent == nil || .Floating in box.flags { return ctx.root.computed_size[axis]; } @@ -292,17 +304,35 @@ ancestor_size :: proc(ctx: ^Context, box: ^Box, axis: Axis) -> int { return 1337; } +prev_non_floating_sibling :: proc(ctx: ^Context, box: ^Box) -> ^Box { + if box == nil { + return nil; + } else if box.prev == nil { + return nil; + } else if !(.Floating in box.prev.flags) { + return box.prev; + } else { + return prev_non_floating_sibling(ctx, box.prev); + } +} + compute_layout :: proc(ctx: ^Context, canvas_size: [2]int, font_width: int, font_height: int, box: ^Box) { if box == nil { return; } axis := Axis.Horizontal; - if box.parent != nil { + if box.parent != nil && !(.Floating in box.flags) { axis = box.parent.axis; box.computed_pos = box.parent.computed_pos; } - if box.prev != nil { - box.computed_pos[axis] = box.prev.computed_pos[axis] + box.prev.computed_size[axis]; + if .Floating in box.flags { + // box.computed_pos = {0,0}; + } else if box.prev != nil { + prev := prev_non_floating_sibling(ctx, box); + + if prev != nil { + box.computed_pos[axis] = prev.computed_pos[axis] + prev.computed_size[axis]; + } } post_compute_size := [2]bool { false, false }; @@ -415,6 +445,8 @@ compute_layout :: proc(ctx: ^Context, canvas_size: [2]int, font_width: int, font our_size := box.computed_size; for child in iterate_box(&iter) { + if .Floating in child.flags { continue; } + compute_layout(ctx, canvas_size, font_width, font_height, child); if child.semantic_size[box.axis].kind == .Fill { number_of_fills[box.axis] += 1; @@ -644,8 +676,19 @@ spacer :: proc(ctx: ^Context, label: string, flags: bit_set[Flag] = {}, semantic return push_box(ctx, label, flags, semantic_size = semantic_size); } +push_floating :: proc(ctx: ^Context, label: string, pos: [2]int, flags: bit_set[Flag] = {.Floating}, axis: Axis = .Vertical, semantic_size: [2]SemanticSize = Fill) -> ^Box { + box := push_box(ctx, label, flags, semantic_size = semantic_size); + box.computed_pos = pos; + + return box; +} + +push_rect :: proc(ctx: ^Context, label: string, border: bool = true, axis: Axis = .Vertical, semantic_size: [2]SemanticSize = Fill) -> ^Box { + return push_box(ctx, label, {.DrawBackground, .DrawBorder if border else nil}, axis, semantic_size = semantic_size); +} + label :: proc(ctx: ^Context, label: string) -> Interaction { - box := push_box(ctx, label, {.DrawText, .Hoverable}); + box := push_box(ctx, label, {.DrawText}); return test_box(ctx, box); }