Phase C: Split process_clicks into 6 focused sub-functions

handle_nav_clicks — sidebar + pipeline stepper navigation
handle_field_clicks — input field focus detection
handle_format_clicks — PDF/PNG/CBZ + Local/DS toggle
handle_action_clicks — all pipeline/file/log action buttons
handle_workspace_nav — prev/next for Script/Panels/Layout/Bubbles
handle_detail_clicks — panel regen, layout regen, bubble editor ops

process_clicks: 269 → 30 lines (orchestration only)
runtime.odin: 806 → 806 lines (delegation replaced inline logic)
All 156 tests pass.
This commit is contained in:
echo 2026-05-22 17:41:09 +02:00
parent be0ccc1539
commit 8b044e3ac1

View File

@ -349,33 +349,31 @@ run_gui_app :: proc(state: ^core.Comic_State) -> shared.App_Error {
return shared.ok() return shared.ok()
} }
// Click Processing // Click Handlers (called by process_clicks)
process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_export, proj_ok, export_ok, has_deepseek: bool, pages_count: int, shift_down: bool, autosave_secs: int, compact_mode: bool) {
// Navigation handle_nav_clicks :: proc(app: ^GUI_App_State) {
nav_screen := []ui.App_Screen{.Story, .Script, .Characters, .Panels, .Layout, .Bubbles, .Export, .Community} nav_screen := []ui.App_Screen{.Story, .Script, .Characters, .Panels, .Layout, .Bubbles, .Export, .Community}
for i in 0 ..< len(nav_screen) { for i in 0 ..< len(nav_screen) {
if clicked(clay.ID("Nav", u32(i))) { if clicked(clay.ID("Nav", u32(i))) {
push_status(&app.status_msg, &app.action_log, navigate_screen_with_status(&app.controller, nav_screen[i])) push_status(&app.status_msg, &app.action_log, navigate_screen_with_status(&app.controller, nav_screen[i]))
} }
} }
// Pipeline stepper clicks navigate to corresponding screen
pipeline_screens := []ui.App_Screen{.Script, .Panels, .Layout, .Export} pipeline_screens := []ui.App_Screen{.Script, .Panels, .Layout, .Export}
for i in 0 ..< len(pipeline_screens) { for i in 0 ..< len(pipeline_screens) {
if clicked(clay.ID("PStep", u32(i))) { if clicked(clay.ID("PStep", u32(i))) {
push_status(&app.status_msg, &app.action_log, navigate_screen_with_status(&app.controller, pipeline_screens[i])) push_status(&app.status_msg, &app.action_log, navigate_screen_with_status(&app.controller, pipeline_screens[i]))
} }
} }
}
// Input field focus handle_field_clicks :: proc(app: ^GUI_App_State) {
input_ids := []string{"field_idea", "field_genre", "field_audience", "field_export", "field_pages", "field_project"} input_ids := []string{"field_idea", "field_genre", "field_audience", "field_export", "field_pages", "field_project"}
for i in 0 ..< len(input_ids) { for i in 0 ..< len(input_ids) {
if clicked(clay.ID(input_ids[i])) { if clicked(clay.ID(input_ids[i])) { app.selected_field = i }
app.selected_field = i
}
} }
}
// Format buttons handle_format_clicks :: proc(app: ^GUI_App_State, has_deepseek: bool) {
if clicked(clay.ID("btn_pdf")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .PDF, &app.is_dirty)) } if clicked(clay.ID("btn_pdf")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .PDF, &app.is_dirty)) }
if clicked(clay.ID("btn_png")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .PNG, &app.is_dirty)) } if clicked(clay.ID("btn_png")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .PNG, &app.is_dirty)) }
if clicked(clay.ID("btn_cbz")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .CBZ, &app.is_dirty)) } if clicked(clay.ID("btn_cbz")) { push_status(&app.status_msg, &app.action_log, set_export_format_with_message(&app.export_format, &app.export_path, .CBZ, &app.is_dirty)) }
@ -384,8 +382,9 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
if !has_deepseek { push_status(&app.status_msg, &app.action_log, "DeepSeek key missing") } if !has_deepseek { push_status(&app.status_msg, &app.action_log, "DeepSeek key missing") }
else { app.use_deepseek_script = true; push_status(&app.status_msg, &app.action_log, "Script source: DeepSeek") } else { app.use_deepseek_script = true; push_status(&app.status_msg, &app.action_log, "Script source: DeepSeek") }
} }
}
// Action buttons handle_action_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_export: bool, pages_count: int, shift_down: bool, proj_ok, export_ok: bool, autosave_secs: int) {
if clicked(clay.ID("btn_new")) { if clicked(clay.ID("btn_new")) {
if app.is_dirty && !shift_down { push_status(&app.status_msg, &app.action_log, request_confirmation(&app.show_confirm_overlay, &app.show_help_overlay, &app.pending_confirm, .Reset_Project, "Confirm reset?")) } if app.is_dirty && !shift_down { push_status(&app.status_msg, &app.action_log, request_confirmation(&app.show_confirm_overlay, &app.show_help_overlay, &app.pending_confirm, .Reset_Project, "Confirm reset?")) }
else { push_status(&app.status_msg, &app.action_log, reset_project_session(&app.controller, &app.is_dirty, &app.last_autosave_at, false)) } else { push_status(&app.status_msg, &app.action_log, reset_project_session(&app.controller, &app.is_dirty, &app.last_autosave_at, false)) }
@ -405,7 +404,6 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
if clicked(clay.ID("btn_next")) { push_status(&app.status_msg, &app.action_log, run_next_action(&app.controller, &app.export_path, app.export_format, pages_count, app.use_deepseek_script, &app.is_dirty, &app.last_export_at)) } if clicked(clay.ID("btn_next")) { push_status(&app.status_msg, &app.action_log, run_next_action(&app.controller, &app.export_path, app.export_format, pages_count, app.use_deepseek_script, &app.is_dirty, &app.last_export_at)) }
if clicked(clay.ID("btn_auto")) { push_status(&app.status_msg, &app.action_log, run_auto_all_action(&app.controller, &app.export_path, app.export_format, pages_count, app.use_deepseek_script, &app.is_dirty, &app.last_export_at)) } if clicked(clay.ID("btn_auto")) { push_status(&app.status_msg, &app.action_log, run_auto_all_action(&app.controller, &app.export_path, app.export_format, pages_count, app.use_deepseek_script, &app.is_dirty, &app.last_export_at)) }
if clicked(clay.ID("btn_autosave")) { push_status(&app.status_msg, &app.action_log, run_auto_all_save_action(&app.controller, &app.project_path, &app.export_path, app.export_format, pages_count, app.use_deepseek_script, &app.is_dirty, &app.last_export_at, &app.last_autosave_at, &app.last_save_at)) }
if clicked(clay.ID("btn_save")) { push_status(&app.status_msg, &app.action_log, save_project_session_with_message(&app.project_path, app.controller.state, &app.is_dirty, &app.last_autosave_at, &app.last_save_at, "Saved project")) } if clicked(clay.ID("btn_save")) { push_status(&app.status_msg, &app.action_log, save_project_session_with_message(&app.project_path, app.controller.state, &app.is_dirty, &app.last_autosave_at, &app.last_save_at, "Saved project")) }
if clicked(clay.ID("btn_open")) { if clicked(clay.ID("btn_open")) {
if app.is_dirty && !shift_down { push_status(&app.status_msg, &app.action_log, request_confirmation(&app.show_confirm_overlay, &app.show_help_overlay, &app.pending_confirm, .Open_Project, "Confirm open?")) } if app.is_dirty && !shift_down { push_status(&app.status_msg, &app.action_log, request_confirmation(&app.show_confirm_overlay, &app.show_help_overlay, &app.pending_confirm, .Open_Project, "Confirm open?")) }
@ -422,10 +420,10 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
if clicked(clay.ID("btn_log_diag")) { push_status(&app.status_msg, &app.action_log, write_diagnostics_with_message(diag_ctx)) } if clicked(clay.ID("btn_log_diag")) { push_status(&app.status_msg, &app.action_log, write_diagnostics_with_message(diag_ctx)) }
if clicked(clay.ID("btn_log_status_copy")) { push_status(&app.status_msg, &app.action_log, copy_text_with_status(app.status_msg, "Copied status to clipboard")) } if clicked(clay.ID("btn_log_status_copy")) { push_status(&app.status_msg, &app.action_log, copy_text_with_status(app.status_msg, "Copied status to clipboard")) }
if clicked(clay.ID("btn_log_diag_copy")) { push_status(&app.status_msg, &app.action_log, copy_diagnostics_with_message(diag_ctx)) } if clicked(clay.ID("btn_log_diag_copy")) { push_status(&app.status_msg, &app.action_log, copy_diagnostics_with_message(diag_ctx)) }
}
handle_workspace_nav :: proc(app: ^GUI_App_State) {
screen := app.controller.active_screen screen := app.controller.active_screen
// Workspace navigation buttons
if screen == .Script { if screen == .Script {
page_count := len(app.controller.state.script.pages) page_count := len(app.controller.state.script.pages)
if clicked(clay.ID("btn_script_prev")) && page_count > 0 { if clicked(clay.ID("btn_script_prev")) && page_count > 0 {
@ -489,18 +487,18 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
} }
} }
}
handle_detail_clicks :: proc(app: ^GUI_App_State) {
screen := app.controller.active_screen
// Panels detail buttons
screen = app.controller.active_screen
if screen == .Panels { if screen == .Panels {
if clicked(clay.ID("btn_panel_regenerate")) { if clicked(clay.ID("btn_panel_regenerate")) {
panel_count := count_script_panels(app.controller.state.script) panel_count := count_script_panels(app.controller.state.script)
if panel_count > 0 { if panel_count > 0 {
idx := clamp_panel_cursor(panel_count, app.summary_opts.panel_cursor) idx := clamp_panel_cursor(panel_count, app.summary_opts.panel_cursor)
panel, _, ok := panel_by_flat_index(app.controller.state.script, idx) panel, _, ok := panel_by_flat_index(app.controller.state.script, idx)
if ok { if ok { push_status(&app.status_msg, &app.action_log, action_regenerate_panel(&app.controller, panel.panel_id)) }
push_status(&app.status_msg, &app.action_log, action_regenerate_panel(&app.controller, panel.panel_id))
}
} }
} }
panel_count := count_script_panels(app.controller.state.script) panel_count := count_script_panels(app.controller.state.script)
@ -514,7 +512,6 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
} }
// Layout detail buttons
if screen == .Layout { if screen == .Layout {
if clicked(clay.ID("btn_layout_regen")) { if clicked(clay.ID("btn_layout_regen")) {
push_status(&app.status_msg, &app.action_log, action_regenerate_page_layout(&app.controller, app.summary_opts.layout_page_cursor)) push_status(&app.status_msg, &app.action_log, action_regenerate_page_layout(&app.controller, app.summary_opts.layout_page_cursor))
@ -529,7 +526,6 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
} }
// Bubbles detail buttons
if screen == .Bubbles { if screen == .Bubbles {
layout_count := len(app.controller.state.page_layouts) layout_count := len(app.controller.state.page_layouts)
if layout_count > 0 { if layout_count > 0 {
@ -554,8 +550,6 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
} }
} }
// Bubble row clicks
if layout_count > 0 { if layout_count > 0 {
page_idx := clamp_layout_cursor(layout_count, app.summary_opts.bubble_page_cursor) page_idx := clamp_layout_cursor(layout_count, app.summary_opts.bubble_page_cursor)
layout_val := app.controller.state.page_layouts[page_idx] layout_val := app.controller.state.page_layouts[page_idx]
@ -570,13 +564,9 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
if clicked(clay.ID(fmt.tprintf("btn_bubble_delete_%d", i))) { if clicked(clay.ID(fmt.tprintf("btn_bubble_delete_%d", i))) {
push_status(&app.status_msg, &app.action_log, action_delete_bubble(&app.controller, panel_id, i)) push_status(&app.status_msg, &app.action_log, action_delete_bubble(&app.controller, panel_id, i))
app.is_dirty = true app.is_dirty = true
if app.summary_opts.bubble_edit_cursor > 0 { if app.summary_opts.bubble_edit_cursor > 0 { app.summary_opts.bubble_edit_cursor -= 1 }
app.summary_opts.bubble_edit_cursor -= 1
}
} }
} }
// Type selector buttons
bubble_count := count_bubbles_for_panel(app.controller.state.speech_bubbles, panel_id) bubble_count := count_bubbles_for_panel(app.controller.state.speech_bubbles, panel_id)
if bubble_count > 0 && app.summary_opts.bubble_edit_cursor < bubble_count { if bubble_count > 0 && app.summary_opts.bubble_edit_cursor < bubble_count {
types := []core.Bubble_Type{.Normal, .Thought, .Shout, .Whisper, .Narration, .Sound_Effect} types := []core.Bubble_Type{.Normal, .Thought, .Shout, .Whisper, .Narration, .Sound_Effect}
@ -591,21 +581,37 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
} }
} }
} }
}
process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_export, proj_ok, export_ok, has_deepseek: bool, pages_count: int, shift_down: bool, autosave_secs: int, compact_mode: bool) {
handle_nav_clicks(app)
handle_field_clicks(app)
handle_format_clicks(app, has_deepseek)
handle_action_clicks(app, can_gen_panels, can_layout, can_export, pages_count, shift_down, proj_ok, export_ok, autosave_secs)
handle_workspace_nav(app)
handle_detail_clicks(app)
// Confirm overlay buttons // Overlay clicks
if clicked(clay.ID("confirm_yes")) && app.show_confirm_overlay { if app.show_confirm_overlay {
action := app.pending_confirm confirm_yes := rl.IsKeyPressed(.ENTER) || rl.IsKeyPressed(.Y) || clicked(clay.ID("confirm_yes"))
app.show_confirm_overlay = false confirm_no := rl.IsKeyPressed(.ESCAPE) || rl.IsKeyPressed(.N) || clicked(clay.ID("confirm_no"))
app.pending_confirm = .None if confirm_no {
push_status(&app.status_msg, &app.action_log, resolve_confirm_action_with_message(action, &app.controller, &app.project_path, &app.export_path, app.export_format, &app.is_dirty, &app.last_autosave_at)) app.show_confirm_overlay = false
app.pending_confirm = .None
push_status(&app.status_msg, &app.action_log, "Cancelled destructive action")
} else if confirm_yes {
app.show_confirm_overlay = false
msg := resolve_confirm_action_with_message(app.pending_confirm, &app.controller, &app.project_path, &app.export_path, app.export_format, &app.is_dirty, &app.last_autosave_at)
push_status(&app.status_msg, &app.action_log, msg)
app.pending_confirm = .None
}
} }
if clicked(clay.ID("confirm_no")) && app.show_confirm_overlay { if app.show_help_overlay {
app.show_confirm_overlay = false if rl.IsKeyPressed(.ESCAPE) || rl.IsKeyPressed(.SLASH) {
app.pending_confirm = .None app.show_help_overlay = false
push_status(&app.status_msg, &app.action_log, "Cancelled destructive action") push_status(&app.status_msg, &app.action_log, "Closed help overlay")
}
} }
} }
// Clay UI Primitives // Clay UI Primitives
declare_nav_chip :: proc(id: string, label: string, active: bool) { declare_nav_chip :: proc(id: string, label: string, active: bool) {
bg: clay.Color = clay.Color{0, 0, 0, 0} bg: clay.Color = clay.Color{0, 0, 0, 0}