diff --git a/examples/raygui_controls.v b/examples/raygui_controls.v new file mode 100644 index 0000000..460e690 --- /dev/null +++ b/examples/raygui_controls.v @@ -0,0 +1,203 @@ +module main + +import raylib +import raygui + +const screen_width = 1100 +const screen_height = 650 + +fn rec(x f32, y f32, width f32, height f32) raylib.Rectangle { + return raylib.Rectangle{x, y, width, height} +} + +fn writable_buffer(size int, initial string) []u8 { + mut buffer := []u8{len: size, init: 0} + bytes := initial.bytes() + count := if bytes.len < size - 1 { bytes.len } else { size - 1 } + for i in 0 .. count { + buffer[i] = bytes[i] + } + return buffer +} + +fn buffer_string(buffer []u8) string { + unsafe { + return (&u8(buffer.data)).vstring() + } +} + +fn main() { + raylib.init_window(screen_width, screen_height, 'raygui controls test suite') + defer { + raylib.close_window() + } + raylib.set_target_fps(60) + + mut dropdown_a := 0 + mut dropdown_b := 0 + mut dropdown_a_edit := false + mut dropdown_b_edit := false + mut combo_active := 0 + mut checked := false + mut spinner_value := 0 + mut spinner_edit := false + mut int_value := 0 + mut int_value_edit := false + + mut text_box := writable_buffer(64, 'Text box') + mut text_box_edit := false + mut float_text := writable_buffer(64, '12.50') + mut float_value := f32(12.5) + mut float_edit := false + mut input_text := writable_buffer(256, '') + mut secret_view_active := false + mut show_input_box := false + mut show_message_box := false + + mut multiline := writable_buffer(1024, + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nThis mutable buffer also exercises GuiTextBox with a large capacity. Edit this text to verify insertion, deletion and wrapping.') + mut multiline_edit := false + + mut list_scroll := 0 + mut list_active := -1 + mut toggle_group := 0 + mut toggle_slider := 0 + mut color := raylib.red + mut slider := f32(50) + mut slider_bar := f32(60) + mut progress := f32(0.35) + mut alpha := f32(0.5) + mut view_scroll := raylib.Vector2{0, 0} + mut view := raylib.Rectangle{} + mut mouse_cell := raylib.Vector2{} + + for !raylib.window_should_close() { + if raylib.is_key_pressed(int(raylib.KeyboardKey.key_escape)) { + show_message_box = !show_message_box + } + if raylib.is_key_pressed(int(raylib.KeyboardKey.key_left)) { + progress -= 0.1 + } + if raylib.is_key_pressed(int(raylib.KeyboardKey.key_right)) { + progress += 0.1 + } + if progress < 0 { + progress = 0 + } + if progress > 1 { + progress = 1 + } + + raylib.begin_drawing() + raylib.clear_background(raylib.raywhite) + + if dropdown_a_edit || dropdown_b_edit || show_input_box || show_message_box { + raygui.gui_lock() + } + + raygui.gui_group_box(rec(15, 15, 185, 575), 'BASIC CONTROLS') + if dropdown_a_edit || dropdown_b_edit { + raygui.gui_unlock() + } + if raygui.gui_dropdown_box(rec(30, 45, 155, 30), 'ONE;TWO;THREE', &dropdown_a, + dropdown_a_edit) != 0 { + dropdown_a_edit = !dropdown_a_edit + } + if raygui.gui_dropdown_box(rec(30, 85, 155, 30), 'ALPHA;BETA;GAMMA;DELTA', &dropdown_b, + dropdown_b_edit) != 0 { + dropdown_b_edit = !dropdown_b_edit + } + if !(dropdown_a_edit || dropdown_b_edit) && !show_input_box && !show_message_box { + raygui.gui_unlock() + } + + raygui.gui_check_box(rec(30, 130, 18, 18), 'CHECK ME', &checked) + raygui.gui_combo_box(rec(75, 125, 110, 30), 'ZERO;ONE;TWO', &combo_active) + if raygui.gui_spinner(rec(30, 165, 155, 30), '', &spinner_value, 0, 100, spinner_edit) != 0 { + spinner_edit = !spinner_edit + } + if raygui.gui_value_box(rec(30, 205, 155, 30), '', &int_value, 0, 100, int_value_edit) != 0 { + int_value_edit = !int_value_edit + } + + if raygui.gui_text_box(rec(30, 245, 155, 30), &u8(text_box.data), text_box.len - 1, + text_box_edit) != 0 { + text_box_edit = !text_box_edit + } + + if raygui.gui_value_box_float(rec(30, 285, 155, 30), 'float', &u8(float_text.data), + &float_value, float_edit) != 0 { + float_edit = !float_edit + } + + if raygui.gui_button(rec(30, 330, 155, 30), '#6#Open text input') != 0 { + show_input_box = true + } + raygui.gui_label(rec(30, 375, 155, 20), 'text: ${buffer_string(text_box)}') + raygui.gui_label(rec(30, 400, 155, 20), 'float text: ${buffer_string(float_text)}') + raygui.gui_label(rec(30, 425, 155, 20), 'float value: ${float_value:.2f}') + raygui.gui_label(rec(30, 450, 155, 20), 'input: ${buffer_string(input_text)}') + + raygui.gui_group_box(rec(215, 15, 210, 575), 'LISTS / TOGGLES') + raygui.gui_list_view(rec(230, 45, 180, 180), + 'Charmander;Bulbasaur;#18#Squirtle;Pikachu;Eevee;Pidgey', &list_scroll, &list_active) + raygui.gui_toggle_group(rec(230, 245, 180, 25), 'ONE\nTWO\nTHREE', &toggle_group) + raygui.gui_toggle_slider(rec(230, 350, 180, 30), 'ON;OFF', &toggle_slider) + raygui.gui_panel(rec(230, 405, 180, 120), 'Panel') + raygui.gui_label(rec(245, 440, 150, 20), 'list active: ${list_active}') + raygui.gui_label(rec(245, 465, 150, 20), 'toggle: ${toggle_group}') + + raygui.gui_group_box(rec(440, 15, 255, 575), 'COLOR / VALUES') + raygui.gui_color_picker(rec(460, 45, 200, 190), '', &color) + raygui.gui_slider(rec(475, 270, 185, 20), 'slider', '${slider:.2f}', &slider, -50, 100) + raygui.gui_slider_bar(rec(460, 310, 200, 20), '', '${int(slider_bar)}', &slider_bar, 0, 100) + raygui.gui_progress_bar(rec(460, 350, 200, 20), '', '${int(progress * 100)}%', &progress, + 0, 1) + raygui.gui_color_bar_alpha(rec(460, 395, 200, 30), '', &alpha) + raygui.gui_scroll_panel(rec(460, 455, 100, 100), '', rec(460, 455, 280, 700), &view_scroll, + &view) + raygui.gui_grid(rec(575, 455, 100, 100), '', 20, 3, &mouse_cell) + + raygui.gui_group_box(rec(710, 15, 375, 575), 'MULTILINE MUTABLE TEXT') + if raygui.gui_text_box(rec(725, 45, 345, 450), &u8(multiline.data), multiline.len - 1, + multiline_edit) != 0 { + multiline_edit = !multiline_edit + } + raygui.gui_status_bar(rec(725, 520, 345, 25), + 'bytes: ${buffer_string(multiline).len}/${multiline.len - 1}') + + if show_input_box { + raygui.gui_unlock() + raylib.draw_rectangle(0, 0, screen_width, screen_height, raylib.Color{ + r: 245 + g: 245 + b: 245 + a: 220 + }) + result := raygui.gui_text_input_box(rec(410, 245, 280, 160), 'Mutable input', + 'Type text and press OK', 'OK;Cancel', &u8(input_text.data), input_text.len - 1, + &secret_view_active) + if result == 1 || result == 2 || result == 0 { + show_input_box = false + } + } + if show_message_box { + raygui.gui_unlock() + raylib.draw_rectangle(0, 0, screen_width, screen_height, raylib.Color{ + r: 245 + g: 245 + b: 245 + a: 220 + }) + result := raygui.gui_message_box(rec(425, 270, 250, 120), 'Message box', + 'ESC toggles this dialog', 'Close') + if result >= 0 { + show_message_box = false + } + } + + raygui.gui_status_bar(rec(0, screen_height - 25, screen_width, 25), + 'edit text, float and modal input | ESC: message box | arrows: progress') + raylib.end_drawing() + } +} diff --git a/raygui/raygui.c.v b/raygui/raygui.c.v index 5d8d7e0..e84edac 100644 --- a/raygui/raygui.c.v +++ b/raygui/raygui.c.v @@ -755,17 +755,20 @@ pub fn gui_value_box(bounds Rectangle, text string, value &int, min_value int, m fn C.GuiValueBoxFloat(Rectangle, &char, &char, &f32, bool) int // Value box control for float values +// text_value must point to a mutable, null-terminated buffer (it is written to when edit_mode is true) @[inline] -pub fn gui_value_box_float(bounds Rectangle, text string, text_value string, value &f32, edit_mode bool) int { - return C.GuiValueBoxFloat(bounds, text.str, text_value.str, value, edit_mode) +pub fn gui_value_box_float(bounds Rectangle, text string, text_value &u8, value &f32, edit_mode bool) int { + return C.GuiValueBoxFloat(bounds, text.str, &char(text_value), value, edit_mode) } fn C.GuiTextBox(Rectangle, &char, int, bool) int // Text Box control, updates input text +// text must point to a mutable, null-terminated buffer at least text_size + 1 bytes long +// (it is written to when edit_mode is true) @[inline] -pub fn gui_text_box(bounds Rectangle, text string, text_size int, edit_mode bool) int { - return C.GuiTextBox(bounds, text.str, text_size, edit_mode) +pub fn gui_text_box(bounds Rectangle, text &u8, text_size int, edit_mode bool) int { + return C.GuiTextBox(bounds, &char(text), text_size, edit_mode) } fn C.GuiSlider(Rectangle, &char, &char, &f32, f32, f32) int @@ -843,10 +846,12 @@ pub fn gui_message_box(bounds Rectangle, title string, message string, buttons s fn C.GuiTextInputBox(Rectangle, &char, &char, &char, &char, int, &bool) int // Text Input Box control, ask for text, supports secret +// text must point to a mutable, null-terminated buffer at least `ext_max_size + 1 bytes long +// (it is written to when the user confirms input) @[inline] -pub fn gui_text_input_box(bounds Rectangle, title string, message string, buttons string, text string, text_max_size int, secret_view_active &bool) int { - return C.GuiTextInputBox(bounds, title.str, message.str, buttons.str, text.str, text_max_size, - secret_view_active) +pub fn gui_text_input_box(bounds Rectangle, title string, message string, buttons string, text &u8, text_max_size int, secret_view_active &bool) int { + return C.GuiTextInputBox(bounds, title.str, message.str, buttons.str, &char(text), + text_max_size, secret_view_active) } fn C.GuiColorPicker(Rectangle, &char, &Color) int diff --git a/raylib.c.v b/raylib.c.v index 46e05b0..2f26e1b 100644 --- a/raylib.c.v +++ b/raylib.c.v @@ -4447,8 +4447,8 @@ fn C.TextAppend(&char, &char, &int) // Append text at specific position and move cursor! @[inline] -pub fn text_append(text string, append string, position &int) { - C.TextAppend(&char(text.str), &char(append.str), position) +pub fn text_append(text &u8, append string, position &int) { + C.TextAppend(&char(text), &char(append.str), position) } fn C.TextFindIndex(&char, &char) int diff --git a/scripts/raygen.py b/scripts/raygen.py index 8124425..fb39610 100644 --- a/scripts/raygen.py +++ b/scripts/raygen.py @@ -104,6 +104,14 @@ "raylib": [ "ConfigFlags", "Gesture" ] } +# These take a plain "char *" too, but only to free it. Their counterparts +# (LoadFileText, LoadUTF8) hand back a V string wrapping that same pointer, so +# they keep taking a string. +unload_string_functions = [ + "UnloadFileText", + "UnloadUTF8", +] + # Raylib uses integer types for its enum arguments, so we will convert those to # enum types for ease-of-use in V. functions_with_enum_args = { @@ -271,6 +279,18 @@ def generate(path: str, json_path: str, lib: str): params = function.get("params", []) has_params = len(params) > 0 for param in params: + # A plain "char *" is a mutable output + # buffer that the C function writes into (e.g. + # GuiTextBox()'s text). Converting those to V + # strings, like we do for "const char *", would let + # the C code write past a V string's allocation + # so we keep track of them here and expose + # them as raw pointers instead. + param["is_mutable_char_ptr"] = ( + param["type"].strip() == "char *" and + c_name not in unload_string_functions + ) + # If this arg represents an enum flag, then we # should use that instead of an int type. We # handle them here because we need more info @@ -303,8 +323,17 @@ def generate(path: str, json_path: str, lib: str): # Convert C-string arguments to V-strings to make the DX better :D for index, arg in enumerate(params): if arg["type"] == "&char": - call_args[index] = f"{arg["name"]}.str" - decl_args[index] = decl_args[index].replace("&char", "string") + if arg["is_mutable_char_ptr"]: + # Mutable output buffers can't safely be + # represented as immutable V strings, we + # expose the raw pointer instead. Callers are + # responsible for providing a large enough & + # null-terminated writable buffer. + call_args[index] = f"&char({arg["name"]})" + decl_args[index] = decl_args[index].replace("&char", "&u8") + else: + call_args[index] = f"{arg["name"]}.str" + decl_args[index] = decl_args[index].replace("&char", "string") c_args = ", ".join(c_args) decl_args = ", ".join(decl_args)