transcript browser working

This commit is contained in:
Krzosa Karol
2025-07-28 14:12:39 +02:00
parent 869af3f6dd
commit c1c58d33b9
5 changed files with 389 additions and 138 deletions

View File

@@ -175,3 +175,11 @@ fn ma_temp_t ma_begin_scratch2(ma_arena_t *a, ma_arena_t *b) {
ma_arena_t *conflicts[] = {a, b}; ma_arena_t *conflicts[] = {a, b};
return ma_begin_scratch_ex(conflicts, 2); return ma_begin_scratch_ex(conflicts, 2);
} }
fn void *ma_push__copy(ma_arena_t *arena, void *data, u64 size) {
void *result = ma_push_size_ex(arena, size);
if (result) {
memory_copy(result, data, size);
}
return result;
}

View File

@@ -32,6 +32,8 @@ fn ma_arena_t *ma_push_arena(ma_arena_t *allocator, usize size);
fn void ma_push_arena_ex(ma_arena_t *allocator, ma_arena_t *result, usize size); fn void ma_push_arena_ex(ma_arena_t *allocator, ma_arena_t *result, usize size);
#define ma_push_type(arena, Type) (Type *)ma_push_size((arena), sizeof(Type)) #define ma_push_type(arena, Type) (Type *)ma_push_size((arena), sizeof(Type))
#define ma_push_array(arena, Type, count) (Type *)ma_push_size((arena), sizeof(Type) * (count)) #define ma_push_array(arena, Type, count) (Type *)ma_push_size((arena), sizeof(Type) * (count))
#define ma_push_type_copy(ma, data) ma_push__copy((ma), (data), sizeof(*(data)))
#define ma_push_array_copy(ma, data, count) ma_push__copy((ma), (data), (count) * sizeof(*(data)))
// //
// pop // pop

View File

