diff --git a/README.md b/README.md new file mode 100644 index 0000000..48b9558 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# wasm_transcript_browser + +This project generates a searchable transcript browser from local `.srt` files and ships it as a WebAssembly web app. + +Type a word or phrase, click a match, and it opens the exact timestamp in the matching YouTube video. You can also copy a direct timestamped link. + +## What it does + +- Parses subtitle files (`.srt`) from a local folder. +- Generates a packed transcript index (`build/entries.inc`) at build time. +- Compiles a C codebase to `main.wasm` and serves it with a small HTML/JS shell. +- Provides instant text search over all transcript text. +- Creates timestamped YouTube links from search hits. + +## How the pipeline works + +1. `build_file.c` defines a hard-coded source folder: + + - `folder_to_create_transcript_for` + +2. During build, `src/prototype/prototype.meta.c`: + + - scans `.srt` files in that folder, + - parses subtitle entries, + - normalizes text (lowercase, removes punctuation, turns `-` into spaces), + - writes generated data to `build/entries.inc`. + +3. `src/prototype/main.c` includes that generated file and compiles to WASM. + +4. The browser app (`package/index.html` + `package/main.wasm`) renders a custom UI and handles link open/copy actions. + +## Transcript filename format + +To build correct YouTube links, filenames are expected to include the 11-char YouTube ID wrapped in one character on each side at the end of the name (commonly brackets). + +Example: + +- `My Video Title [dQw4w9WgXcQ].en.srt` +- `My Video Title [dQw4w9WgXcQ].srt` + +The app extracts the video ID from the ending token and creates links like: + +- `https://youtu.be/?feature=shared&t=` + +## Build + +### Prerequisites + +- `clang` (or `gcc`/MSVC depending on your platform) +- Python 3 (for local static server) + +### Linux + +```bash +./build.sh +``` + +### Windows + +```bat +build.bat +``` + +Build output of interest: + +- `build/entries.inc` (generated transcript index) +- `package/index.html` +- `package/main.wasm` + +## Run locally + +From `package/`: + +```bash +python3 -m http.server 8080 +``` + +Then open: + +- `http://localhost:8080` + +Windows helper script: + +- `package/run_server.bat` + +## Configuration notes + +- Update transcript source folder in `build_file.c` before building. +- The current build file also contains hard-coded deploy commands (`ssh`/`scp`) in `build_prototype_wasm_target`; remove or update those for your own environment. + +## Project status + +This is an older personal project with a custom C build/codegen stack. The rough edges are expected, but the core idea works: local transcript ingestion + fast search + one-click timestamped YouTube links. diff --git a/build_file.c b/build_file.c index a8cb5c8..bf339cd 100644 --- a/build_file.c +++ b/build_file.c @@ -19,12 +19,22 @@ #include "src/meta/meta_table_format.c" #include "src/meta/meta_cfiles.c" +s8_t folder_to_create_transcript_for = s8("D:/videos/jblow"); + #include "src/app/app.meta.c" #include "src/ui/ui.meta.c" #include "src/render/render.meta.c" #include "src/prototype/prototype.meta.c" #include "src/testing/testing.meta.c" +b32 run_win32_app_base_target = false; +b32 run_testing_target = false; +b32 run_prototype_dll_target = false; +b32 run_prototype_standalone_target = false; + +b32 run_prototype_wasm_target = true; +b32 run_server = false; + void build_testing_target(void) { if (!cache_code_modified(s8("../src/testing/testing_main.c"), s8("testing.exe"))) { return; @@ -102,6 +112,13 @@ void build_prototype_wasm_target(void) { ); os_copy(s8("main.wasm"), s8("../package/main.wasm"), os_copy_overwrite); if (ok != 0) exit(ok); + + // os_systemf("ssh root@65.21.244.51 rm /website/index.html /website/main.wasm"); + // os_systemf("scp ../package/index.html root@65.21.244.51:/website/index.html"); + // os_systemf("scp ../package/main.wasm root@65.21.244.51:/website/main.wasm"); + os_systemf("ssh root@157.90.144.237 rm /var/www/html/jiang/index.html /var/www/html/jiang/main.wasm"); + os_systemf("scp ../package/index.html root@157.90.144.237:/var/www/html/jiang/index.html"); + os_systemf("scp ../package/main.wasm root@157.90.144.237:/var/www/html/jiang/main.wasm"); } } @@ -151,13 +168,6 @@ int main(int argc, char **argv) { generate_render_code(&tcx->temp); generate_testing_code(&tcx->temp); - b32 run_win32_app_base_target = true; - b32 run_testing_target = false; - b32 run_prototype_dll_target = false; - b32 run_prototype_wasm_target = true; - b32 run_prototype_standalone_target = false; - b32 run_server = false; - if (run_server) { os_systemf("start /D ..\\package ..\\package\\run_server.bat"); } diff --git a/init.project.lua b/init.project.lua deleted file mode 100644 index 859ffa6..0000000 --- a/init.project.lua +++ /dev/null @@ -1,5 +0,0 @@ -function CC(cmd) - Cmd { working_dir = GetProjectPath(), destination = "console", cmd = cmd } -end - - diff --git a/package/index.html b/package/index.html index a95a95c..c62a52e 100644 --- a/package/index.html +++ b/package/index.html @@ -5,7 +5,7 @@ - Document + Hello traveller! @@ -60,6 +60,20 @@ class stream_t { } } +function unsecuredCopyToClipboard(text) { + const textArea = document.createElement("textarea"); + textArea.value = text; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + document.execCommand('copy'); + } catch (err) { + console.error('Unable to copy to clipboard', err); + } + document.body.removeChild(textArea); +} + class memory_t { exports = null; // this is set after wasm module created constructor(wasm_memory) { @@ -108,6 +122,9 @@ const wasm_app_imports = { wasm_trap: () => { throw new Error(); }, wasm_write_to_console: (str, len) => { console.log(mem.read_cstr(str, len)); }, + wasm_open_link: (str, len) => { window.open(mem.read_cstr(str, len)); }, + wasm_set_clipboard: (str, len) => { unsecuredCopyToClipboard(mem.read_cstr(str, len)); }, + // gfx wasm_draw_text: (str, len, x, y, font_str, font_len, font_size, r, g, b, a) => { ctx2d.font = `${font_size}px ${mem.read_cstr(font_str, font_len)}`; @@ -137,8 +154,14 @@ const wasm_app_imports = { ctx2d.rect(x, y, w, h); ctx2d.clip(); }, - wasm_open_link: (str, len) => { - window.open(mem.read_cstr(str, len)); + wasm_set_cursor: (cursor_num) => { + let cursor = 'auto'; + if (cursor_num == 0) { + cursor = 'text'; + } else if (cursor_num == 1) { + cursor = 'pointer'; + } + document.getElementById("canvas").style.cursor = cursor; }, }; @@ -201,14 +224,20 @@ const wasm_app_imports = { } }); + addEventListener("contextmenu", (event) => { event.preventDefault(); return false; }) + addEventListener("resize", (event) => { wake_up(); }); addEventListener("keydown", (event) => { if (["F1", "F2", "F3", "p"].includes(event.key)) event.preventDefault(); - wasm_exports["wasm_key_down"](mem.write_string_to_cglobal("wasm_temp_buff1", event.key), event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + let memory = wasm_exports["wasm_temp_alloc"](64); + mem.write_string_into_cmemory(memory, 64, event.key); + wasm_exports["wasm_key_down"](memory, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); wake_up(); }); addEventListener("keyup", (event) => { - wasm_exports["wasm_key_up"](mem.write_string_to_cglobal("wasm_temp_buff1", event.key), event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + let memory = wasm_exports["wasm_temp_alloc"](64); + mem.write_string_into_cmemory(memory, 64, event.key); + wasm_exports["wasm_key_up"](memory, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); wake_up(); }); addEventListener("mousemove", (event) => { @@ -216,7 +245,7 @@ const wasm_app_imports = { wake_up(); }); addEventListener("mousedown", (event) => { - wasm_exports["wasm_mouse_down"](event.clientX, event.clientY, event.button, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + wasm_exports["wasm_mouse_down"](event.clientX, event.clientY, event.button, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey, event.detail); wake_up(); }); addEventListener("mouseup", (event) => { diff --git a/src/app/app.gen.c b/src/app/app.gen.c index c4b3f48..ce5572e 100644 --- a/src/app/app.gen.c +++ b/src/app/app.gen.c @@ -266,13 +266,14 @@ type_t type__app_event_t = { type_kind_struct, s8_const("app_event_t"), sizeof(a {.name = s8_const("key"), .type = &type__app_key_t, .offset = offsetof(app_event_t, key), .dont_serialize = 0}, {.name = s8_const("text"), .type = &type__s8_t, .offset = offsetof(app_event_t, text), .dont_serialize = 0}, {.name = s8_const("mouse_wheel_delta"), .type = &type__v3f32_t, .offset = offsetof(app_event_t, mouse_wheel_delta), .dont_serialize = 0}, + {.name = s8_const("clicks"), .type = &type__i8, .offset = offsetof(app_event_t, clicks), .dont_serialize = 0}, {.name = s8_const("ctrl"), .type = &type__b8, .offset = offsetof(app_event_t, ctrl), .dont_serialize = 0}, {.name = s8_const("shift"), .type = &type__b8, .offset = offsetof(app_event_t, shift), .dont_serialize = 0}, {.name = s8_const("alt"), .type = &type__b8, .offset = offsetof(app_event_t, alt), .dont_serialize = 0}, {.name = s8_const("mouse_pos"), .type = &type__v2f32_t, .offset = offsetof(app_event_t, mouse_pos), .dont_serialize = 0}, {.name = s8_const("mouse_delta"), .type = &type__v2f32_t, .offset = offsetof(app_event_t, mouse_delta), .dont_serialize = 0}, }, - .count = 12, + .count = 13, }; type_t type__app_frame_t = { type_kind_struct, s8_const("app_frame_t"), sizeof(app_frame_t), .members = (type_member_t[]){ diff --git a/src/app/app.gen.h b/src/app/app.gen.h index 9308a21..d7dda13 100644 --- a/src/app/app.gen.h +++ b/src/app/app.gen.h @@ -104,6 +104,7 @@ struct app_event_t { app_key_t key; s8_t text; v3f32_t mouse_wheel_delta; + i8 clicks; b8 ctrl; b8 shift; b8 alt; diff --git a/src/app/app.meta.c b/src/app/app.meta.c index e73b5e5..a36a038 100644 --- a/src/app/app.meta.c +++ b/src/app/app.meta.c @@ -170,6 +170,7 @@ void generate_app_code(ma_arena_t *arena) { v3f32_t mouse_wheel_delta; // @mouse_wheel // always present data + i8 clicks; b8 ctrl; b8 shift; b8 alt; diff --git a/src/app/app_wasm.c b/src/app/app_wasm.c index d826dcb..2b9f1de 100644 --- a/src/app/app_wasm.c +++ b/src/app/app_wasm.c @@ -1,9 +1,3 @@ -gb_wasm_export char wasm_temp_buff1[128] = {[127] = 0x13}; -gb_wasm_export i32 wasm_temp_buff1_len = 127; -gb_wasm_export char wasm_temp_buff2[128] = {[127] = 0x13}; -gb_wasm_export i32 wasm_temp_buff2_len = 127; -fn_wasm_import void wasm_open_link(isize str, i32 len); - gb f32 wasm_dpr; gb f32 wasm_delta_time; gb f32 wasm_time; @@ -55,7 +49,7 @@ fn_wasm_export void wasm_mouse_move(f32 x, f32 y, b32 ctrl, b32 shift, b32 alt, wasm_set_cached_buttons(ctrl, shift, alt); } -fn_wasm_export void wasm_mouse_down(f32 x, f32 y, i32 button, b32 ctrl, b32 shift, b32 alt, b32 meta) { +fn_wasm_export void wasm_mouse_down(f32 x, f32 y, i32 button, b32 ctrl, b32 shift, b32 alt, b32 meta, i32 clicks) { button += 1; assert(button >= app_mouse_button_left && button <= app_mouse_button_right); wasm_set_cached_buttons(ctrl, shift, alt); @@ -63,6 +57,7 @@ fn_wasm_export void wasm_mouse_down(f32 x, f32 y, i32 button, b32 ctrl, b32 shif wasm_add_event((app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = button, + .clicks = clicks, }); } @@ -88,8 +83,6 @@ fn_wasm_export void wasm_mouse_wheel(f32 x, f32 y, f32 delta_x, f32 delta_y, f32 fn_wasm_export void wasm_key_down(char *key, b32 ctrl, b32 shift, b32 alt, b32 meta) { wasm_set_cached_buttons(ctrl, shift, alt); - assert(wasm_temp_buff1[127] == 0x13); // make sure we didn't overwrite memory in JS - assert(wasm_temp_buff2[127] == 0x13); s8_t key8 = s8_from_char(key); wasm_key_map_t map = wasm_map_key_string_to_app_key(key8); if (map.key != app_key_null) { @@ -112,8 +105,6 @@ fn_wasm_export void wasm_key_down(char *key, b32 ctrl, b32 shift, b32 alt, b32 m fn_wasm_export void wasm_key_up(char *key, b32 ctrl, b32 shift, b32 alt, b32 meta) { wasm_set_cached_buttons(ctrl, shift, alt); - assert(wasm_temp_buff1[127] == 0x13); // make sure we didn't overwrite memory in JS - assert(wasm_temp_buff2[127] == 0x13); s8_t key8 = s8_from_char(key); wasm_key_map_t map = wasm_map_key_string_to_app_key(key8); @@ -159,7 +150,15 @@ fn_wasm_export void wasm_init(void) { os_core_init(); } +fn_wasm_export isize wasm_temp_alloc(isize size) { + return (isize)ma_push_size(&tcx->temp, size); +} + +fn_wasm_import void wasm_open_link(isize str, i32 len); +fn_wasm_import void wasm_set_clipboard(isize str, i32 len); +fn_wasm_import void wasm_set_cursor(i32 cursor_num); + // @todo: os? -fn void open_link(s8_t url) { - wasm_open_link((isize)url.str, (i32)url.len); -} \ No newline at end of file +fn void open_link(s8_t url) { wasm_open_link((isize)url.str, (i32)url.len); } +fn void set_clipboard(s8_t url) { wasm_set_clipboard((isize)url.str, (i32)url.len); } +fn void set_cursor(i32 cursor_num) { wasm_set_cursor(cursor_num); } \ No newline at end of file diff --git a/src/app/app_wasm.html b/src/app/app_wasm.html index a95a95c..63e0f16 100644 --- a/src/app/app_wasm.html +++ b/src/app/app_wasm.html @@ -5,7 +5,7 @@ - Document + Hello traveller! @@ -60,6 +60,20 @@ class stream_t { } } +function unsecuredCopyToClipboard(text) { + const textArea = document.createElement("textarea"); + textArea.value = text; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + document.execCommand('copy'); + } catch (err) { + console.error('Unable to copy to clipboard', err); + } + document.body.removeChild(textArea); +} + class memory_t { exports = null; // this is set after wasm module created constructor(wasm_memory) { @@ -108,6 +122,9 @@ const wasm_app_imports = { wasm_trap: () => { throw new Error(); }, wasm_write_to_console: (str, len) => { console.log(mem.read_cstr(str, len)); }, + wasm_open_link: (str, len) => { window.open(mem.read_cstr(str, len)); }, + wasm_set_clipboard: (str, len) => { unsecuredCopyToClipboard(mem.read_cstr(str, len)); }, + // gfx wasm_draw_text: (str, len, x, y, font_str, font_len, font_size, r, g, b, a) => { ctx2d.font = `${font_size}px ${mem.read_cstr(font_str, font_len)}`; @@ -137,8 +154,14 @@ const wasm_app_imports = { ctx2d.rect(x, y, w, h); ctx2d.clip(); }, - wasm_open_link: (str, len) => { - window.open(mem.read_cstr(str, len)); + wasm_set_cursor: (cursor_num) => { + let cursor = 'auto'; + if (cursor_num == 0) { + cursor = 'text'; + } else if (cursor_num == 1) { + cursor = 'pointer'; + } + document.getElementById("canvas").style.cursor = cursor; }, }; @@ -201,14 +224,20 @@ const wasm_app_imports = { } }); + addEventListener("contextmenu", (event) => { event.preventDefault(); return false; }) + addEventListener("resize", (event) => { wake_up(); }); addEventListener("keydown", (event) => { if (["F1", "F2", "F3", "p"].includes(event.key)) event.preventDefault(); - wasm_exports["wasm_key_down"](mem.write_string_to_cglobal("wasm_temp_buff1", event.key), event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + let memory = wasm_exports["wasm_temp_alloc"](8); + mem.write_string_into_cmemory(memory, 8, event.key); + wasm_exports["wasm_key_down"](memory, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); wake_up(); }); addEventListener("keyup", (event) => { - wasm_exports["wasm_key_up"](mem.write_string_to_cglobal("wasm_temp_buff1", event.key), event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + let memory = wasm_exports["wasm_temp_alloc"](8); + mem.write_string_into_cmemory(memory, 8, event.key); + wasm_exports["wasm_key_up"](memory, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); wake_up(); }); addEventListener("mousemove", (event) => { @@ -216,7 +245,7 @@ const wasm_app_imports = { wake_up(); }); addEventListener("mousedown", (event) => { - wasm_exports["wasm_mouse_down"](event.clientX, event.clientY, event.button, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey); + wasm_exports["wasm_mouse_down"](event.clientX, event.clientY, event.button, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey, event.detail); wake_up(); }); addEventListener("mouseup", (event) => { diff --git a/src/core/core_arena.c b/src/core/core_arena.c index 018c522..98f59b0 100644 --- a/src/core/core_arena.c +++ b/src/core/core_arena.c @@ -83,7 +83,6 @@ fn void *ma_push_size_ex(ma_arena_t *arena, usize size) { } } if (new_len > arena->commit) { - debugf("expression failed(new_len > arena->commit) len:%llu base_len:%llu reserve:%llu commit:%llu, align: %llu", arena->len, arena->base_len, arena->reserve, arena->commit, arena->align); invalid_codepath; return NULL; } diff --git a/src/meta/build_tool.c b/src/meta/build_tool.c index e7ed81e..33efe45 100644 --- a/src/meta/build_tool.c +++ b/src/meta/build_tool.c @@ -4479,8 +4479,11 @@ CL_PRIVATE_FUNCTION void CL_DefaultTokenize(CL_Lexer *T, CL_Token *token) { CL_Advance(T); } uint64_t len = T->stream - token->str; - CL_ASSERT(len > 2); - token->u64 = CL_ParseInteger(T, token, token->str + 2, len - 2, 16); + if (len <= 2) { + CL_ReportError(T, token, "Invalid hex number"); + } else { + token->u64 = CL_ParseInteger(T, token, token->str + 2, len - 2, 16); + } break; } } diff --git a/src/prototype/main.c b/src/prototype/main.c index ee8e13f..4afd49e 100644 --- a/src/prototype/main.c +++ b/src/prototype/main.c @@ -15,19 +15,17 @@ // #include "win32_transcript_browser.c" #include "prototype.gen.c" -// @todo: how to pack the data so it's available on wasm? -// @todo: cursor doesn't change when hovering on text +// @todo: scrolling when clicked on bar bugs out and is slow // @todo: copy paste -// @todo: selecting and writing is bugged -// @todo: make it possible without executable -// https://drive.proton.me/urls/F9X0GWGH38#t7MaKdLbvOfN // @todo @ui scrolling when focused button goes out of screen fn_export b32 app_update(thread_ctx_t *thread_ctx, app_frame_t *frame) { tcx = thread_ctx; + if (frame->first_event->kind == app_event_kind_init) { run_all_tests(); + mt_tweak_f32(font_size, 30, 4, 200); mt_tweak_f32(_font_size, 30, 30, 30); @@ -38,7 +36,7 @@ fn_export b32 app_update(thread_ctx_t *thread_ctx, app_frame_t *frame) { res_init(tcx->user_ctx); res_t *res = tcx->user_ctx; - res_report(res, "type in the path to folder with videos and press enter, for me it would be: D:/videos/jiang"); + res_report(res, "type something!"); return true; } else if (frame->first_event->kind == app_event_kind_reload) { ui_reload(); @@ -57,4 +55,4 @@ fn_export b32 app_update(thread_ctx_t *thread_ctx, app_frame_t *frame) { // ui_demo_update(frame, tweak_table, lengthof(tweak_table)); transcript_browser_update(frame, tweak_table, lengthof(tweak_table)); return true; -} +} \ No newline at end of file diff --git a/src/prototype/prototype.meta.c b/src/prototype/prototype.meta.c index 4050bd3..c70f716 100644 --- a/src/prototype/prototype.meta.c +++ b/src/prototype/prototype.meta.c @@ -20,7 +20,8 @@ void generate_transcript(s8_t folder) { i64 i1 = 0; for (i64 i0 = 0; i0 < it->string.len; i0 += 1) { if (it->string.str[i0] == '"' || it->string.str[i0] == '\'' || - it->string.str[i0] == ',' || it->string.str[i0] == '.') { + it->string.str[i0] == ',' || it->string.str[i0] == '.' || + it->string.str[i0] == '\\') { continue; } else if (it->string.str[i0] == '-') { temp_string[i1++] = ' '; @@ -77,7 +78,7 @@ void generate_transcript(s8_t folder) { } void generate_prototype_code(ma_arena_t *arena) { - generate_transcript(s8("D:/videos/jiang")); + generate_transcript(folder_to_create_transcript_for); sb8_t *include_paths = sb8(arena); sb8_append(include_paths, os_abs(arena, s8("../src"))); mt_files_t files = mt_lex_files(arena, s8("../src/prototype/main.c"), include_paths); diff --git a/src/prototype/transcript_browser.c b/src/prototype/transcript_browser.c index 7912e12..58ffb6c 100644 --- a/src/prototype/transcript_browser.c +++ b/src/prototype/transcript_browser.c @@ -31,7 +31,7 @@ fn void res_search_matches(res_t *res) { const i64 search_iter = 4096*16; for (i64 i = res->search_idx; i < res->search_idx + search_iter && i < the_text_itself.len; i += 1) { s8_t text = s8_skip(the_text_itself, i); - if (s8_starts_with(text, res->search_text_input.string)) { + if (s8_starts_with_ex(text, res->search_text_input.string, true)) { s8_t found = s8_make(text.str, res->search_text_input.len); array_add(&res->search_matches, found); } @@ -64,11 +64,11 @@ fn srt_entry_t res_find_entry(res_t *res, s8_t res_string) { fn void transcript_browser_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_count) { ui_begin_frame(frame); rn_begin_frame(frame); - res_t *res = (res_t *)tcx->user_ctx; res_search_matches(res); for (app_event_t *ev = frame->first_event; ev; ev = ev->next) { + ui_begin_build(UILOC, ev, window_rect_from_frame(frame)); locl b8 set_focus; @@ -130,24 +130,30 @@ fn void transcript_browser_update(app_frame_t *frame, mt_tweak_t *tweak_table, i s8_t middle = it; s8_t string = s8_printf(scratch.arena, "%S**%S**%S", left, middle, right); - ui_box_t *box = ui_box(.string = string, .flags = {.draw_rect = true, .draw_text = true, .keyboard_nav = true}); + ui_box_t *box = ui_box(.string = string, .flags = {.set_click_cursor = res->search_text_input.len != 0, .draw_rect = true, .draw_text = true, .keyboard_nav = true}); ui_signal_t sig = ui_signal_from_box(box); if (sig.clicked && matches.data != res->error_messages.data) { srt_entry_t entry = res_find_entry(res, middle); int seconds = entry.hour * 60 * 60 + entry.minute * 60 + entry.second; - debugf("time = %u %u %u seconds = %d", entry.hour, entry.minute, entry.second, seconds); s8_t id = s8_get_postfix(entry.filepath, 13); - debugf("id = %S", id); - if (id.str[0] != '[') { - fatalf("internal error: dear user, it appears that youtube id link is not composed of exactly 11 letters, quite a pickle, if you could report that to me would be very nice"); - } id = s8_skip(id, 1); id = s8_chop(id, 1); s8_t url = s8_printf(scratch.arena, "https://youtu.be/%S?feature=shared&t=%d", id, seconds); - debugf("url = %S", url); open_link(url); } + b32 right_clicked = (ui->hot.value == box->id.value && ev->kind == app_event_kind_mouse_down && ev->mouse_button == app_mouse_button_right); + b32 copied = (ui->focus.value == box->id.value && ev->kind == app_event_kind_key_down && ev->key == app_key_c && ev->ctrl); + if (right_clicked || copied) { + srt_entry_t entry = res_find_entry(res, middle); + int seconds = entry.hour * 60 * 60 + entry.minute * 60 + entry.second; + s8_t id = s8_get_postfix(entry.filepath, 13); + id = s8_skip(id, 1); + id = s8_chop(id, 1); + s8_t url = s8_printf(scratch.arena, "https://youtu.be/%S?feature=shared&t=%d %S%S%S", id, seconds, left, middle, right); + set_clipboard(url); + } + if (set_focus) { ui->focus = box->id; set_focus = false; diff --git a/src/ui/ui.c b/src/ui/ui.c index b8c63c4..663c0ff 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -402,6 +402,12 @@ fn r2f32_t ui_get_appear_rect(ui_box_t *box) { fn ui_draw_compute_t ui_draw_compute(ui_box_t *box) { ui_draw_compute_t co = {0}; + if (ui->set_cursor_for_frame == false && box->flags.set_click_cursor && + (ui->hot.value == box->id.value || ui->active.value == box->id.value)) { + set_cursor(1); + ui->set_cursor_for_frame = true; + } + co.rect = ui_get_appear_rect(box); co.background_color = box->background_color; co.text_color = box->text_color; @@ -431,6 +437,10 @@ fn ui_draw_compute_t ui_draw_compute(ui_box_t *box) { } fn void ui_text_input_draw(ui_box_t *box) { + if (ui->set_cursor_for_frame == false && (ui->hot.value == box->id.value || ui->active.value == box->id.value)) { + set_cursor(0); + ui->set_cursor_for_frame = true; + } ui_draw_compute_t co = ui_draw_compute(box); rn_draw_rect(co.rect, co.background_color); @@ -535,11 +545,7 @@ fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, ui_box_fla if (ev->kind == app_event_kind_text) { signal.text_changed = true; ui_text_replace(ti, ti->caret.range, ev->text); - if (sel_size) { - ti->caret = ui_carets(ti->caret.range.min); - } else { - ti->caret = ui_carets(ti->caret.range.min + 1); - } + ti->caret = ui_carets(ti->caret.range.min + 1); } else if (ev->kind == app_event_kind_key_down) { if (ev->key == app_key_backspace) { signal.text_changed = true; @@ -564,6 +570,7 @@ fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, ui_box_fla } else { ui_text_replace(ti, r1i32(ti->caret.e[0], ti->caret.e[0] + 1), s8_null); } + } else if (ev->key == app_key_left) { signal.text_changed = true; if (ev->shift) { @@ -580,13 +587,20 @@ fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, ui_box_fla } } else if (ev->key == app_key_enter) { signal.text_commit = true; + } else if (ev->key == app_key_a && ev->ctrl) { + ti->caret = ui_caret(0, ti->len); } + } v2f32_t size = rn_measure_string(rn->main_font, s8("_")); v2f32_t pos = v2f32_sub(ev->mouse_pos, box->final_rect.min); i32 p = (i32)f32_round(pos.x / size.x); if (ev->kind == app_event_kind_mouse_down && ev->mouse_button == app_mouse_button_left) { - ti->caret = ui_carets(p); + if (ev->clicks == 2) { + // ti->caret = ui_caret(0, ti->len); + } else { + ti->caret = ui_carets(p); + } } if (signal.dragging) { ti->caret = ui_caret_set_front(ti->caret, p); @@ -1203,6 +1217,7 @@ fn void ui_draw(void) { fn void ui_begin_frame(app_frame_t *frame) { ui = tcx->ui_ctx; ui->frame = frame; + ui->set_cursor_for_frame = false; } fn void ui_end_frame(void) { @@ -1230,6 +1245,10 @@ fn void ui_end_frame(void) { ui->active = ui_null_id; } } + + if (ui->set_cursor_for_frame == false) { + set_cursor(32323); + } } fn void ui_reload(void) { diff --git a/src/ui/ui.h b/src/ui/ui.h index f051de5..d74e64c 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -18,6 +18,7 @@ struct ui_box_flags_t { b8 draw_border: 1; b8 draw_text: 1; b8 clip_rect: 1; + b8 set_click_cursor : 1; b8 animate_appear: 1; @@ -154,6 +155,8 @@ struct ui_t { // drawing r2f32_t clip_rect; + + b32 set_cursor_for_frame; }; gb ui_t *ui; diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 7193d85..0000000 --- a/todo.txt +++ /dev/null @@ -1,61 +0,0 @@ -[ ] app - [ ] event playback - [ ] win32 - [ ] hot reload / plugins - [ ] tests using yield - [ ] touchpad gestures: https://learn.microsoft.com/en-us/windows/win32/wintouch/windows-touch-gestures-overview - [ ] linux - [ ] wasm - [ ] drag and drop - [ ] open file dialog -[ ] os - [?] wasm (explore) - [ ] linux - -[ ] render - [ ] wasm (maybe try first without opengl and then opengl) - [ ] opengl - [ ] maybe copy package stuff to build? - -[ ] ui - [ ] sleep when not animating - [ ] concept of active panel - [ ] separate root so can't focus off with keyboard - [ ] clicking away from panel, closes it or changes focus - [ ] hot to move between panels? - [ ] table - [ ] scrolling with keyboard - [ ] context menu - [ ] maybe a separate root for context menu? - [ ] hover tooltips - [ ] separate root which gets drawn on top of all ui - [ ] upper left menu dynamics - [ ] text input - [ ] ctrl backspace, ctrl delete - [ ] set mouse cursor on hover - [ ] maybe copy over text editor code from other project? - [ ] everything lister (with edits etc.) - [ ] pernament storage? - [ ] push pop flag - [ ] how to do colors? - - css like based on ids and pre built palettes - - fat box with stacks (now) - [ ] demo style, with different buttons and controls, headings - [ ] color picker - [ ] slider - [ ] draw image in box ui - [ ] push flags? - -[ ] core - [ ] ast - [ ] move to core layer at some point as the abstract format for different types of serialization - [ ] use bit fields instead of ast_flag - [ ] mt_tag syntax // f32 value; mt_tag(min = 0, min = 10) // mt_tag(something) - [ ] remove most flags - [ ] explore metadata flags for common formats aka (key_value, list etc.. will - [ ] json format - [ ] ini format - [ ] build system - [ ] use threads - [ ] meta - [ ] s8_bin