From 4359bdaa3c78a04c95e72d52d010f63f89d85757 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Sun, 5 Mar 2023 15:59:57 +0100 Subject: [PATCH] [ui, textbox] position cursor at mouse position / extend selection on shift + mouse press or drag --- examples/ui/build.bat | 4 + examples/ui/build.sh | 11 +++ examples/ui/main.c | 199 ++++++++++++++++++++++++++++++++++++++++++ src/ui.c | 45 ++++++++-- 4 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 examples/ui/build.bat create mode 100755 examples/ui/build.sh create mode 100644 examples/ui/main.c diff --git a/examples/ui/build.bat b/examples/ui/build.bat new file mode 100644 index 0000000..74ba806 --- /dev/null +++ b/examples/ui/build.bat @@ -0,0 +1,4 @@ + +set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers + +cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.dll.lib /out:../../bin/example_ui.exe diff --git a/examples/ui/build.sh b/examples/ui/build.sh new file mode 100755 index 0000000..df717e6 --- /dev/null +++ b/examples/ui/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BINDIR=../../bin +RESDIR=../../resources +SRCDIR=../../src + +INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app" +LIBS="-L$BINDIR -lmilepost" +FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG" + +clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_ui main.c diff --git a/examples/ui/main.c b/examples/ui/main.c new file mode 100644 index 0000000..93bbe25 --- /dev/null +++ b/examples/ui/main.c @@ -0,0 +1,199 @@ +/************************************************************//** +* +* @file: main.cpp +* @author: Martin Fouilleul +* @date: 30/07/2022 +* @revision: +* +*****************************************************************/ +#include +#include +#include + +#define _USE_MATH_DEFINES //NOTE: necessary for MSVC +#include + +#include"milepost.h" + +#define LOG_SUBSYSTEM "Main" + + +mg_font create_font() +{ + //NOTE(martin): create font + str8 fontPath = mp_app_get_resource_path(mem_scratch(), "../resources/OpenSansLatinSubset.ttf"); + char* fontPathCString = str8_to_cstring(mem_scratch(), fontPath); + + FILE* fontFile = fopen(fontPathCString, "r"); + if(!fontFile) + { + LOG_ERROR("Could not load font file '%s': %s\n", fontPathCString, strerror(errno)); + return(mg_font_nil()); + } + unsigned char* fontData = 0; + fseek(fontFile, 0, SEEK_END); + u32 fontDataSize = ftell(fontFile); + rewind(fontFile); + fontData = (unsigned char*)malloc(fontDataSize); + fread(fontData, 1, fontDataSize, fontFile); + fclose(fontFile); + + unicode_range ranges[5] = {UNICODE_RANGE_BASIC_LATIN, + UNICODE_RANGE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT, + UNICODE_RANGE_LATIN_EXTENDED_A, + UNICODE_RANGE_LATIN_EXTENDED_B, + UNICODE_RANGE_SPECIALS}; + + mg_font font = mg_font_create_from_memory(fontDataSize, fontData, 5, ranges); + free(fontData); + + return(font); +} + +int main() +{ + LogLevel(LOG_LEVEL_WARNING); + + mp_init(); + mp_clock_init(); //TODO put that in mp_init()? + + ui_init(); + + mp_rect windowRect = {.x = 100, .y = 100, .w = 810, .h = 610}; + mp_window window = mp_window_create(windowRect, "test", 0); + + mp_rect contentRect = mp_window_get_content_rect(window); + + //NOTE: create surface + mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT); + mg_surface_swap_interval(surface, 0); + + //TODO: create canvas + mg_canvas canvas = mg_canvas_create(surface); + + if(mg_canvas_is_nil(canvas)) + { + printf("Error: couldn't create canvas\n"); + return(-1); + } + + mg_font font = create_font(); + + // start app + mp_window_bring_to_front(window); + mp_window_focus(window); + + f32 x = 400, y = 300; + f32 speed = 0; + f32 dx = speed, dy = speed; + f64 frameTime = 0; + + mem_arena textArena = {0}; + mem_arena_init(&textArena); + + while(!mp_should_quit()) + { + f64 startTime = mp_get_time(MP_CLOCK_MONOTONIC); + + mp_pump_events(0); + mp_event event = {0}; + while(mp_next_event(&event)) + { + switch(event.type) + { + case MP_EVENT_WINDOW_CLOSE: + { + mp_request_quit(); + } break; + + default: + break; + } + } + + if(mp_key_pressed(MP_KEY_C)) + { + printf("pressed C!\n"); + } + + mg_surface_prepare(surface); + + mp_rect frame = mp_window_get_content_rect(window); + + ui_style defaultStyle = {.bgColor = {0.9, 0.9, 0.9, 1}, + .borderSize = 2, + .borderColor = {0, 0, 1, 1}, + .fontColor = {0, 0, 0, 1}, + .font = font, + .fontSize = 32}; + + ui_begin_frame(frame.w, frame.h, defaultStyle); + { + ui_push_size(UI_AXIS_X, UI_SIZE_CHILDREN, 10, 0); + ui_push_size(UI_AXIS_Y, UI_SIZE_CHILDREN, 10, 0); + ui_panel("buttons") + { + ui_push_size(UI_AXIS_X, UI_SIZE_TEXT, 5, 0); + ui_push_size(UI_AXIS_Y, UI_SIZE_TEXT, 5, 0); + if(ui_button("1: Click me!").clicked) + { + printf("Clicked button 1!\n"); + } + if(ui_button("2: Click me!").clicked) + { + printf("Clicked button 2!\n"); + } + ui_pop_size(UI_AXIS_X); + ui_pop_size(UI_AXIS_Y); + } + + ui_panel("sliders") + { + static float scrollValue1 = 0.; + static float scrollValue2 = 0.; + + ui_push_size(UI_AXIS_X, UI_SIZE_PIXELS, 300, 1); + ui_push_size(UI_AXIS_Y, UI_SIZE_PIXELS, 50, 1); + ui_scrollbar("scroll1", 0.5, &scrollValue1); + ui_scrollbar("scroll2", 0.5, &scrollValue2); + ui_pop_size(UI_AXIS_X); + ui_pop_size(UI_AXIS_Y); + } + ui_panel("textbox") + { + ui_push_size(UI_AXIS_X, UI_SIZE_PIXELS, 300, 0); + ui_push_size(UI_AXIS_Y, UI_SIZE_PIXELS, 50, 0); + + static str8 text = {}; + ui_text_box_result res = ui_text_box("textbox", mem_scratch(), text); + if(res.changed) + { + mem_arena_clear(&textArena); + text = str8_push_copy(&textArena, res.text); + } + + ui_pop_size(UI_AXIS_X); + ui_pop_size(UI_AXIS_Y); + } + ui_pop_size(UI_AXIS_X); + ui_pop_size(UI_AXIS_Y); + } + ui_end_frame(); + ui_draw(); + + mg_flush(); + mg_surface_present(surface); + + mem_arena_clear(mem_scratch()); + frameTime = mp_get_time(MP_CLOCK_MONOTONIC) - startTime; + } + + mg_font_destroy(font); + mg_canvas_destroy(canvas); + mg_surface_destroy(surface); + mp_window_destroy(window); + + mp_terminate(); + + return(0); +} diff --git a/src/ui.c b/src/ui.c index 4b37da6..5fc6297 100644 --- a/src/ui.c +++ b/src/ui.c @@ -91,9 +91,9 @@ typedef struct ui_context ui_box* hovered; ui_box* focus; - u32 editCursor; - u32 editMark; - u32 editFirstDisplayedChar; + i32 editCursor; + i32 editMark; + i32 editFirstDisplayedChar; f64 editCursorBlinkStart; } ui_context; @@ -2136,12 +2136,45 @@ ui_text_box_result ui_text_box(const char* name, mem_arena* arena, str8 text) ui->editFirstDisplayedChar = 0; ui->editCursor = 0; ui->editMark = 0; - ui->editCursorBlinkStart = ui->frameTime; } - //TODO: set cursor on mouse pos - + ui->editCursorBlinkStart = ui->frameTime; } + if(sig.pressed || sig.dragging) + { + //NOTE: set cursor/extend selection on mouse press or drag + vec2 pos = ui_mouse_position(); + f32 textMargin = 5; //TODO parameterize this margin! must be the same as in ui_text_box_render + f32 cursorX = pos.x - frame->rect.x - textMargin; + + str32 codepoints = utf8_push_to_codepoints(&ui->frameArena, text); + i32 newCursor = codepoints.len; + f32 x = 0; + for(int i = ui->editFirstDisplayedChar; ifont, style->fontSize, str32_slice(codepoints, i, i+1)); + if(x + 0.5*bbox.w > cursorX) + { + newCursor = i; + break; + } + x += bbox.w; + } + //NOTE: put cursor the closest to new cursor (this maximizes the resulting selection, + // and seems to be the standard behaviour across a number of text editor) + if(abs(newCursor - ui->editCursor) > abs(newCursor - ui->editMark)) + { + i32 tmp = ui->editCursor; + ui->editCursor = ui->editMark; + ui->editMark = tmp; + } + //NOTE: set the new cursor, and set or leave the mark depending on mode + ui->editCursor = newCursor; + if(sig.pressed && !(mp_key_mods() & MP_KEYMOD_SHIFT)) + { + ui->editMark = ui->editCursor; + } + } } else {