@@ -159,10 +159,13 @@ fn void os_advance(os_iter_t *it) {
it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
it->name = s8_from_s16(it->arena, s16_make(data->cFileName, str16_len(data->cFileName))); it->name = s8_from_s16(it->arena, s16_make(data->cFileName, str16_len(data->cFileName)));
char *is_dir = it->is_directory ? "/" : ""; char *is_dir = it->is_directory ? "/" : "";
char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/"; char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/";
it->rel = s8_printf(it->arena, "%S%s%S%s", it->path, separator, it->name, is_dir); it->rel = s8_printf(it->arena, "%S%s%S%s", it->path, separator, it->name, is_dir);
it->abs = os_abs(it->arena, it->rel); it->abs = os_abs(it->arena, it->rel);
s8_normalize_path_unsafe(it->rel);
s8_normalize_path_unsafe(it->abs);
it->is_valid = true; it->is_valid = true;
if (it->is_directory) { if (it->is_directory) {

View File

@@ -20,9 +20,13 @@ fn_export b32 app_update(thread_ctx_t *thread_ctx, app_frame_t *frame) {
mt_tweak_f32(font_size, 30, 4, 200); mt_tweak_f32(font_size, 30, 4, 200);
mt_tweak_f32(_font_size, 30, 30, 30); mt_tweak_f32(_font_size, 30, 30, 30);
ma_arena_t *perm = &tcx->perm; rn_init(&tcx->perm, font_size, frame->dpr);
rn_init(perm, font_size, frame->dpr); ui_init(&tcx->perm);
ui_init(perm);
global_res = ma_push_type(&tcx->perm, res_t);
res_init(global_res);
res_add_folder(global_res, s8("D:/videos/jiang"));
// res_add_folder(global_res, s8("D:/tools"));
return true; return true;
} else if (frame->first_event->kind == app_event_kind_reload) { } else if (frame->first_event->kind == app_event_kind_reload) {
ui_reload(); ui_reload();

View File

@@ -1,103 +1,8 @@
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);
s8_t cmds[] = {
s8("show data tab"),
s8("show log tab"),
s8("show menus tab"),
s8("show menus tab2"),
s8("show menus tab3"),
s8("show menus tab4"),
s8("show menus tab5"),
s8("show menus tab6"),
s8("show menus tab8"),
s8("show menus tab7"),
s8("show menus tab9"),
s8("show menus tab0"),
s8("show menus tab11"),
s8("show menus tab22"),
s8("show menus tab33"),
s8("show menus tab44"),
s8("show menus tab55"),
s8("show menus tab66"),
};
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;
locl char buff[128];
locl ui_text_input_t text_input;
ui_signal_t text_input_sig;
text_input.str = buff;
text_input.cap = lengthof(buff);
ui_box_t *top_box = ui_box(.null_id = true, .rect = r2f32_cut_top(ui_top_rectp(), ui_em(1.0f)), .flags = {.draw_rect = true, .clip_rect = true});
ui_set_text_align(ui_text_align_left)
ui_set_lop(ui_lop_cut_top)
ui_set_top(top_box) {
text_input_sig = ui_text_input(&text_input, .sim_even_if_no_focus = true, .keyboard_nav = false);
}
ma_temp_t scratch = ma_begin_scratch();
s8_t needle = s8_make(text_input.str, text_input.len);
fuzzy_pair_t *pairs = s8_fuzzy_rate_array(scratch.arena, needle, cmds, lengthof(cmds));
if (text_input_sig.text_changed) {
set_focus = true;
}
ui_box_t *lister = ui_box(.null_id = true, .rect = r2f32_cut_top(ui_top_rectp(), ui_max), .flags = {.draw_rect = true, .clip_rect = true});
locl f32 verti_scroller_value;
ui_scroller_t scroller = ui_begin_scroller(UILOC, (ui_scroller_params_t){
.parent = lister,
.verti = {
.enabled = true,
.value = &verti_scroller_value,
.item_pixels = ui_em(1),
.item_count = (i32)lengthof(cmds),
},
});
ui_set_top(lister)
ui_set_string_pos_offset(ui_dm(1)) {
ui_cut_top_scroller_offset(scroller);
for (i32 i = scroller.verti.istart; i < scroller.verti.iend; i += 1) {
s8_t text = cmds[pairs[i].index];
ui_box_t *box = ui_box(.string = text, .flags = { .draw_rect = true, .draw_text = true, .keyboard_nav = true });
ui_signal_from_box(box);
if (set_focus) {
ui->focus = box->id;
set_focus = false;
}
}
}
if (text_input_sig.text_commit) {
fuzzy_pair_t *pair = &pairs[0];
// ui_g_panel = (i32)pair->index + 1;
ui_text_clear(&text_input);
set_focus = true;
}
ui_end_scroller(scroller);
ui_end_build();
ma_end_scratch(scratch);
}
rn_begin(white_color);
ui_draw();
rn_end();
ui_end_frame();
}
/////////////////////////////// ///////////////////////////////
// work queue // work queue
#define WORK_FUNCTION(name) void name(void *data) #define FN_WORK(name) void name(void *data)
typedef WORK_FUNCTION(work_queue_callback_t); typedef FN_WORK(work_queue_callback_t);
typedef struct { typedef struct {
work_queue_callback_t *callback; work_queue_callback_t *callback;
@@ -106,32 +11,32 @@ typedef struct {
typedef struct work_queue_t work_queue_t; typedef struct work_queue_t work_queue_t;
struct work_queue_t { struct work_queue_t {
int32_t thread_count; i32 thread_count;
work_queue_entry_t entries[256]; work_queue_entry_t entries[1024];
int64_t volatile index_to_write; i64 volatile index_to_write;
int64_t volatile index_to_read; i64 volatile index_to_read;
int64_t volatile completion_index; i64 volatile completion_index;
int64_t volatile completion_goal; i64 volatile completion_goal;
void *semaphore; void *semaphore;
}; };
typedef struct thread_startup_info_t thread_startup_info_t; typedef struct thread_startup_info_t thread_startup_info_t;
struct thread_startup_info_t { struct thread_startup_info_t {
uint32_t thread_id; u32 thread_id;
int32_t thread_index; i32 thread_index;
work_queue_t *queue; work_queue_t *queue;
}; };
fn int64_t atomic_increment(volatile int64_t *i) { fn i64 atomic_increment(volatile i64 *i) {
return InterlockedIncrement64(i); return InterlockedIncrement64(i);
} }
fn int64_t atomic_compare_and_swap(volatile int64_t *dst, int64_t exchange, int64_t comperand) { fn i64 atomic_compare_and_swap(volatile i64 *dst, i64 exchange, i64 comperand) {
return InterlockedCompareExchange64(dst, exchange, comperand); return InterlockedCompareExchange64(dst, exchange, comperand);
} }
fn void work_queue_push(work_queue_t *wq, void *data, work_queue_callback_t *callback) { fn void work_queue_push(work_queue_t *wq, void *data, work_queue_callback_t *callback) {
uint32_t new_index = (wq->index_to_write + 1) % lengthof(wq->entries); u32 new_index = (wq->index_to_write + 1) % lengthof(wq->entries);
assert(new_index != wq->index_to_read); assert(new_index != wq->index_to_read);
work_queue_entry_t *entry = wq->entries + wq->index_to_write; work_queue_entry_t *entry = wq->entries + wq->index_to_write;
@@ -146,10 +51,10 @@ fn void work_queue_push(work_queue_t *wq, void *data, work_queue_callback_t *cal
fn b8 work_queue_try_doing_work(work_queue_t *wq) { fn b8 work_queue_try_doing_work(work_queue_t *wq) {
b8 should_sleep = false; b8 should_sleep = false;
int64_t original_index_to_read = wq->index_to_read; i64 original_index_to_read = wq->index_to_read;
int64_t new_index_to_read = (original_index_to_read + 1) % lengthof(wq->entries); i64 new_index_to_read = (original_index_to_read + 1) % lengthof(wq->entries);
if (original_index_to_read != wq->index_to_write) { if (original_index_to_read != wq->index_to_write) {
int64_t index = atomic_compare_and_swap(&wq->index_to_read, new_index_to_read, original_index_to_read); i64 index = atomic_compare_and_swap(&wq->index_to_read, new_index_to_read, original_index_to_read);
if (index == original_index_to_read) { if (index == original_index_to_read) {
work_queue_entry_t *entry = wq->entries + index; work_queue_entry_t *entry = wq->entries + index;
entry->callback(entry->data); entry->callback(entry->data);
@@ -164,7 +69,8 @@ fn b8 work_queue_try_doing_work(work_queue_t *wq) {
fn DWORD WINAPI work_queue_thread_entry(LPVOID param) { fn DWORD WINAPI work_queue_thread_entry(LPVOID param) {
thread_startup_info_t *ti = (thread_startup_info_t *)param; thread_startup_info_t *ti = (thread_startup_info_t *)param;
os_core_init(); tcx = &global_thread_context;
os_win32_register_crash_handler();
tcx->thread_index = ti->thread_index; tcx->thread_index = ti->thread_index;
for (;;) { for (;;) {
if (work_queue_try_doing_work(ti->queue)) { if (work_queue_try_doing_work(ti->queue)) {
@@ -173,7 +79,7 @@ fn DWORD WINAPI work_queue_thread_entry(LPVOID param) {
} }
} }
fn void work_queue_init(work_queue_t *queue, uint32_t thread_count, thread_startup_info_t *info) { fn void work_queue_init(work_queue_t *queue, thread_startup_info_t *info, u32 thread_count) {
queue->thread_count = thread_count; queue->thread_count = thread_count;
queue->index_to_read = 0; queue->index_to_read = 0;
queue->index_to_write = 0; queue->index_to_write = 0;
@@ -182,7 +88,7 @@ fn void work_queue_init(work_queue_t *queue, uint32_t thread_count, thread_start
queue->semaphore = CreateSemaphoreExA(0, 0, thread_count, 0, 0, SEMAPHORE_ALL_ACCESS); queue->semaphore = CreateSemaphoreExA(0, 0, thread_count, 0, 0, SEMAPHORE_ALL_ACCESS);
assert(queue->semaphore != INVALID_HANDLE_VALUE); assert(queue->semaphore != INVALID_HANDLE_VALUE);
for (uint32_t i = 0; i < thread_count; i++) { for (u32 i = 0; i < thread_count; i++) {
thread_startup_info_t *ti = info + i; thread_startup_info_t *ti = info + i;
ti->thread_index = i; ti->thread_index = i;
ti->queue = queue; ti->queue = queue;
@@ -206,6 +112,52 @@ fn void work_queue_wait(work_queue_t *wq) {
} }
} }
///////////////////////////////
// process
typedef struct process_t process_t;
struct process_t {
b8 is_valid;
char platform[32];
};
fn process_t process_run(s8_t args) {
ma_temp_t scratch = ma_begin_scratch();
wchar_t *application_name = NULL;
wchar_t *cmd = s16_from_s8(scratch.arena, args).str;
BOOL inherit_handles = FALSE;
DWORD creation_flags = 0;
void *enviroment = NULL;
wchar_t *working_dir = NULL;
STARTUPINFOW startup_info = {};
startup_info.cb = sizeof(STARTUPINFOW);
process_t result = {};
assert(sizeof(result.platform) >= sizeof(PROCESS_INFORMATION));
PROCESS_INFORMATION *process_info = (PROCESS_INFORMATION *)result.platform;
BOOL success = CreateProcessW(application_name, cmd, NULL, NULL, inherit_handles, creation_flags, enviroment, working_dir, &startup_info, process_info);
result.is_valid = true;
if (!success) {
result.is_valid = false;
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
char *buff = (char *)lpMsgBuf;
size_t buffLen = strlen((const char *)buff);
if (buffLen > 0 && buff[buffLen - 1] == '\n') {
buff[buffLen - 1] = 0;
}
debugf("failed to create process | message: %s | cmd: %s", (char *)lpMsgBuf, args);
LocalFree(lpMsgBuf);
}
ma_end_scratch(scratch);
return result;
}
/////////////////////////////// ///////////////////////////////
// mutex // mutex
@@ -244,18 +196,13 @@ fn void mutex_unlock(mutex_t mutex) {
typedef struct srt_entry_t srt_entry_t; typedef struct srt_entry_t srt_entry_t;
struct srt_entry_t { struct srt_entry_t {
srt_entry_t *next;
u16 hour, minute, second; u16 hour, minute, second;
s8_t string; s8_t string;
s8_t filepath;
}; };
typedef array(srt_entry_t) array_srt_entry_t;
typedef struct srt_result_t srt_result_t; fn array_srt_entry_t srt_parse(ma_arena_t *arena, s8_t filename) {
struct srt_result_t {
srt_entry_t *first;
srt_entry_t *last;
};
srt_result_t srt_parse(ma_arena_t *arena, s8_t filename) {
s8_t content = os_read(arena, filename); s8_t content = os_read(arena, filename);
sb8_t lines = s8_split(arena, content, s8("\n"), s8_split_cleanup); sb8_t lines = s8_split(arena, content, s8("\n"), s8_split_cleanup);
@@ -269,7 +216,8 @@ srt_result_t srt_parse(ma_arena_t *arena, s8_t filename) {
// time interval // time interval
// text // text
int section_number = 1; int section_number = 1;
srt_result_t result = {0}; array_srt_entry_t result = {0};
result.alo = malo(arena);
for (sb8_node_t *it = lines.first; it;) { for (sb8_node_t *it = lines.first; it;) {
// parse section number // parse section number
long num = strtol(it->str, NULL, 10); long num = strtol(it->str, NULL, 10);
@@ -282,17 +230,16 @@ srt_result_t srt_parse(ma_arena_t *arena, s8_t filename) {
entry.hour = (u16)strtol(it->str, NULL, 10); entry.hour = (u16)strtol(it->str, NULL, 10);
entry.minute = (u16)strtol(it->str + 3, NULL, 10); entry.minute = (u16)strtol(it->str + 3, NULL, 10);
entry.second = (u16)strtol(it->str + 6, NULL, 10); entry.second = (u16)strtol(it->str + 6, NULL, 10);
entry.filepath = filename;
it = it->next; it = it->next;
// parse text // parse text
s8_t next_section_number = s8_printf(arena, "%d", section_number); s8_t next_section_number = s8_printf(arena, "%d", section_number);
while (it && !s8_are_equal(next_section_number, it->string)) { while (it && !s8_are_equal(next_section_number, it->string)) {
b8 duplicate = result.last && s8_are_equal(it->string, result.last->string); b8 duplicate = result.len && s8_are_equal(it->string, array_last(&result).string);
if (!duplicate) { if (!duplicate) {
srt_entry_t *entry_copy = ma_push_type(arena, srt_entry_t); entry.string = it->string;
entry_copy[0] = entry; array_add(&result, entry);
entry_copy->string = it->string;
SLLQ_APPEND(result.first, result.last, entry_copy);
} }
it = it->next; it = it->next;
} }
@@ -301,14 +248,301 @@ srt_result_t srt_parse(ma_arena_t *arena, s8_t filename) {
return result; return result;
} }
fn_test void testing_out_things(void) { ///////////////////////////////
mutex_t mutex = mutex_create(); // multithreaded resource loading
mutex_lock(mutex);
mutex_unlock(mutex);
mutex_destroy(mutex);
typedef array(s8_t) array_s8_t;
typedef struct res_parse_work_t res_parse_work_t;
typedef struct res_t res_t;
struct res_parse_work_t {
s8_t srt;
res_t *res;
};
struct res_t {
ma_arena_t misc_arena;
array_s8_t error_messages;
mutex_t mutex;
ma_arena_t text_arena;
ma_arena_t node_arena;
array_srt_entry_t mapping_entries;
thread_startup_info_t thread_info[16];
work_queue_t work_queue;
mutex_t log_mutex;
i64 search_stop_counter;
mutex_t search_mutex;
array_s8_t search_matches;
ui_text_input_t search_text_input;
char search_prompt[256];
};
#define res_report(res, ...) (mutex_lock((res)->log_mutex), array_add(&(res)->error_messages, s8_printf(&(res)->misc_arena, __VA_ARGS__)), mutex_unlock((res)->log_mutex))
fn void res_init(res_t *res) {
work_queue_init(&res->work_queue, res->thread_info, lengthof(res->thread_info));
ma_init(&res->text_arena, ma_default_reserve_size);
ma_init(&res->node_arena, ma_default_reserve_size);
ma_init(&res->misc_arena, ma_default_reserve_size);
res->log_mutex = mutex_create();
res->mutex = mutex_create();
res->search_mutex = mutex_create();
array_init(malo(&res->misc_arena), &res->search_matches, 10000000);
array_init(malo(&res->misc_arena), &res->error_messages, 10000);
res->text_arena.align = 0;
}
fn s8_t srt_find_matching_video(s8_t filename) {
s8_t no_srt = s8_chop_last_period(filename);
if (s8_ends_with(no_srt, s8(".en"))) {
no_srt = s8_chop_last_period(no_srt);
}
char *ext[] = {"mp4", "webm", "mkv"};
for (i32 i = 0; i < lengthof(ext); i += 1) {
s8_t video = s8_printf(tcx->temp, "%S.%s", no_srt, ext[i]);
if (os_exists(video)) {
return video;
}
}
return s8_null;
}
FN_WORK(res_parse_thread) {
res_parse_work_t *pair = (res_parse_work_t *)data;
ma_temp_t scratch = ma_begin_scratch(); ma_temp_t scratch = ma_begin_scratch();
srt_result_t result = srt_parse(scratch.arena, s8("D:\\videos\\zizek\\Hegel and the Spirit of Distrust Sven-Olov Wallenstein & Slavoj Žižek in Conversation [a7gEN-Rxr4c].en.srt")); array_srt_entry_t srt = srt_parse(scratch.arena, pair->srt);
unused(result); mutex_lock(pair->res->mutex);
pair->res->mapping_entries.alo = malo(&pair->res->node_arena);
for (i64 i = 0; i < srt.len; i += 1) {
srt_entry_t it = srt.data[i];
it.filepath = s8_copy(&pair->res->misc_arena, it.filepath);
it.string = s8_copy(&pair->res->text_arena, it.string);
it.string.str[it.string.len] = ' ';
array_add(&pair->res->mapping_entries, it);
}
mutex_unlock(pair->res->mutex);
ma_end_scratch(scratch); ma_end_scratch(scratch);
if (srt.len) {
res_report(pair->res, "%S", pair->srt);
} else {
res_report(pair->res, "failed to load: %S", pair->srt);
}
}
fn void res_add_folder(res_t *res, s8_t folder) {
ma_temp_t scratch = ma_begin_scratch();
if (!os_is_dir(folder)) {
res_report(res, "failed to find folder: %S", folder);
}
for (os_iter_t *iter = os_iter(scratch.arena, folder); iter->is_valid; os_advance(iter)) {
if (iter->is_directory) {
continue;
}
if (!s8_ends_with(iter->abs, s8(".srt"))) {
continue;
}
// @leak
res_parse_work_t *work = ma_push_type(&res->misc_arena, res_parse_work_t);
work->srt = iter->abs;
work->res = res;
work_queue_push(&res->work_queue, work, res_parse_thread);
}
ma_end_scratch(scratch);
}
fn srt_entry_t *res_find_entry(res_t *res, s8_t res_string) {
srt_entry_t *result = NULL;
mutex_lock(res->mutex);
for (srt_entry_t *it = res->mapping_entries.data; it != res->mapping_entries.data + res->mapping_entries.len; it += 1) {
u64 begin = (u64)(it->string.str);
u64 end = (u64)(it->string.str + it->string.len);
u64 needle = (u64)(res_string.str);
if (needle >= begin && needle < end) {
result = it;
break;
}
}
mutex_unlock(res->mutex);
return result;
}
fn s8_t res_get_string(res_t *res) {
mutex_lock(res->mutex);
s8_t result = (s8_t){res->text_arena.data, res->text_arena.len};
mutex_unlock(res->mutex);
return result;
}
///////////////////////////////
// search thread
FN_WORK(res_search_thread) {
res_t *res = (res_t *)data;
if (res->search_text_input.len == 0) {
return;
}
i64 search_stop_counter = res->search_stop_counter;
mutex_lock(res->search_mutex);
res->search_matches.len = 0;
mutex_unlock(res->search_mutex);
s8_t buffer = res_get_string(res);
s8_t find = res->search_text_input.string;
i64 index = 0;
while (s8_seek(buffer, find, s8_seek_ignore_case, &index)) {
if (search_stop_counter != res->search_stop_counter) {
break;
}
assert(res->search_matches.cap == 10000000);
s8_t found = s8_make(buffer.str + index, find.len);
array_add(&res->search_matches, found);
buffer = s8_skip(buffer, index + find.len);
}
}
fn void res_search_start(res_t *res) {
res->search_stop_counter += 1;
work_queue_push(&res->work_queue, res, res_search_thread);
}
fn array_s8_t res_search_get(ma_arena_t *arena, res_t *res) {
mutex_lock(res->search_mutex);
array_s8_t copy = res->search_matches;
copy.data = ma_push_array_copy(arena, copy.data, copy.len);
mutex_unlock(res->search_mutex);
return copy;
}
res_t *global_res = NULL;
// @todo @core make temp arena normal pointer in ctx, dont init on win32 etc.
// @todo @core make it sure that not too much arenas are initialized for threads
// @todo @ui scrolling when focused button goes out of screen
fn_test void testing_out_things(void) {
}
///////////////////////////////
// update
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 = global_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;
ui_signal_t text_input_sig;
res->search_text_input.str = res->search_prompt;
res->search_text_input.cap = lengthof(res->search_prompt);
ui_box_t *top_box = ui_box(.null_id = true, .rect = r2f32_cut_top(ui_top_rectp(), ui_em(1.0f)), .flags = {.draw_rect = true, .clip_rect = true});
ui_set_text_align(ui_text_align_left)
ui_set_lop(ui_lop_cut_top)
ui_set_top(top_box) {
text_input_sig = ui_text_input(&res->search_text_input, .sim_even_if_no_focus = true, .keyboard_nav = false);
}
ma_temp_t scratch = ma_begin_scratch();
array_s8_t matches = res_search_get(scratch.arena, res);
if (matches.len == 0 || res->search_text_input.len == 0) {
// @concurrency not sure if this is ok
matches = res->error_messages;
}
ui_box_t *lister = ui_box(.null_id = true, .rect = r2f32_cut_top(ui_top_rectp(), ui_max), .flags = {.draw_rect = true, .clip_rect = true});
locl f32 verti_scroller_value;
ui_scroller_t scroller = ui_begin_scroller(UILOC, (ui_scroller_params_t){
.parent = lister,
.verti = {
.enabled = true,
.value = &verti_scroller_value,
.item_pixels = ui_em(1),
.item_count = (i32)matches.len,
},
});
ui_set_top(lister)
ui_set_string_pos_offset(ui_dm(1)) {
ui_cut_top_scroller_offset(scroller);
for (i32 i = scroller.verti.istart; i < scroller.verti.iend; i += 1) {
if (i >= matches.len) {
break;
}
ui_push_id((ui_id_t){i});
s8_t it = matches.data[i];
uintptr_t begin_region = (uintptr_t)res->text_arena.data;
uintptr_t end_region = (uintptr_t)res->text_arena.data + res->text_arena.len;
u64 chars_per_line = (i64)(frame->window_size.x) / (i64)ui_dm(1) - strlen(res->search_prompt) * 2;
u64 begin = (uintptr_t)(it.str - chars_per_line / 2);
u64 end = (uintptr_t)(it.str + it.len + chars_per_line / 2);
u64 a = CLAMP(begin, begin_region, end_region);
u64 b = CLAMP((uintptr_t)it.str, begin_region, end_region);
s8_t left = {(char *)a, (i64)(b - a)};
u64 c = CLAMP((uintptr_t)(it.str + it.len), begin_region, end_region);
u64 d = CLAMP(end, begin_region, end_region);
s8_t right = {(char *)c, (i64)(d - c)};
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_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);
s8_t video = srt_find_matching_video(entry->filepath);
if (video.len == 0) {
res_report(res, "failed to find video for: %S", entry->filepath);
} else {
int seconds = entry->hour * 60 * 60 + entry->minute * 60 + entry->second;
for (i64 i = 0; i < video.len; i += 1) {
if (video.str[i] == '/') video.str[i] = '\\';
}
s8_t cmd = s8_printf(scratch.arena, "\"C:/Program Files/VideoLAN/VLC/vlc.exe\" --start-time %d \"%S\"", seconds, video);
debugf("%S\n", cmd);
process_run(cmd);
}
}
if (set_focus) {
ui->focus = box->id;
set_focus = false;
}
ui_pop_id();
}
}
if (text_input_sig.text_changed) {
res_search_start(res);
set_focus = true;
}
assert(res->error_messages.cap == 10000);
assert(res->search_matches.cap == 10000000);
ui_end_scroller(scroller);
ui_end_build();
ma_end_scratch(scratch);
}
rn_begin(white_color);
ui_draw();
rn_end();
ui_end_frame();
} }