get plugin interface sorta working with the ui lib

rust-rewrite
Patrick Cleavelin 2024-01-26 01:32:16 -06:00
parent 96338e2924
commit de908cfe06
12 changed files with 646 additions and 189 deletions

View File

@ -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/

View File

@ -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": {

View File

@ -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

View File

@ -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<UiSemanticSize> 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:

View File

@ -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);
}
*/
}

View File

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2024-01-24"

View File

@ -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) {

View File

@ -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();

View File

@ -6,7 +6,7 @@ import "vendor:sdl2/ttf"
import "../theme"
scale :: 2;
scale :: 1;
start_char :: ' ';
end_char :: '~';

View File

@ -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 <C>-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 <C>-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);
}
}
}
}
}
}
}

View File

@ -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),

View File

@ -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);
}