From 413ef78504278d37080b9b59a652e4bbd5e2a0fc Mon Sep 17 00:00:00 2001 From: kasull Date: Sun, 1 Mar 2026 20:18:08 -0500 Subject: editor ui rework add responsive editor relayout for window resize rework header with back/save, view mode buttons, and 2d view type selector add clipped, scrollable assets and contextual tool panels simplify tool button wiring and repeated layout logic simplify 2d top-down editor internals and shared grid update hooks --- src/editor/editor.cpp | 30 ++ src/editor/editor.h | 33 +- src/editor/gui.cpp | 913 ++++++++++++++++++++++++++++++++++++++++++++-- src/editor/properties.cpp | 122 +++++-- src/editor/toolview.cpp | 103 +++++- src/editor/view2d.cpp | 288 +++++++++------ src/game.cpp | 58 ++- src/gui/button.cpp | 23 +- src/gui/floatinput.cpp | 88 ++++- src/render/gl.cpp | 237 +++++++++--- src/render/gl.h | 9 + src/render/gl_3d.cpp | 13 +- 12 files changed, 1670 insertions(+), 247 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index ad23d1e..3c67ecd 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -4,6 +4,36 @@ GAME_EDITOR* editor = 0; +void editor_register_grid_dependency( GAME_EDITOR* e, void* tag, EDITOR_GRID_DEP_CALLBACK cb ) { + if( !e || !tag || !cb ) + return; + + I32 idx = e->grid_dependencies.idx_where( fn( GAME_EDITOR::GRID_DEPENDENCY* dep ) { + return dep->tag == tag; + } ); + + if( idx != -1 ) { + e->grid_dependencies[idx].cb = cb; + return; + } + + GAME_EDITOR::GRID_DEPENDENCY dep; + dep.tag = tag; + dep.cb = cb; + e->grid_dependencies.push( dep ); +} + +void editor_notify_grid_change( GAME_EDITOR* e ) { + if( !e ) + return; + + e->grid_dependencies.each( fn( GAME_EDITOR::GRID_DEPENDENCY* dep ) { + if( dep->cb ) { + dep->cb( e ); + } + } ); +} + GAME_EDITOR* editor_create( GAME_DATA* game ) { if( editor ) { dlog( "editor_create() : attempted to create editor when one already exists\n" ); diff --git a/src/editor/editor.h b/src/editor/editor.h index cd42a4e..0fee667 100644 --- a/src/editor/editor.h +++ b/src/editor/editor.h @@ -8,7 +8,6 @@ const F32 EDITOR_DEFAULT_POLY_SIDES = 6.f; const F32 EDITOR_DEFAULT_WALL_HEIGHT = 80.f; const F32 EDITOR_DEFAULT_PLACEMENT_HEIGHT = 0.f; const I32 EDITOR_POLY_SIDES_MIN = 3; -const I32 EDITOR_POLY_SIDES_MAX = 32; enum EditorTools_t { EDITOR_TOOL_NONE, @@ -36,6 +35,9 @@ enum EditorSelectType_t { EDITOR_SELECT_SURFPROPS }; +struct GAME_EDITOR; +typedef void( *EDITOR_GRID_DEP_CALLBACK )( GAME_EDITOR* e ); + struct GAME_EDITOR_TOOL { U8 type; @@ -68,6 +70,24 @@ struct GAME_EDITOR { struct GUI_EDITOR_PROPVIEW* props; struct GUI_EDITOR_TOOLVIEW* tool; I32 map_select{}; + + I32 props_w{300}; + I32 props_h{300}; + I32 view_h{370}; + + GUI_VIEW* assets{}; + GUI_VIEW* status{}; + I32 assets_scroll{}; + I32 view_mode{}; + I32 view2d_type{}; + + GUI_LABEL* header_viewtype_label{}; + GUI_BUTTON* header_back{}; + GUI_BUTTON* header_save{}; + GUI_BUTTON* header_mode_2d{}; + GUI_BUTTON* header_mode_3d{}; + GUI_BUTTON* header_mode_sim{}; + GUI_BASE* header_viewtype{}; } gui; @@ -78,6 +98,12 @@ struct GAME_EDITOR { F32 spritesize{}; U8 drawbsp{}; + struct GRID_DEPENDENCY { + void* tag{}; + EDITOR_GRID_DEP_CALLBACK cb{}; + }; + LIST grid_dependencies{}; + LIST map_list{}; }; @@ -87,6 +113,9 @@ extern STAT editor_load_map( GAME_EDITOR* e, const char* mapname ); extern STAT editor_save_map( GAME_EDITOR* e ); extern STAT editor_new_map( GAME_EDITOR* e, const char* mapname ); extern STAT editor_close( GAME_EDITOR* e ); +extern void editor_resize( GAME_EDITOR* e, I32 w, I32 h ); +extern void editor_register_grid_dependency( GAME_EDITOR* e, void* tag, EDITOR_GRID_DEP_CALLBACK cb ); +extern void editor_notify_grid_change( GAME_EDITOR* e ); extern LIST* editor_get_map_list( GAME_EDITOR* e ); @@ -136,6 +165,8 @@ struct GUI_EDITOR_PROPVIEW : GUI_VIEW { struct GUI_EDITOR_TOOLVIEW : GUI_VIEW { GUI_VIEW* itemview; U8 wallshape_dropdown_open; + I32 scroll; + I32 content_h; }; struct GUI_EDITOR_TEXTUREPICKER : GUI_WINDOW { diff --git a/src/editor/gui.cpp b/src/editor/gui.cpp index c5e44df..92f3f7c 100644 --- a/src/editor/gui.cpp +++ b/src/editor/gui.cpp @@ -1,5 +1,798 @@ #include "editor.h" #include "../game/world/bsp.h" +#include "../game/assets.h" +#include "../game.h" +#include "../render/gl_2d.h" + +const I32 EDITOR_LAYOUT_MARGIN = 10; +const I32 EDITOR_LAYOUT_TITLE_OFFSET = 15; +const I32 EDITOR_LAYOUT_NAV_Y = 10; +const I32 EDITOR_LAYOUT_CONTENT_Y = 38; +const I32 EDITOR_LAYOUT_COLUMN_GAP = 10; +const I32 EDITOR_LAYOUT_VIEW_TOOL_GAP = 17; +const I32 EDITOR_LAYOUT_TOOL_BTN_TOP_GAP = 3; +const I32 EDITOR_LAYOUT_TOOL_PANEL_GAP = 5; +const I32 EDITOR_LAYOUT_LEFT_BOX_GAP = 10; +const I32 EDITOR_LAYOUT_STATUS_H = 22; +const I32 EDITOR_LAYOUT_STATUS_GAP = 6; +const I32 EDITOR_LAYOUT_TOOL_PANEL_W = 300; +const I32 EDITOR_LAYOUT_TOOL_PANEL_H = 150; +const I32 EDITOR_LAYOUT_PROPS_DEFAULT_W = 300; +const I32 EDITOR_LAYOUT_PROPS_DEFAULT_H = 300; +const I32 EDITOR_LAYOUT_VIEW_DEFAULT_H = 370; +const I32 EDITOR_LAYOUT_PROPS_MIN_W = 200; +const I32 EDITOR_LAYOUT_PROPS_MIN_H = 120; +const I32 EDITOR_LAYOUT_ASSETS_MIN_H = 120; +const I32 EDITOR_LAYOUT_VIEW_MIN_H = 140; +const I32 EDITOR_LAYOUT_RIGHT_MIN_W = 260; + +enum EditorInfoBoxType_t { + EDITOR_INFOBOX_ASSETS = 1, + EDITOR_INFOBOX_STATUS = 2 +}; + +struct GUI_EDITOR_INFOBOX : GUI_VIEW { + U8 type{}; +}; + +struct GUI_EDITOR_VIEWTYPE_SEGMENT : GUI_BASE { + U8 held{}; + I32 held_seg{-1}; +}; + +enum EditorViewMode_t { + EDITOR_VIEWMODE_2D = 0, + EDITOR_VIEWMODE_3D = 1, + EDITOR_VIEWMODE_SIM = 2 +}; + +enum Editor2DViewType_t { + EDITOR_2DVIEW_TOP_DOWN = 0, + EDITOR_2DVIEW_SIDE_ELEVATION = 1, + EDITOR_2DVIEW_FRONT_ELEVATION = 2 +}; + +static void editor_layout_map_view( GAME_EDITOR* e ); + +static void editor_apply_view_mode( GAME_EDITOR* e ) { + if( !e || !e->gui.v2d || !e->gui.v3d ) + return; + + U8 mode = e->gui.view_mode; + if( mode == EDITOR_VIEWMODE_2D ) { + e->gui.v2d->enabled = 1; + e->gui.v3d->enabled = 0; + return; + } + + e->gui.v2d->enabled = 0; + e->gui.v3d->enabled = 1; +} + +static void editor_header_back_cb( void* ) { + editor_close( editor ); +} + +static void editor_header_save_cb( void* ) { + editor_save_map( editor ); +} + +static void editor_set_view_mode( I32 mode ) { + editor->gui.view_mode = mode; + editor_apply_view_mode( editor ); + editor_layout_map_view( editor ); +} + +static void editor_header_mode_2d_cb( void* ) { + editor_set_view_mode( EDITOR_VIEWMODE_2D ); +} + +static void editor_header_mode_3d_cb( void* ) { + editor_set_view_mode( EDITOR_VIEWMODE_3D ); +} + +static void editor_header_mode_sim_cb( void* ) { + editor_set_view_mode( EDITOR_VIEWMODE_SIM ); +} + +static void editor_header_viewtype_segment_widths( I32 w, I32* w0, I32* w1, I32* w2 ) { + I32 base = max( 1, w / 3 ); + I32 rem = max( 0, w - base * 3 ); + *w0 = base + ( rem > 0 ? 1 : 0 ); + *w1 = base + ( rem > 1 ? 1 : 0 ); + *w2 = max( 1, w - *w0 - *w1 ); +} + +static I32 editor_header_viewtype_segment_index( GUI_EDITOR_VIEWTYPE_SEGMENT* seg, I32 mx, I32 my ) { + I32 x = gui_relx( seg ); + I32 y = gui_rely( seg ); + if( mx < x || mx >= x + seg->w || my < y || my >= y + seg->h ) + return -1; + + I32 w0 = 0, w1 = 0, w2 = 0; + editor_header_viewtype_segment_widths( seg->w, &w0, &w1, &w2 ); + I32 rel = mx - x; + if( rel < w0 ) return 0; + if( rel < w0 + w1 ) return 1; + return 2; +} + +static void gui_editor_viewtype_segment_draw_fn( void* ptr ) { + GUI_EDITOR_VIEWTYPE_SEGMENT* seg = (GUI_EDITOR_VIEWTYPE_SEGMENT*)ptr; + I32 x = gui_relx( seg ); + I32 y = gui_rely( seg ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + I32 hover_seg = editor_header_viewtype_segment_index( seg, mx, my ); + I32 active_seg = editor->gui.view2d_type; + U8 down = gui_mbutton_down( GUI_MBTNLEFT ); + + CLR border = gui_is_fg_window( seg ) ? ui_clr.border : ui_clr.border_inactive; + I32 inner_x = x + 1; + I32 inner_y = y + 1; + I32 inner_w = max( 1, seg->w - 2 ); + I32 inner_h = max( 1, seg->h - 2 ); + gui_draw_frect( inner_x, inner_y, inner_w, inner_h, ui_clr.bg_sec ); + + I32 w0 = 0, w1 = 0, w2 = 0; + editor_header_viewtype_segment_widths( inner_w, &w0, &w1, &w2 ); + I32 segx[3] = { inner_x, inner_x + w0, inner_x + w0 + w1 }; + I32 segw[3] = { w0, w1, w2 }; + const char* txts[3] = { "top down", "side", "front" }; + + for( I32 i = 0; i < 3; ++i ) { + U8 selected = i == active_seg; + U8 hover = hover_seg == i; + + CLR fill = ui_clr.bg_sec; + if( selected ) + fill = CLR::blend( ui_clr.bg_alt, ui_clr.border, 0.15f ); + else if( hover ) + fill = CLR::blend( ui_clr.bg_alt, ui_clr.bg_sec, 0.5f ); + + if( selected && down && hover ) + fill = CLR::blend( fill, ui_clr.bg_sec, 0.35f ); + + gui_draw_frect( segx[i], inner_y, max( 1, segw[i] ), inner_h, fill ); + + CLR txt = selected ? ui_clr.txt : CLR::blend( ui_clr.txt, ui_clr.bg_sec, 0.45f ); + gui_draw_str( segx[i] + segw[i] / 2, y + ( seg->h / 2 ) - 7, ALIGN_C, FNT_JPN12, txt, txts[i] ); + } + + I32 split1_x = inner_x + w0; + I32 split2_x = inner_x + w0 + w1; + gui_draw_line( split1_x, inner_y, split1_x, inner_y + inner_h - 1, border ); + gui_draw_line( split2_x, inner_y, split2_x, inner_y + inner_h - 1, border ); + gui_draw_rect( x, y, seg->w, seg->h, border ); +} + +static void gui_editor_viewtype_segment_input_fn( void* ptr ) { + GUI_EDITOR_VIEWTYPE_SEGMENT* seg = (GUI_EDITOR_VIEWTYPE_SEGMENT*)ptr; + I32 mx, my; + gui_cursor_pos( &mx, &my ); + U8 m1 = gui_mbutton_down( GUI_MBTNLEFT ); + I32 cur_seg = editor_header_viewtype_segment_index( seg, mx, my ); + + if( m1 ) { + if( !seg->held ) { + seg->held = 1; + seg->held_seg = cur_seg; + } + return; + } + + if( seg->held && seg->held_seg >= 0 && seg->held_seg == cur_seg ) { + editor->gui.view2d_type = seg->held_seg; + editor_layout_map_view( editor ); + } + + seg->held = 0; + seg->held_seg = -1; +} + +static GUI_EDITOR_VIEWTYPE_SEGMENT* gui_editor_viewtype_segment( I32 x, I32 y, I32 w, I32 h, const char* name ) { + if( !gui_check_target() ) + return 0; + + GUI_EDITOR_VIEWTYPE_SEGMENT* seg = new GUI_EDITOR_VIEWTYPE_SEGMENT; + seg->x = x; + seg->y = y; + seg->w = w; + seg->h = h; + seg->xbound = w; + seg->ybound = h; + seg->draw_fn = gui_editor_viewtype_segment_draw_fn; + seg->input_fn = gui_editor_viewtype_segment_input_fn; + seg->held = 0; + seg->held_seg = -1; + strcpy( seg->name, name ); + + GUI_VIEW* parent = gui_get_view(); + seg->parent = parent; + parent->children.push( seg ); + return seg; +} + +struct EDITOR_LAYOUT { + I32 left_x; + I32 left_w; + I32 right_x; + I32 right_w; + I32 top_y; + I32 props_y; + I32 props_h; + I32 assets_y; + I32 assets_h; + I32 view_y; + I32 view_h; + I32 tool_y; + I32 tool_h; + I32 tool_btn_x; + I32 tool_btn_y; + I32 tool_btn_w; + I32 tool_btn_step; + I32 tool_panel_x; + I32 tool_panel_w; + I32 tool_panel_h; + I32 status_x; + I32 status_y; + I32 status_w; + I32 status_h; +}; + +static void editor_set_bounds( GUI_BASE* node, I32 x, I32 y, I32 w, I32 h ) { + if( !node ) + return; + + I32 x_extra = node->xbound - node->w; + I32 y_extra = node->ybound - node->h; + if( x_extra < 0 ) x_extra = 0; + if( y_extra < 0 ) y_extra = 0; + + node->x = x; + node->y = y; + node->w = max( 1, w ); + node->h = max( 1, h ); + node->xbound = node->w + x_extra; + node->ybound = node->h + y_extra; +} + +static void editor_create_header_controls( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + + egui->header_back = gui_button( 10, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ back ]", editor_header_back_cb ); + egui->header_save = gui_button( 110, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ save ]", editor_header_save_cb ); + + egui->header_viewtype_label = gui_label( 250, EDITOR_LAYOUT_NAV_Y + 4, "view type:" ); + egui->header_viewtype = gui_editor_viewtype_segment( 320, EDITOR_LAYOUT_NAV_Y, 186, 20, "viewtype segment" ); + + egui->header_mode_2d = gui_button( 500, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ 2d view ]", editor_header_mode_2d_cb ); + egui->header_mode_3d = gui_button( 600, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ 3d view ]", editor_header_mode_3d_cb ); + egui->header_mode_sim = gui_button( 700, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ simulation ]", editor_header_mode_sim_cb ); +} + +static I32 editor_layout_props_w_max( GUI_EDITORWINDOW* wnd ) { + I32 max_w = wnd->w - ( EDITOR_LAYOUT_MARGIN * 2 ) - EDITOR_LAYOUT_COLUMN_GAP - EDITOR_LAYOUT_RIGHT_MIN_W; + return max( EDITOR_LAYOUT_PROPS_MIN_W, max_w ); +} + +static I32 editor_layout_content_bottom( GUI_EDITORWINDOW* wnd ) { + return wnd->h - EDITOR_LAYOUT_MARGIN - EDITOR_LAYOUT_STATUS_H - EDITOR_LAYOUT_STATUS_GAP; +} + +static I32 editor_layout_left_available_h( GUI_EDITORWINDOW* wnd ) { + return max( 1, editor_layout_content_bottom( wnd ) - EDITOR_LAYOUT_CONTENT_Y ); +} + +static I32 editor_layout_view_h_max( GUI_EDITORWINDOW* wnd ) { + I32 max_h = editor_layout_content_bottom( wnd ) + - EDITOR_LAYOUT_CONTENT_Y + - EDITOR_LAYOUT_VIEW_TOOL_GAP + - EDITOR_LAYOUT_TOOL_PANEL_H + - ( EDITOR_LAYOUT_TITLE_OFFSET * 2 ); + return max( EDITOR_LAYOUT_VIEW_MIN_H, max_h ); +} + +static void editor_layout_clamp_custom_sizes( GAME_EDITOR* e ) { + if( !e || !e->wnd ) + return; + + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + if( egui->props_w <= 0 ) + egui->props_w = EDITOR_LAYOUT_PROPS_DEFAULT_W; + if( egui->props_h <= 0 ) + egui->props_h = EDITOR_LAYOUT_PROPS_DEFAULT_H; + if( egui->view_h <= 0 ) + egui->view_h = EDITOR_LAYOUT_VIEW_DEFAULT_H; + + egui->props_w = min( max( egui->props_w, EDITOR_LAYOUT_PROPS_MIN_W ), editor_layout_props_w_max( e->wnd ) ); + I32 left_avail_h = editor_layout_left_available_h( e->wnd ); + I32 max_props_h = left_avail_h + - EDITOR_LAYOUT_LEFT_BOX_GAP + - EDITOR_LAYOUT_ASSETS_MIN_H + - ( EDITOR_LAYOUT_TITLE_OFFSET * 2 ); + max_props_h = max( EDITOR_LAYOUT_PROPS_MIN_H, max_props_h ); + egui->props_h = min( max( egui->props_h, EDITOR_LAYOUT_PROPS_MIN_H ), max_props_h ); + + I32 view_max = editor_layout_view_h_max( e->wnd ); + egui->view_h = view_max; +} + +static const char* editor_tool_name() { + switch( editor->tool.type ) { + case EDITOR_TOOL_SELECT: return "select"; + case EDITOR_TOOL_WALL: return "wall"; + case EDITOR_TOOL_POLY: return "poly"; + case EDITOR_TOOL_SPRITE: return "sprite"; + case EDITOR_TOOL_ENT: return "ent"; + default: return "none"; + } +} + +const I32 EDITOR_ASSETS_ROW_H = 28; +const I32 EDITOR_ASSETS_THUMB = 20; +const I32 EDITOR_ASSETS_INNER_PAD_X = 8; +const I32 EDITOR_ASSETS_INNER_PAD_Y = 6; +const I32 EDITOR_ASSETS_SCROLLBAR_W = 8; +const I32 EDITOR_ASSETS_SCROLLBAR_GAP = 4; +const I32 EDITOR_ASSETS_SCROLL_STEP = 20; + +struct EDITOR_ASSETS_LAYOUT { + I32 inner_x{}; + I32 inner_y{}; + I32 inner_w{}; + I32 list_h{}; + I32 row_x{}; + I32 row_w{}; + I32 count{}; + I32 content_h{}; + I32 max_scroll{}; + U8 show_scroll{}; + I32 track_x{}; + I32 track_y{}; + I32 track_h{}; +}; + +static EDITOR_ASSETS_LAYOUT editor_assets_layout( GUI_EDITOR_INFOBOX* box, I32 panel_x, I32 panel_y, I32 prop_count ) { + EDITOR_ASSETS_LAYOUT l{}; + l.inner_x = panel_x + EDITOR_ASSETS_INNER_PAD_X; + l.inner_y = panel_y + EDITOR_ASSETS_INNER_PAD_Y; + l.inner_w = max( 1, box->w - ( EDITOR_ASSETS_INNER_PAD_X * 2 ) ); + l.list_h = max( 1, box->h - ( EDITOR_ASSETS_INNER_PAD_Y * 2 ) ); + l.count = prop_count + 1; + l.content_h = l.count * EDITOR_ASSETS_ROW_H; + l.max_scroll = max( 0, l.content_h - l.list_h ); + l.show_scroll = l.content_h > l.list_h; + l.row_x = l.inner_x + 1; + l.row_w = l.inner_w - 2; + if( l.show_scroll ) + l.row_w -= EDITOR_ASSETS_SCROLLBAR_W + EDITOR_ASSETS_SCROLLBAR_GAP; + l.row_w = max( 1, l.row_w ); + l.track_x = l.inner_x + l.inner_w - EDITOR_ASSETS_SCROLLBAR_W; + l.track_y = l.inner_y; + l.track_h = l.list_h - 2; + return l; +} + +static void editor_assets_clamp_scroll( const EDITOR_ASSETS_LAYOUT& l ) { + editor->gui.assets_scroll = min( max( 0, editor->gui.assets_scroll ), l.max_scroll ); +} + +static void gui_editor_infobox_draw_assets( GUI_EDITOR_INFOBOX* box, I32 panel_x, I32 panel_y ) { + if( !editor->map ) + return; + + WORLD_MAP* map = editor->map; + EDITOR_ASSETS_LAYOUT l = editor_assets_layout( box, panel_x, panel_y, (I32)map->props.size ); + editor_assets_clamp_scroll( l ); + I32 yoff = -editor->gui.assets_scroll; + + for( I32 idx = 0; idx < l.count; ++idx ) { + I32 row_y = l.inner_y + idx * EDITOR_ASSETS_ROW_H + yoff; + if( row_y + EDITOR_ASSETS_ROW_H - 2 <= l.inner_y || row_y >= l.inner_y + l.list_h ) + continue; + + U8 map_entry = idx == 0; + SURF_PROPS* p = map_entry ? 0 : &map->props[idx - 1]; + U8 selected = 0; + if( editor->gui.props ) { + if( map_entry ) { + selected = editor->gui.props->seltype == EDITOR_SELECT_ORIGIN + && editor->gui.props->curselect == editor->map; + } else { + selected = editor->gui.props->seltype == EDITOR_SELECT_SURFPROPS + && editor->gui.props->curselect == p; + } + } + + CLR rowbg = idx % 2 ? ui_clr.bg : ui_clr.bg_alt; + if( selected ) + rowbg = CLR::blend( (CLR){ 0.f, 1.f, 0.f, 1.f }, ui_clr.bg, 0.85f ); + + gui_draw_frect( l.row_x, row_y, l.row_w, EDITOR_ASSETS_ROW_H - 2, rowbg ); + if( selected ) + gui_draw_rect( l.row_x, row_y, l.row_w, EDITOR_ASSETS_ROW_H - 2, (CLR){ 0.f, 1.f, 0.f, 1.f } ); + + I32 tx = l.row_x + 3; + I32 ty = row_y + 3; + gui_draw_frect( tx, ty, EDITOR_ASSETS_THUMB, EDITOR_ASSETS_THUMB, ui_clr.border ); + if( map_entry ) { + gui_draw_frect( tx + 1, ty + 1, EDITOR_ASSETS_THUMB - 2, EDITOR_ASSETS_THUMB - 2, ui_clr.bg_alt ); + gui_draw_str( tx + EDITOR_ASSETS_THUMB / 2, ty + 4, ALIGN_C, FNT_JPN12, ui_clr.txt, "m" ); + } else if( p->tex ) { + gl_2d_textured_frect( _gui.gl2d_font, { (F32)(tx + 1), (F32)(ty + 1) }, { (F32)(EDITOR_ASSETS_THUMB - 2), (F32)(EDITOR_ASSETS_THUMB - 2) }, p->tex ); + } else { + gui_draw_frect( tx + 1, ty + 1, EDITOR_ASSETS_THUMB - 2, EDITOR_ASSETS_THUMB - 2, p->clr ); + } + + I32 text_x = tx + EDITOR_ASSETS_THUMB + 8; + CLR txt = selected ? (CLR){ 0.8f, 1.f, 0.8f, 1.f } : ui_clr.txt; + if( map_entry ) { + gui_draw_str( text_x, row_y + 7, ALIGN_L, FNT_JPN12, txt, "[map] -> %s", map->name ); + } else if( p->tex ) { + gui_draw_str( text_x, row_y + 7, ALIGN_L, FNT_JPN12, txt, "[%d] -> %s", idx - 1, p->tex->name ); + } else { + gui_draw_str( text_x, row_y + 7, ALIGN_L, FNT_JPN12, txt, "[%d] -> %.2f, %.2f, %.2f", idx - 1, p->clr.r, p->clr.g, p->clr.b ); + } + + } + + if( l.show_scroll ) { + gui_draw_frect( l.track_x, l.track_y, EDITOR_ASSETS_SCROLLBAR_W, l.track_h, ui_clr.bg_alt ); + gui_draw_rect( l.track_x, l.track_y, EDITOR_ASSETS_SCROLLBAR_W, l.track_h, ui_clr.border ); + + I32 thumb_h = max( 18, ( l.track_h * l.list_h ) / max( 1, l.content_h ) ); + thumb_h = min( thumb_h, l.track_h ); + I32 travel = max( 1, l.track_h - thumb_h ); + I32 thumb_y = l.track_y + ( travel * editor->gui.assets_scroll ) / max( 1, l.max_scroll ); + gui_draw_frect( l.track_x + 1, thumb_y + 1, EDITOR_ASSETS_SCROLLBAR_W - 2, max( 1, thumb_h - 2 ), ui_clr.txt ); + } +} + +static void gui_editor_infobox_draw_fn( void* ptr ) { + GUI_EDITOR_INFOBOX* box = (GUI_EDITOR_INFOBOX*)ptr; + + I32 x = gui_relx( box ); + I32 y = gui_rely( box ); + I32 w = box->w; + I32 h = box->h; + + if( box->type == EDITOR_INFOBOX_STATUS ) { + CLR col = gui_is_fg_window( box )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x, y, w, h, col ); + gui_draw_frect( x + 1, y + 1, w - 2, h - 2, ui_clr.bg_sec ); + gui_draw_str( + x + 6, + y + 4, + ALIGN_L, + FNT_JPN12, + ui_clr.txt, + "selected tool: %s. click in viewport to create/edit.", + editor_tool_name() + ); + + if( editor->game && editor->game->gl ) { + GL_DATA* gl = editor->game->gl; + gui_draw_str( + x + w - 6, + y + 4, + ALIGN_R, + FNT_JPN12, + ui_clr.txt, + "fps: %.3f (%.5f ms) %dx%d", + gl->fps, + gl->frametime, + gl->canvas_size[0], + gl->canvas_size[1] + ); + } + return; + } + + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "assets" ); + y += EDITOR_LAYOUT_TITLE_OFFSET; + + CLR col = gui_is_fg_window( box )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x, y, w, h, col ); + gui_draw_frect( x + 1, y + 1, w - 2, h - 2, ui_clr.bg_sec ); + + gui_draw_push_clip( x + 2, y + 2, w - 4, h - 4 ); + gui_editor_infobox_draw_assets( box, x, y ); + gui_draw_pop_clip(); +} + +static void gui_editor_infobox_input_fn( void* ptr ) { + GUI_EDITOR_INFOBOX* box = (GUI_EDITOR_INFOBOX*)ptr; + static U8 assets_click_held = 0; + if( box->type == EDITOR_INFOBOX_ASSETS ) { + I32 x = gui_relx( box ); + I32 y = gui_rely( box ) + EDITOR_LAYOUT_TITLE_OFFSET; + I32 w = box->w; + I32 h = box->h; + I32 prop_count = editor->map ? (I32)editor->map->props.size : 0; + EDITOR_ASSETS_LAYOUT l = editor_assets_layout( box, x, y, prop_count ); + editor_assets_clamp_scroll( l ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + if( inbounds ) { + U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL ); + if( scroll ) { + gui_capture_scroll(); + if( scroll == 1 ) + editor->gui.assets_scroll -= EDITOR_ASSETS_SCROLL_STEP; + else if( scroll == (U8)-1 ) + editor->gui.assets_scroll += EDITOR_ASSETS_SCROLL_STEP; + + editor_assets_clamp_scroll( l ); + } + + U8 m1 = gui_mbutton_down( GUI_MBTNLEFT ); + if( m1 && !assets_click_held && editor->map ) { + if( mx >= l.row_x && mx < l.row_x + l.row_w ) { + I32 idx = ( my - l.inner_y + editor->gui.assets_scroll ) / EDITOR_ASSETS_ROW_H; + if( idx >= 0 && idx < l.count && editor->gui.props ) { + if( idx == 0 ) + gui_editor_propview_select( editor->gui.props, editor->map, EDITOR_SELECT_ORIGIN ); + else + gui_editor_propview_select( editor->gui.props, &editor->map->props[idx - 1], EDITOR_SELECT_SURFPROPS ); + } + } + } + assets_click_held = m1; + } + if( !gui_mbutton_down( GUI_MBTNLEFT ) ) + assets_click_held = 0; + } + + gui_base_input_fn( box ); +} + +static GUI_EDITOR_INFOBOX* gui_editor_infobox( I32 x, I32 y, I32 w, I32 h, U8 type, const char* name ) { + if( !gui_check_target() ) + return 0; + + GUI_EDITOR_INFOBOX* box = new GUI_EDITOR_INFOBOX; + box->x = x; + box->y = y; + box->w = w; + box->h = h; + box->xbound = w; + box->ybound = h + ( type == EDITOR_INFOBOX_STATUS ? 0 : EDITOR_LAYOUT_TITLE_OFFSET ); + box->draw_fn = gui_editor_infobox_draw_fn; + box->input_fn = gui_editor_infobox_input_fn; + box->type = type; + strcpy( box->name, name ); + + GUI_VIEW* parent = gui_get_view(); + box->parent = parent; + parent->children.push( box ); + return box; +} + +static EDITOR_LAYOUT editor_calc_layout( GAME_EDITOR* e ) { + GUI_EDITORWINDOW* wnd = e->wnd; + editor_layout_clamp_custom_sizes( e ); + + EDITOR_LAYOUT l{}; + l.left_x = EDITOR_LAYOUT_MARGIN; + l.left_w = e->gui.props_w; + l.right_x = l.left_x + l.left_w + EDITOR_LAYOUT_COLUMN_GAP; + l.right_w = max( 1, wnd->w - EDITOR_LAYOUT_MARGIN - l.right_x ); + l.tool_btn_x = l.right_x; + l.tool_btn_w = 45; + l.tool_panel_x = l.tool_btn_x + l.tool_btn_w + EDITOR_LAYOUT_TOOL_PANEL_GAP; + l.tool_panel_w = EDITOR_LAYOUT_TOOL_PANEL_W; + + l.top_y = EDITOR_LAYOUT_NAV_Y; + l.props_y = EDITOR_LAYOUT_CONTENT_Y; + l.view_y = EDITOR_LAYOUT_CONTENT_Y; + l.props_h = e->gui.props_h; + I32 left_total_h = max( 1, editor_layout_content_bottom( wnd ) - l.props_y ); + I32 props_max_h = left_total_h + - EDITOR_LAYOUT_LEFT_BOX_GAP + - EDITOR_LAYOUT_ASSETS_MIN_H + - ( EDITOR_LAYOUT_TITLE_OFFSET * 2 ); + props_max_h = max( EDITOR_LAYOUT_PROPS_MIN_H, props_max_h ); + l.props_h = min( max( l.props_h, EDITOR_LAYOUT_PROPS_MIN_H ), props_max_h ); + l.assets_y = l.props_y + EDITOR_LAYOUT_TITLE_OFFSET + l.props_h + EDITOR_LAYOUT_LEFT_BOX_GAP; + l.assets_h = max( EDITOR_LAYOUT_ASSETS_MIN_H, editor_layout_content_bottom( wnd ) - l.assets_y - EDITOR_LAYOUT_TITLE_OFFSET ); + + l.view_h = e->gui.view_h; + l.tool_y = l.view_y + EDITOR_LAYOUT_TITLE_OFFSET + l.view_h + EDITOR_LAYOUT_VIEW_TOOL_GAP; + l.tool_btn_y = l.tool_y + EDITOR_LAYOUT_TOOL_BTN_TOP_GAP; + + I32 bottom_content = editor_layout_content_bottom( wnd ); + l.tool_h = max( 1, bottom_content - l.tool_y - EDITOR_LAYOUT_TITLE_OFFSET ); + l.tool_panel_h = l.tool_h; + l.tool_btn_step = 23; + + l.tool_panel_w = EDITOR_LAYOUT_TOOL_PANEL_W; + I32 required_w = l.tool_btn_w + EDITOR_LAYOUT_TOOL_PANEL_GAP + l.tool_panel_w; + if( required_w > l.right_w ) { + I32 overflow = required_w - l.right_w; + I32 shrink_panel = min( overflow, max( 0, l.tool_panel_w - 180 ) ); + l.tool_panel_w -= shrink_panel; + } + + l.tool_panel_x = l.tool_btn_x + l.tool_btn_w + EDITOR_LAYOUT_TOOL_PANEL_GAP; + + l.status_x = EDITOR_LAYOUT_MARGIN; + l.status_w = max( 1, wnd->w - EDITOR_LAYOUT_MARGIN * 2 ); + l.status_h = EDITOR_LAYOUT_STATUS_H; + l.status_y = wnd->h - EDITOR_LAYOUT_MARGIN - l.status_h; + + return l; +} + +static void editor_resize_base_view( GUI_EDITORWINDOW* wnd ) { + GUI_VIEW* base = (GUI_VIEW*)gui_find_node( wnd, "BASE_VIEW" ); + if( !base ) + return; + + editor_set_bounds( base, 1, 1, wnd->w - 2, wnd->h - 2 ); +} + +static void editor_layout_start_menu( GAME_EDITOR* e ) { + GUI_EDITORWINDOW* wnd = e->wnd; + GUI_LIST* list = (GUI_LIST*)gui_find_node( wnd, "map list" ); + GUI_BUTTON* new_map = (GUI_BUTTON*)gui_find_node( wnd, "new map" ); + GUI_BUTTON* load_map = (GUI_BUTTON*)gui_find_node( wnd, "load map" ); + if( !list || !new_map || !load_map ) + return; + + const I32 min_margin = 10; + const I32 list_title_offset = 15; + const I32 button_gap = 10; + const I32 button_h = 25; + const I32 list_button_gap = 5; + I32 list_w = 200; + I32 list_h = 200; + I32 max_list_w = max( 80, wnd->w - min_margin * 2 ); + I32 start_y = EDITOR_LAYOUT_CONTENT_Y; + I32 max_list_h = max( 80, wnd->h - ( start_y + list_title_offset + list_button_gap + button_h + min_margin ) ); + if( list_w > max_list_w ) list_w = max_list_w; + if( list_h > max_list_h ) list_h = max_list_h; + + I32 x = ( wnd->w - list_w ) / 2; + I32 y = start_y; + I32 bottom = y + list_title_offset + list_h + list_button_gap + button_h; + if( bottom > wnd->h - min_margin ) { + y = max( min_margin, wnd->h - min_margin - ( list_title_offset + list_h + list_button_gap + button_h ) ); + } + + editor_set_bounds( list, x, y, list_w, list_h ); + I32 bw = max( 50, ( list_w - button_gap ) / 2 ); + I32 by = y + list_title_offset + list_h + list_button_gap; + editor_set_bounds( new_map, x, by, bw, button_h ); + editor_set_bounds( load_map, x + bw + button_gap, by, bw, button_h ); +} + +static void editor_layout_tool_buttons( GUI_EDITORWINDOW* wnd, const EDITOR_LAYOUT& l ) { + const char* names[] = { "none", "select", "wall", "poly", "sprite", "ent" }; + I32 y = l.tool_btn_y; + for( U32 i = 0; i < sizeof( names ) / sizeof( names[0] ); ++i ) { + GUI_BUTTON* btn = (GUI_BUTTON*)gui_find_node( wnd, names[i] ); + editor_set_bounds( btn, l.tool_btn_x, y, l.tool_btn_w, 20 ); + y += l.tool_btn_step; + } +} + +static void editor_layout_map_view( GAME_EDITOR* e ) { + if( !e->gui.v2d || !e->gui.v3d || !e->gui.props || !e->gui.tool ) + return; + + EDITOR_LAYOUT l = editor_calc_layout( e ); + + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + GUI_BUTTON* back = egui->header_back; + GUI_BUTTON* save = egui->header_save; + GUI_BUTTON* v2btn = egui->header_mode_2d; + GUI_BUTTON* v3btn = egui->header_mode_3d; + GUI_BUTTON* simbtn = egui->header_mode_sim; + GUI_LABEL* view_type_lbl = egui->header_viewtype_label; + GUI_BASE* view_type_seg = egui->header_viewtype; + + const I32 top_btn_y = l.top_y; + const I32 top_btn_h = 20; + const I32 nav_left_x = 10; + const I32 nav_btn_w = 92; + const I32 nav_gap = 8; + const I32 mode_btn_w = 92; + I32 mode_group_w = mode_btn_w * 3 + nav_gap * 2; + I32 mode_x = e->wnd->w - EDITOR_LAYOUT_MARGIN - mode_group_w; + I32 type_seg_x = l.right_x; + I32 type_lbl_w = 64; + I32 type_lbl_x = type_seg_x - type_lbl_w - 6; + I32 max_type_w = mode_x - type_seg_x - 8; + I32 type_seg_w = min( 174, max( 120, max_type_w ) ); + + editor_set_bounds( back, nav_left_x, top_btn_y, nav_btn_w, top_btn_h ); + editor_set_bounds( save, nav_left_x + nav_btn_w + nav_gap, top_btn_y, nav_btn_w, top_btn_h ); + + GUI_BUTTON* mode_btns[] = { v2btn, v3btn, simbtn }; + const char* mode_names[] = { "[ 2d view ]", "[ 3d view ]", "[ simulation ]" }; + for( I32 i = 0; i < 3; ++i ) { + I32 bx = mode_x + ( mode_btn_w + nav_gap ) * i; + editor_set_bounds( mode_btns[i], bx, top_btn_y, mode_btn_w, top_btn_h ); + if( mode_btns[i] ) + snprintf( mode_btns[i]->name, GUI_NAME_LEN, "%s", mode_names[i] ); + } + + U8 show_viewtype = e->gui.view_mode == EDITOR_VIEWMODE_2D; + if( view_type_lbl ) { + view_type_lbl->x = type_lbl_x; + view_type_lbl->y = top_btn_y + 4; + view_type_lbl->enabled = show_viewtype; + } + if( view_type_seg ) { + editor_set_bounds( view_type_seg, type_seg_x, top_btn_y, type_seg_w, top_btn_h ); + view_type_seg->enabled = show_viewtype; + } + + editor_set_bounds( e->gui.props, l.left_x, l.props_y, l.left_w, l.props_h ); + if( e->gui.props->itemview ) { + editor_set_bounds( + e->gui.props->itemview, + 0, + EDITOR_LAYOUT_TITLE_OFFSET, + e->gui.props->w, + e->gui.props->h + ); + } + if( e->gui.assets ) { + editor_set_bounds( e->gui.assets, l.left_x, l.assets_y, l.left_w, l.assets_h ); + } + + editor_set_bounds( e->gui.v2d, l.right_x, l.view_y, l.right_w, l.view_h ); + e->gui.v2d->ybound = e->gui.v2d->h + EDITOR_LAYOUT_TITLE_OFFSET; + editor_set_bounds( e->gui.v3d, l.right_x, l.view_y, l.right_w, l.view_h ); + e->gui.v3d->ybound = e->gui.v3d->h + EDITOR_LAYOUT_TITLE_OFFSET; + + editor_layout_tool_buttons( e->wnd, l ); + + editor_set_bounds( e->gui.tool, l.tool_panel_x, l.tool_y, l.tool_panel_w, l.tool_panel_h ); + if( e->gui.tool->itemview ) { + editor_set_bounds( + e->gui.tool->itemview, + 0, + EDITOR_LAYOUT_TITLE_OFFSET, + e->gui.tool->w, + e->gui.tool->h + ); + } + if( e->gui.status ) { + editor_set_bounds( e->gui.status, l.status_x, l.status_y, l.status_w, l.status_h ); + } + + editor_apply_view_mode( e ); + + if( e->gui.gridlabel ) { + I32 gx = 150; + I32 gy = e->gui.v2d->h - 4; + e->gui.gridlabel->x = gx; + e->gui.gridlabel->y = gy + 1; + + GUI_BUTTON* plus = (GUI_BUTTON*)gui_find_node( e->gui.v2d, "+" ); + GUI_BUTTON* minus = (GUI_BUTTON*)gui_find_node( e->gui.v2d, "-" ); + GUI_CHECKBOX* propgrid = (GUI_CHECKBOX*)gui_find_node( e->gui.v2d, "properties grid" ); + GUI_CHECKBOX* wireframe = (GUI_CHECKBOX*)gui_find_node( e->gui.v2d, "wireframe" ); + editor_set_bounds( plus, gx + 70, gy, 18, 18 ); + editor_set_bounds( minus, gx + 93, gy, 18, 18 ); + editor_set_bounds( propgrid, gx + 120, gy, propgrid ? propgrid->w : 120, 18 ); + editor_set_bounds( wireframe, gx + 240, gy, wireframe ? wireframe->w : 80, 18 ); + } + + GUI_BUTTON* compile = (GUI_BUTTON*)gui_find_node( e->gui.v3d, "compile bsp" ); + GUI_CHECKBOX* drawbsp = (GUI_CHECKBOX*)gui_find_node( e->gui.v3d, "draw bsp" ); + I32 gy3d = e->gui.v3d->h - 4; + editor_set_bounds( compile, 1, gy3d, 90, 18 ); + editor_set_bounds( drawbsp, 101, gy3d, drawbsp ? drawbsp->w : 80, 18 ); + + editor_update_properties_column( e ); + gui_editor_toolview_update( e->gui.tool ); + editor_notify_grid_change( e ); +} void gui_editorwindow_draw_fn( void* ptr ) { GUI_EDITORWINDOW* wnd = (GUI_EDITORWINDOW*)ptr; @@ -21,8 +814,8 @@ GUI_EDITORWINDOW* gui_editorwindow_create( I32 w, I32 h ) { GUI_EDITORWINDOW* wnd = new GUI_EDITORWINDOW; wnd->x = 0; wnd->y = 0; - wnd->w = w; - wnd->h = h; + wnd->xbound = wnd->w = w; + wnd->ybound = wnd->h = h; wnd->locked = 1; wnd->draw_fn = gui_editorwindow_draw_fn; strcpy( wnd->name, "EDITORWINDOW" ); @@ -38,14 +831,12 @@ GUI_EDITORWINDOW* gui_editorwindow_create( I32 w, I32 h ) { GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ) { GUI_EDITORWINDOW* wnd = gui_editorwindow_create( w, h ); - gui_title( "ray2Dscape (level editor)" ); - GAME_EDITOR* e = editor; LIST* map_list = editor_get_map_list( e ); - gui_list( wnd->w / 2 - 100, 40, 200, 200, "map list", map_list, &editor->gui.map_select ); - gui_button( wnd->w / 2 - 100, 260, 95, 25, "new map", editor_new_map_cb ); - gui_button( wnd->w / 2 + 5, 260, 95, 25, "load map", editor_load_map_cb ); + gui_list( wnd->w / 2 - 100, EDITOR_LAYOUT_CONTENT_Y, 200, 200, "map list", map_list, &editor->gui.map_select ); + gui_button( wnd->w / 2 - 100, EDITOR_LAYOUT_CONTENT_Y + 220, 95, 25, "new map", editor_new_map_cb ); + gui_button( wnd->w / 2 + 5, EDITOR_LAYOUT_CONTENT_Y + 220, 95, 25, "load map", editor_load_map_cb ); return wnd; } @@ -57,13 +848,16 @@ void editor_update_properties_column( GAME_EDITOR* e ) { void editor_create_properties_column( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; - gui_button( 10, 10, 145, 20, "back", pfn( void* ) { editor_close( editor ); } ); - gui_button( 165, 10, 145, 20, "save", pfn( void* ) { editor_save_map( editor ); } ); - - egui->props = gui_editor_propview( 10, 38, 300, 460 ); + egui->props = gui_editor_propview( 10, EDITOR_LAYOUT_CONTENT_Y, 300, 460 ); gui_editor_propview_select( egui->props, e->map, EDITOR_SELECT_ORIGIN ); } +void editor_create_auxiliary_panels( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + egui->assets = gui_editor_infobox( 10, EDITOR_LAYOUT_CONTENT_Y + 310, 300, 180, EDITOR_INFOBOX_ASSETS, "EDITOR_ASSETS_VIEW" ); + egui->status = gui_editor_infobox( 10, 568, 780, EDITOR_LAYOUT_STATUS_H, EDITOR_INFOBOX_STATUS, "EDITOR_STATUS_VIEW" ); +} + void editor_update_toolview( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; gui_editor_toolview_update( egui->tool ); @@ -80,41 +874,49 @@ void settool( U8 t ) { editor_update_toolview( editor ); } } + +static U8 EDITOR_TOOL_BUTTON_TYPES[] = { + EDITOR_TOOL_NONE, + EDITOR_TOOL_SELECT, + EDITOR_TOOL_WALL, + EDITOR_TOOL_POLY, + EDITOR_TOOL_SPRITE, + EDITOR_TOOL_ENT +}; + +static void settool_btn_cb( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + if( !btn || !btn->extra ) + return; + + settool( *(U8*)btn->extra ); +} + void editor_create_toolview_column( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; - I32 x = 320, y = 428, off = 23; + I32 x = 320; + I32 y = EDITOR_LAYOUT_CONTENT_Y + EDITOR_LAYOUT_VIEW_DEFAULT_H + EDITOR_LAYOUT_VIEW_TOOL_GAP + EDITOR_LAYOUT_TOOL_BTN_TOP_GAP; + I32 off = 23; + const char* labels[] = { "none", "select", "wall", "poly", "sprite", "ent" }; - gui_button( x, y, 45, 20, "none", pfn( void* ) { settool( EDITOR_TOOL_NONE ); } ); y += off; - gui_button( x, y, 45, 20, "select", pfn( void* ) { settool( EDITOR_TOOL_SELECT ); } ); y += off; - gui_button( x, y, 45, 20, "wall", pfn( void* ) { settool( EDITOR_TOOL_WALL ); } ); y += off; - gui_button( x, y, 45, 20, "poly", pfn( void* ) { settool( EDITOR_TOOL_POLY ); } ); y += off; - gui_button( x, y, 45, 20, "sprite", pfn( void* ) { settool( EDITOR_TOOL_SPRITE ); } ); y += off; - gui_button( x, y, 45, 20, "ent", pfn( void* ) { settool( EDITOR_TOOL_ENT ); } ); y += off; + for( U32 i = 0; i < sizeof( labels ) / sizeof( labels[0] ); ++i ) { + GUI_BUTTON* btn = gui_button( x, y, 45, 20, labels[i], settool_btn_cb ); + btn->extra = &EDITOR_TOOL_BUTTON_TYPES[i]; + y += off; + } - egui->tool = gui_editor_toolview( 370, 425, 300, 150 ); + egui->tool = gui_editor_toolview( 370, y - EDITOR_LAYOUT_TOOL_BTN_TOP_GAP, 300, 150 ); gui_editor_toolview_update( egui->tool ); } void editor_create_game_view_column( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; - I32 x = 320, y = 10; - - gui_button( x, y, 229, 20, "2d view", pfn( void* ptr ) { - editor->gui.v3d->enabled = 0; - editor->gui.v2d->enabled = 1; - } ); - - gui_button( x + 239, y, 229, 20, "3d view", pfn( void* ptr ) { - editor->gui.v3d->enabled = 1; - editor->gui.v2d->enabled = 0; - } ); - - y += 28; + I32 x = 320, y = EDITOR_LAYOUT_CONTENT_Y; egui->v2d = gui_editor_2dview( x, y, 468, 370 ); egui->v3d = gui_editor_3dview( x, y, 468, 370 ); - egui->v2d->enabled = 0; - egui->v3d->enabled = 1; + egui->v2d->enabled = 1; + egui->v3d->enabled = 0; } void editor_create_map_view( GAME_EDITOR* e ) { @@ -128,15 +930,58 @@ void editor_create_map_view( GAME_EDITOR* e ) { gui_free( *ptr ); } ); w->children.clear(); + e->gui.assets = 0; + e->gui.status = 0; + e->gui.assets_scroll = 0; + e->gui.header_viewtype_label = 0; + e->gui.header_back = 0; + e->gui.header_save = 0; + e->gui.header_mode_2d = 0; + e->gui.header_mode_3d = 0; + e->gui.header_mode_sim = 0; + e->gui.header_viewtype = 0; + e->gui.view2d_type = EDITOR_2DVIEW_TOP_DOWN; + e->gui.view_mode = EDITOR_VIEWMODE_2D; gui_set_window( w ); gui_set_view( 0 ); gui_view( 1, 1, w->w - 2, w->h - 2 ); + editor_create_header_controls( e ); editor_create_properties_column( e ); editor_create_toolview_column( e ); editor_create_game_view_column( e ); + editor_create_auxiliary_panels( e ); + editor_layout_map_view( e ); +} + +void editor_resize( GAME_EDITOR* e, I32 w, I32 h ) { + if( !e || !e->wnd ) + return; + + w = max( 1, w ); + h = max( 1, h ); + + GUI_EDITORWINDOW* wnd = e->wnd; + if( wnd->w == w && wnd->h == h ) + return; + + editor_set_bounds( wnd, 0, 0, w, h ); + editor_resize_base_view( wnd ); + + if( e->map ) + editor_layout_map_view( e ); + else + editor_layout_start_menu( e ); + + if( e->gui.new_map_popup ) { + GUI_WINDOW* popup = e->gui.new_map_popup; + if( popup->x > w - 5 ) popup->x = w - 5; + if( popup->x + popup->w < 5 ) popup->x = 5 - popup->w; + if( popup->y > h - 5 ) popup->y = h - 5; + if( popup->y + popup->h < 5 ) popup->y = 5 - popup->h; + } } void close_new_map_popup( void* ) { diff --git a/src/editor/properties.cpp b/src/editor/properties.cpp index ebae177..fa3a117 100644 --- a/src/editor/properties.cpp +++ b/src/editor/properties.cpp @@ -3,6 +3,45 @@ #include "../game/assets.h" const I32 PROPVIEW_TITLE_OFFSET = 15; +const I32 PROPVIEW_PAD = 10; +const I32 PROPVIEW_ACTION_BTN_W = 20; +const I32 PROPVIEW_ACTION_BTN_GAP = 5; + +static I32 propview_col_left() { + return PROPVIEW_PAD; +} + +static I32 propview_col_right( GUI_EDITOR_PROPVIEW* view ) { + if( !view ) + return PROPVIEW_PAD; + + return max( PROPVIEW_PAD, view->w - PROPVIEW_PAD ); +} + +static I32 propview_input_width( GUI_EDITOR_PROPVIEW* view ) { + return max( 120, propview_col_right( view ) - propview_col_left() ); +} + +static I32 propview_sub_input_x( I32 x ) { + return x + 10; +} + +static I32 propview_sub_input_width( GUI_EDITOR_PROPVIEW* view, I32 x ) { + I32 right = propview_col_right( view ); + return max( 100, right - propview_sub_input_x( x ) ); +} + +static I32 propview_action_x( GUI_EDITOR_PROPVIEW* view ) { + I32 right = propview_col_right( view ); + return max( propview_col_left(), right - PROPVIEW_ACTION_BTN_W ); +} + +static I32 propview_action2_x( GUI_EDITOR_PROPVIEW* view ) { + return max( + propview_col_left(), + propview_action_x( view ) - PROPVIEW_ACTION_BTN_W - PROPVIEW_ACTION_BTN_GAP + ); +} void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* view, void* what, U8 seltype ) { if( !editor->map ) @@ -27,11 +66,19 @@ void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* view, void* what, U8 selty } // returns the subentry height -I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROPS* props ) { +I32 gui_editor_propview_surfprops_subentry( + GUI_EDITOR_PROPVIEW* view, + I32 x, + I32 y, + I32* propid, + SURF_PROPS* props +) { I32 oldy = y; I32 space = 20; + I32 action_x = propview_action_x( view ); + I32 action2_x = propview_action2_x( view ); gui_label( x, y, "prop id: %d", *propid ); - GUI_BUTTON* newprop = gui_button( x + 235, y, 20, 20, "+", pfn( void* ptr ) { + GUI_BUTTON* newprop = gui_button( action2_x, y, 20, 20, "+", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; SURF_PROPS props; props.tex = 0; @@ -39,10 +86,12 @@ I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROP editor->map->props.push( props ); *(U32*)(btn->extra) = editor->map->props.size - 1; + // Keep the newest prop visible in the assets panel (pixel scroll). + editor->gui.assets_scroll = ( editor->map->props.size + 1 ) * 28; gui_editor_propview_update( editor->gui.props ); } ); newprop->extra = propid; - GUI_BUTTON* goprop = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* goprop = gui_button( action_x, y, 20, 20, "\x1A", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS ); } ); @@ -55,7 +104,7 @@ I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROP else gui_label( x + 10, y, "texture: none" ); - GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = gui_button( action_x, y, 20, 20, "\x1A", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); @@ -64,7 +113,13 @@ I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROP btn->extra = &props->tex; y += space; - gui_colorinput( x + 10, y, 270, "color", &props->clr ); y += (space+18); + gui_colorinput( + propview_sub_input_x( x ), + y, + propview_sub_input_width( view, x ), + "color", + &props->clr + ); y += (space+18); return y - oldy; } @@ -83,12 +138,12 @@ void gui_editor_propview_create_wallprops( GUI_EDITOR_PROPVIEW* view ) { } ); gui_label( x, y, "idx: %d", wall_idx ); y += space; - y += gui_editor_propview_surfprops_subentry( x, y, &s->propid, props ); + y += gui_editor_propview_surfprops_subentry( view, x, y, &s->propid, props ); GUI_VECTORINPUT* posinput; - posinput = gui_vectorinput( x, y, 280, "start", (F32*)&s->start, 3, -INFINITY, INFINITY, step ); y += (space+18); + posinput = gui_vectorinput( x, y, propview_input_width( view ), "start", (F32*)&s->start, 3, -INFINITY, INFINITY, step ); y += (space+18); posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); }; - posinput = gui_vectorinput( x, y, 280, "end", (F32*)&s->end, 3, -INFINITY, INFINITY, step ); y += (space+18); + posinput = gui_vectorinput( x, y, propview_input_width( view ), "end", (F32*)&s->end, 3, -INFINITY, INFINITY, step ); y += (space+18); posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); }; } @@ -103,15 +158,16 @@ void gui_editor_propview_create_polyprops( GUI_EDITOR_PROPVIEW* view ) { I32 poly_idx = m->polygons.idx_where( fn( MAP_POLYGON* mp ) { return p == mp; } ); + I32 action_x = propview_action_x( view ); gui_label( x, y, "idx: %d", poly_idx ); y += space; - y += gui_editor_propview_surfprops_subentry( x, y, &p->propid, props ); + y += gui_editor_propview_surfprops_subentry( view, x, y, &p->propid, props ); gui_label( x, y, "vertices: %d", p->vertices.size ); y += space; I32 idx = 0; p->vertices.each( fn( MAP_VERTEX* v ) { gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f }", idx, v->pos.x, v->pos.y, v->pos.z ); - GUI_BUTTON* btn = gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = gui_button( action_x, y - 2, 20, 20, "\x1A", pfn( void* ptr ) { GUI_EDITOR_PROPVIEW* view = editor->gui.props; GUI_BUTTON* btn = (GUI_BUTTON*)ptr; MAP_POLYGON* p = (MAP_POLYGON*)view->curselect; @@ -139,6 +195,7 @@ void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) { gui_label( x, y, "walls: %d", m->walls.size ); y += space; gui_label( x, y, "polygons: %d", m->polygons.size ); y += space; gui_label( x, y, "props: %d", m->props.size ); y += space; + I32 action_x = propview_action_x( view ); I32 i = 0; m->props.each( fn( SURF_PROPS* p ) { if( p->tex ) @@ -146,7 +203,7 @@ void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) { else gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f, %.02f }", i++, p->clr.r, p->clr.g, p->clr.b, p->clr.a ); - GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = gui_button( action_x, y, 20, 20, "\x1A", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS ); } ); @@ -155,8 +212,8 @@ void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) { } ); gui_label( x, y, "sprites: %d", m->sprites.size ); y += space; gui_label( x, y, "loaded textures: %d", m->textures.size ); y += space; - gui_vectorinput( x, y, 280, "spawn position", (F32*)&m->startpos, 3, -INFINITY, INFINITY, step ); y += (space+18); - GUI_FLOATINPUT* ang = gui_floatinput( x, y, 280, "spawn angle", &m->startang, -180.f, 180.f, 1.f ); y += (space+18); + gui_vectorinput( x, y, propview_input_width( view ), "spawn position", (F32*)&m->startpos, 3, -INFINITY, INFINITY, step ); y += (space+18); + GUI_FLOATINPUT* ang = gui_floatinput( x, y, propview_input_width( view ), "spawn angle", &m->startang, -180.f, 180.f, 1.f ); y += (space+18); ang->wraparound = 1; } @@ -174,11 +231,12 @@ void gui_editor_propview_create_pvertexprops( GUI_EDITOR_PROPVIEW* view ) { } ); F32 step = editor->propgrid? editor->grid : 0.25f; + I32 action_x = propview_action_x( view ); gui_label( x, y, "idx: %d", vert_idx ); y += space; gui_label( x, y, "polygon idx: %d", poly_idx ); - gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ) { + gui_button( action_x, y - 2, 20, 20, "\x1A", pfn( void* ) { GUI_EDITOR_PROPVIEW* view = editor->gui.props; MAP_VERTEX* v = (MAP_VERTEX*)view->curselect; WORLD_MAP* m = editor->map; @@ -192,10 +250,10 @@ void gui_editor_propview_create_pvertexprops( GUI_EDITOR_PROPVIEW* view ) { } ); y += space; - GUI_VECTORINPUT* posinput = gui_vectorinput( x, y, 280, "position", (F32*)&v->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); + GUI_VECTORINPUT* posinput = gui_vectorinput( x, y, propview_input_width( view ), "position", (F32*)&v->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); posinput->cb = pfn( void* ptr ) { map_check_bounds( editor->map ); }; - gui_vectorinput( x, y, 280, "texture coordinates", (F32*)&v->uv, 2, 0.f, 1.f, 0.005f, "xy", "%.03f" ); y += (space+18); - gui_colorinput( x, y, 280, "color", &v->clr ); y += (space+18); + gui_vectorinput( x, y, propview_input_width( view ), "texture coordinates", (F32*)&v->uv, 2, 0.f, 1.f, 0.005f, "xy", "%.03f" ); y += (space+18); + gui_colorinput( x, y, propview_input_width( view ), "color", &v->clr ); y += (space+18); } void gui_editor_propview_create_spriteprops( GUI_EDITOR_PROPVIEW* view ) { @@ -208,19 +266,20 @@ void gui_editor_propview_create_spriteprops( GUI_EDITOR_PROPVIEW* view ) { I32 sprite_idx = m->sprites.idx_where( fn( MAP_SPRITE* ms ) { return ms == s; } ); + I32 action_x = propview_action_x( view ); F32 step = editor->propgrid? editor->grid : 0.25f; gui_label( x, y, "idx: %d", sprite_idx ); y += space; - gui_vectorinput( x, y, 280, "position", (F32*)&s->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); - gui_vectorinput( x, y, 280, "size", (F32*)&s->size, 2, 1.f, INFINITY, 1.f, "wh" ); y += (space+18); - gui_colorinput( x, y, 280, "color", &s->clr ); y += (space+18); + gui_vectorinput( x, y, propview_input_width( view ), "position", (F32*)&s->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); + gui_vectorinput( x, y, propview_input_width( view ), "size", (F32*)&s->size, 2, 1.f, INFINITY, 1.f, "wh" ); y += (space+18); + gui_colorinput( x, y, propview_input_width( view ), "color", &s->clr ); y += (space+18); if( s->tex ) gui_label( x, y, "texture: %s", s->tex->name ); else gui_label( x, y, "texture: none" ); - GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = gui_button( action_x, y, 20, 20, "\x1A", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); @@ -240,13 +299,14 @@ void gui_editor_propview_create_surfprops( GUI_EDITOR_PROPVIEW* view ) { I32 i = m->props.idx_where( fn( SURF_PROPS* mp ) { return mp == p; } ); if( i == -1 ) return; + I32 action_x = propview_action_x( view ); gui_label( x, y, "prop id: %d", i ); y += space; if( p->tex ) gui_label( x, y, "texture: %s", assets_abspath( p->tex->name ) ); else gui_label( x, y, "texture: none" ); - GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = gui_button( action_x, y, 20, 20, "\x1A", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); @@ -254,7 +314,7 @@ void gui_editor_propview_create_surfprops( GUI_EDITOR_PROPVIEW* view ) { } ); btn->extra = &p->tex; y += space; - gui_colorinput( x, y, 280, "color", &p->clr ); y += (space+18); + gui_colorinput( x, y, propview_input_width( view ), "color", &p->clr ); y += (space+18); } void gui_editor_propview_create_entprops( GUI_EDITOR_PROPVIEW* view ) { @@ -285,15 +345,15 @@ void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* view ) { void gui_editor_propview_get_title( GUI_EDITOR_PROPVIEW* view, char* buf ) { switch( view->seltype ) { - case EDITOR_SELECT_NONE: sprintf( buf, "properties: " ); break; - case EDITOR_SELECT_POLY: sprintf( buf, "polygon properties: " ); break; - case EDITOR_SELECT_ORIGIN: sprintf( buf, "map [%s] properties: ", editor->map->name ); break; - case EDITOR_SELECT_ENT: sprintf( buf, "entity properties: " ); break; - case EDITOR_SELECT_SPRITE: sprintf( buf, "sprite properties: " ); break; - case EDITOR_SELECT_PVERTEX: sprintf( buf, "vertex properties: " ); break; - case EDITOR_SELECT_SURFPROPS: sprintf( buf, "surface properties: " ); break; + case EDITOR_SELECT_NONE: sprintf( buf, "properties" ); break; + case EDITOR_SELECT_POLY: sprintf( buf, "polygon properties" ); break; + case EDITOR_SELECT_ORIGIN: sprintf( buf, "properties" ); break; + case EDITOR_SELECT_ENT: sprintf( buf, "entity properties" ); break; + case EDITOR_SELECT_SPRITE: sprintf( buf, "sprite properties" ); break; + case EDITOR_SELECT_PVERTEX: sprintf( buf, "vertex properties" ); break; + case EDITOR_SELECT_SURFPROPS: sprintf( buf, "surface properties" ); break; case EDITOR_SELECT_WVERTEX: - case EDITOR_SELECT_WALL: sprintf( buf, "wall properties: " ); break; + case EDITOR_SELECT_WALL: sprintf( buf, "wall properties" ); break; } } diff --git a/src/editor/toolview.cpp b/src/editor/toolview.cpp index df1d79a..56e55f2 100644 --- a/src/editor/toolview.cpp +++ b/src/editor/toolview.cpp @@ -7,6 +7,9 @@ const I32 TOOLVIEW_INNER_PAD = 20; const I32 TOOLVIEW_ROW_HEIGHT = 20; const I32 TOOLVIEW_ROW_GAP = 4; const I32 TOOLVIEW_WALL_SHAPE_LIST_HEIGHT = 54; +const I32 TOOLVIEW_SCROLL_STEP = 20; +const I32 TOOLVIEW_SCROLLBAR_W = 8; +const I32 TOOLVIEW_SCROLLBAR_MIN_H = 18; GUI_EDITOR_TOOLVIEW* gui_editor_toolview_from_item_child( GUI_BASE* child ) { if( !child || !child->parent || !child->parent->parent ) @@ -15,6 +18,25 @@ GUI_EDITOR_TOOLVIEW* gui_editor_toolview_from_item_child( GUI_BASE* child ) { return (GUI_EDITOR_TOOLVIEW*)child->parent->parent; } +I32 gui_editor_toolview_visible_h( GUI_EDITOR_TOOLVIEW* view ) { + return std::max( 1, view->h - 4 ); +} + +I32 gui_editor_toolview_max_scroll( GUI_EDITOR_TOOLVIEW* view ) { + return std::max( 0, view->content_h - gui_editor_toolview_visible_h( view ) ); +} + +void gui_editor_toolview_clamp_scroll( GUI_EDITOR_TOOLVIEW* view ) { + if( !view ) + return; + + I32 max_scroll = gui_editor_toolview_max_scroll( view ); + view->scroll = std::min( std::max( 0, view->scroll ), max_scroll ); + if( view->itemview ) { + view->itemview->y = TOOLVIEW_TITLE_OFFSET - view->scroll; + } +} + void gui_editor_toolview_sanitize_float_input( void* ptr ) { GUI_FLOATINPUT* input = (GUI_FLOATINPUT*)ptr; F32* pval = input->pval; @@ -23,7 +45,6 @@ void gui_editor_toolview_sanitize_float_input( void* ptr ) { if( !isfinite( *pval ) ) *pval = EDITOR_DEFAULT_POLY_SIDES; F32 rounded = floorf( *pval + 0.5f ); if( rounded < EDITOR_POLY_SIDES_MIN ) rounded = EDITOR_POLY_SIDES_MIN; - if( rounded > EDITOR_POLY_SIDES_MAX ) rounded = EDITOR_POLY_SIDES_MAX; *pval = rounded; return; } @@ -148,7 +169,7 @@ I32 gui_editor_toolview_create_poly_sides_input( GUI_EDITOR_TOOLVIEW* view, I32 "sides", &editor->tool.polysides, (F32)EDITOR_POLY_SIDES_MIN, - (F32)EDITOR_POLY_SIDES_MAX, + INFINITY, 1.f, "%.0f" ); @@ -168,14 +189,15 @@ I32 gui_editor_toolview_create_wall_height_input( GUI_EDITOR_TOOLVIEW* view, I32 } I32 gui_editor_toolview_create_placement_height_input( GUI_EDITOR_TOOLVIEW* view, I32 y ) { + F32 step = isfinite( editor->grid ) && editor->grid > 0.f ? editor->grid : 1.f; return gui_editor_toolview_create_float_input( view, y, "placement z", &editor->tool.placementheight, - -4096.f, - 4096.f, - 1.f, + -INFINITY, + INFINITY, + step, "%.0f" ); } @@ -204,6 +226,8 @@ void gui_editor_toolview_update( GUI_EDITOR_TOOLVIEW* view ) { if( editor->tool.type == EDITOR_TOOL_WALL ) { y = gui_editor_toolview_create_wallshape_dropdown( view, y ); if( view->wallshape_dropdown_open ) { + view->content_h = y + 6; + gui_editor_toolview_clamp_scroll( view ); gui_set_view( oldview ); return; } @@ -220,9 +244,31 @@ void gui_editor_toolview_update( GUI_EDITOR_TOOLVIEW* view ) { y = gui_editor_toolview_create_placement_height_input( view, y ); } + view->content_h = y + 6; + gui_editor_toolview_clamp_scroll( view ); gui_set_view( oldview ); } +void gui_editor_toolview_draw_scrollbar( GUI_EDITOR_TOOLVIEW* view, I32 x, I32 y, I32 h ) { + I32 max_scroll = gui_editor_toolview_max_scroll( view ); + if( max_scroll <= 0 ) + return; + + I32 track_x = x + view->w - TOOLVIEW_SCROLLBAR_W - 3; + I32 track_y = y + 2; + I32 track_h = std::max( 8, h - 4 ); + gui_draw_frect( track_x, track_y, TOOLVIEW_SCROLLBAR_W, track_h, ui_clr.bg_alt ); + gui_draw_rect( track_x, track_y, TOOLVIEW_SCROLLBAR_W, track_h, ui_clr.border ); + + I32 visible_h = gui_editor_toolview_visible_h( view ); + I32 thumb_h = std::max( TOOLVIEW_SCROLLBAR_MIN_H, ( track_h * visible_h ) / std::max( 1, view->content_h ) ); + thumb_h = std::min( thumb_h, track_h ); + I32 travel = std::max( 1, track_h - thumb_h ); + I32 thumb_y = track_y + ( travel * view->scroll ) / std::max( 1, max_scroll ); + + gui_draw_frect( track_x + 1, thumb_y + 1, TOOLVIEW_SCROLLBAR_W - 2, std::max( 1, thumb_h - 2 ), ui_clr.txt ); +} + void gui_editor_toolview_draw_fn( void* ptr ) { if( !editor->map ) return; @@ -233,16 +279,52 @@ void gui_editor_toolview_draw_fn( void* ptr ) { I32 w = view->w; I32 h = view->h; - STR<64> title{}; - gui_editor_toolview_get_title( view, title ); - gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, title ); + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "contextual tool options" ); y += TOOLVIEW_TITLE_OFFSET; CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive; gui_draw_frect( x, y, w, h, col ); gui_draw_frect( x+1, y+1, w-2, h-2, ui_clr.bg_sec ); + gui_editor_toolview_clamp_scroll( view ); + I32 max_scroll = gui_editor_toolview_max_scroll( view ); + I32 clip_w = w - 4; + if( max_scroll > 0 ) + clip_w -= TOOLVIEW_SCROLLBAR_W + 2; + + gui_draw_push_clip( x + 2, y + 2, std::max( 1, clip_w ), std::max( 1, h - 4 ) ); view->itemview->draw_fn( view->itemview ); + gui_draw_pop_clip(); + gui_editor_toolview_draw_scrollbar( view, x, y, h ); +} + +void gui_editor_toolview_input_fn( void* ptr ) { + GUI_EDITOR_TOOLVIEW* view = (GUI_EDITOR_TOOLVIEW*)ptr; + gui_base_input_fn( ptr ); + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ) + TOOLVIEW_TITLE_OFFSET; + I32 w = view->w; + I32 h = view->h; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + + if( inbounds ) { + U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL ); + if( scroll && gui_editor_toolview_max_scroll( view ) > 0 ) { + if( scroll == 1 ) + view->scroll -= TOOLVIEW_SCROLL_STEP; + else if( scroll == (U8)-1 ) + view->scroll += TOOLVIEW_SCROLL_STEP; + + gui_editor_toolview_clamp_scroll( view ); + gui_capture_scroll(); + } + } + + gui_editor_toolview_clamp_scroll( view ); } GUI_EDITOR_TOOLVIEW* gui_editor_toolview( I32 x, I32 y, I32 w, I32 h ) { @@ -257,8 +339,10 @@ GUI_EDITOR_TOOLVIEW* gui_editor_toolview( I32 x, I32 y, I32 w, I32 h ) { view->ybound = view->h + TOOLVIEW_TITLE_OFFSET; strcpy( view->name, "EDITOR_PROP_VIEW" ); view->draw_fn = gui_editor_toolview_draw_fn; - view->input_fn = gui_base_input_fn; + view->input_fn = gui_editor_toolview_input_fn; view->wallshape_dropdown_open = 0; + view->scroll = 0; + view->content_h = 0; GUI_VIEW* parent = gui_get_view(); parent->children.push( view ); @@ -266,6 +350,7 @@ GUI_EDITOR_TOOLVIEW* gui_editor_toolview( I32 x, I32 y, I32 w, I32 h ) { gui_set_view( view ); view->itemview = gui_view( 0, TOOLVIEW_TITLE_OFFSET, w, h ); + view->itemview->initheld = 1; gui_set_view( parent ); diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp index 92988df..6814fd2 100644 --- a/src/editor/view2d.cpp +++ b/src/editor/view2d.cpp @@ -8,8 +8,43 @@ const I32 EDITORVIEW_TITLE_OFFSET = 15; const I32 EDITORVIEW_GUTTERS_OFFSETX = 22; const I32 EDITORVIEW_GUTTERS_OFFSETY = 16; const I32 EDITORVIEW_TOOLBAR_OFFSET = 20; +const I32 EDITOR_POLY_SIDES_RUNTIME_MAX = 256; VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ); +VEC2 gui_editor_2dview_world_to_screen( GUI_EDITOR_2DVIEW* view, VEC2 world ); +F32 gui_editor_2dview_placement_z(); + +VEC3 gui_editor_2dview_plane_to_world3( VEC2 plane, F32 depth ) { + return { plane.x, plane.y, depth }; +} + +void gui_editor_2dview_set_plane( VEC3* v, VEC2 plane ) { + if( !v ) + return; + + v->x = plane.x; + v->y = plane.y; +} + +void gui_editor_2dview_add_plane_delta( VEC3* v, VEC2 delta ) { + if( !v ) + return; + + v->x += delta.x; + v->y += delta.y; +} + +void gui_editor_2dview_snap_plane( VEC3* v ) { + if( !v ) + return; + + v->x = m_snap_to_grid( v->x, editor->grid ); + v->y = m_snap_to_grid( v->y, editor->grid ); +} + +VEC2 gui_editor_2dview_world3_to_screen( GUI_EDITOR_2DVIEW* view, const VEC3& v ) { + return gui_editor_2dview_world_to_screen( view, { v.x, v.y } ); +} F32 gui_editor_2dview_ground_z() { WORLD_MAP* map = editor->map; @@ -177,9 +212,16 @@ void gui_editor_2dview_draw_gizmo( I32 x, I32 y, CLR clr, U8 selected = 0 ) { } I32 gui_editor_2dview_poly_sides() { - I32 sides = (I32)floorf( editor->tool.polysides + 0.5f ); + F32 raw = editor->tool.polysides; + if( !isfinite( raw ) ) + raw = EDITOR_DEFAULT_POLY_SIDES; + + if( raw > (F32)EDITOR_POLY_SIDES_RUNTIME_MAX ) + raw = (F32)EDITOR_POLY_SIDES_RUNTIME_MAX; + + I32 sides = (I32)floorf( raw + 0.5f ); if( sides < EDITOR_POLY_SIDES_MIN ) sides = EDITOR_POLY_SIDES_MIN; - if( sides > EDITOR_POLY_SIDES_MAX ) sides = EDITOR_POLY_SIDES_MAX; + if( sides > EDITOR_POLY_SIDES_RUNTIME_MAX ) sides = EDITOR_POLY_SIDES_RUNTIME_MAX; return sides; } @@ -199,7 +241,7 @@ U8 gui_editor_2dview_is_poly_drag_tool() { } I32 gui_editor_2dview_poly_points( VEC2 start, VEC2 end, I32 sides, U8 regular, VEC2* out_points ) { - if( !out_points || sides < EDITOR_POLY_SIDES_MIN || sides > EDITOR_POLY_SIDES_MAX ) + if( !out_points || sides < EDITOR_POLY_SIDES_MIN ) return 0; F32 minx = start.x < end.x ? start.x : end.x; @@ -246,7 +288,7 @@ struct EDITORVIEW_DRAG_RECT { struct EDITORVIEW_DRAG_SHAPE { EDITORVIEW_DRAG_RECT rect; - VEC2 points[EDITOR_POLY_SIDES_MAX]; + LIST points; I32 pointc; }; @@ -266,13 +308,14 @@ U8 gui_editor_2dview_build_drag_shape( GUI_EDITOR_2DVIEW* view, EDITORVIEW_DRAG_ return 0; I32 sides = gui_editor_2dview_poly_sides(); + out_shape->points.resize( sides ); U8 regular = gui_editor_2dview_poly_keep_regular( sides ); out_shape->pointc = gui_editor_2dview_poly_points( view->poly_start, view->poly_end, sides, regular, - out_shape->points + out_shape->points.data ); return out_shape->pointc >= 3; @@ -371,15 +414,16 @@ void gui_editor_2dview_create_poly_from_drag( GUI_EDITOR_2DVIEW* view ) { MAP_POLYGON newp{}; newp.propid = 0; newp.type = MPT_FLOOR; - F32 placez = gui_editor_2dview_placement_z(); + F32 depth = gui_editor_2dview_placement_z(); F32 invw = 1.f / shape.rect.w; F32 invh = 1.f / shape.rect.h; for( I32 i = 0; i < shape.pointc; ++i ) { VEC2 p = shape.points[i]; + VEC3 world = gui_editor_2dview_plane_to_world3( p, depth ); MAP_VERTEX v{}; - v.pos = { p.x, p.y, placez }; + v.pos = world; v.uv = { ( p.x - shape.rect.minx ) * invw, ( shape.rect.maxy - p.y ) * invh @@ -400,15 +444,18 @@ void gui_editor_2dview_create_wallpoly_from_drag( GUI_EDITOR_2DVIEW* view ) { I32 propid = gui_editor_2dview_find_or_create_wallpoly_propid(); - F32 basez = gui_editor_2dview_placement_z(); + F32 depth = gui_editor_2dview_placement_z(); F32 wallheight = gui_editor_2dview_wall_height(); for( I32 i = 0; i < shape.pointc; ++i ) { VEC2 p0 = shape.points[i]; VEC2 p1 = shape.points[(i + 1) % shape.pointc]; + VEC3 w0 = gui_editor_2dview_plane_to_world3( p0, depth ); + VEC3 w1 = gui_editor_2dview_plane_to_world3( p1, depth ); MAP_WALL wall{}; - wall.start = { p0.x, p0.y, basez }; - wall.end = { p1.x, p1.y, wallheight }; + wall.start = w0; + wall.end = w1; + wall.end.z = wallheight; wall.propid = propid; editor->map->walls.push( wall ); } @@ -420,6 +467,10 @@ void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; F32 w = view->w; F32 h = view->h; + F32 min_u = m->mins.x; + F32 max_u = m->maxs.x; + F32 min_v = m->mins.y; + F32 max_v = m->maxs.y; gui_draw_str( x + 2, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "%1.f,%1.f", view->posx, view->posy ); for( U32 i = 1; i <= 5; ++i ) { @@ -428,7 +479,7 @@ void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { F32 tp = (tx - x) / w; - F32 scaledx = m->mins.x + (m->maxs.x - m->mins.x) * tp; + F32 scaledx = min_u + ( max_u - min_u ) * tp; if( w > h ) scaledx *= ( w / h ); scaledx /= view->scale; @@ -443,7 +494,7 @@ void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { F32 tp = (ty - y) / h; - F32 scaledy = m->mins.y + (m->maxs.y - m->mins.y) * tp; + F32 scaledy = min_v + ( max_v - min_v ) * tp; if( w < h ) scaledy *= h / w; scaledy /= view->scale; @@ -456,19 +507,19 @@ void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; - F32 scale = gui_editor_2dview_calc_scale( view ); - F32 xoff = m->mins.x + view->posx; - F32 yoff = m->mins.y + view->posy; - m->polygons.each( fn( MAP_POLYGON* p ) { + SURF_PROPS fallback_props{ .tex = 0, .clr = CLR::WHITE() }; SURF_PROPS* props = polygon_get_props( m, p ); + if( !props ) + props = &fallback_props; if( !editor->wireframe ) { LIST vertices; p->vertices.each( fn( MAP_VERTEX* v ) { + VEC2 screen = gui_editor_2dview_world3_to_screen( view, v->pos ); VERTEX v2; v2.uv = v->uv; - v2.pos.x = x + (v->pos.x - xoff) * scale; - v2.pos.y = y + (v->pos.y - yoff) * scale; + v2.pos.x = screen.x; + v2.pos.y = screen.y; v2.clr = props->clr; vertices.push( v2 ); } ); @@ -482,11 +533,13 @@ void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { for( U32 i = 0; i < p->vertices.size; ++i ) { MAP_VERTEX* v1 = &p->vertices[i]; MAP_VERTEX* v2 = &p->vertices[(i+1) % p->vertices.size]; + VEC2 s0 = gui_editor_2dview_world3_to_screen( view, v1->pos ); + VEC2 s1 = gui_editor_2dview_world3_to_screen( view, v2->pos ); - I32 x0 = x + (I32)( (v1->pos.x - xoff) * scale ); - I32 y0 = y + (I32)( (v1->pos.y - yoff) * scale ); - I32 x1 = x + (I32)( (v2->pos.x - xoff) * scale ); - I32 y1 = y + (I32)( (v2->pos.y - yoff) * scale ); + I32 x0 = (I32)s0.x; + I32 y0 = (I32)s0.y; + I32 x1 = (I32)s1.x; + I32 y1 = (I32)s1.y; gui_draw_line( x0, y0, x1, y1, props->clr ); } @@ -496,8 +549,9 @@ void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { return; // draw gizmos p->vertices.each( fn( MAP_VERTEX* v ) { - I32 vx = (I32)( x + (v->pos.x - xoff) * scale ); - I32 vy = (I32)( y + (v->pos.y - yoff) * scale ); + VEC2 screen = gui_editor_2dview_world3_to_screen( view, v->pos ); + I32 vx = (I32)screen.x; + I32 vy = (I32)screen.y; U8 selected = gui_editor_2dview_is_gizmo_active( view, v, EDITOR_SELECT_PVERTEX ) || gui_editor_2dview_is_gizmo_active( view, p, EDITOR_SELECT_POLY ); @@ -509,17 +563,20 @@ void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { void gui_editor_2dview_draw_walls( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; - F32 scale = gui_editor_2dview_calc_scale( view ); - F32 xoff = m->mins.x + view->posx; - F32 yoff = m->mins.y + view->posy; - m->walls.each( fn( MAP_WALL* s ) { + SURF_PROPS fallback_props{ .tex = 0, .clr = CLR::WHITE() }; SURF_PROPS* props = wall_get_props( m, s ); - - I32 x0 = (I32)( x + (s->start.x - xoff) * scale ); - I32 y0 = (I32)( y + (s->start.y - yoff) * scale ); - I32 x1 = (I32)( x + (s->end.x - xoff) * scale ); - I32 y1 = (I32)( y + (s->end.y - yoff) * scale ); + if( !props ) + props = &fallback_props; + VEC3 w0 = { s->start.x, s->start.y, s->start.z }; + VEC3 w1 = { s->end.x, s->end.y, s->end.z }; + VEC2 p0 = gui_editor_2dview_world3_to_screen( view, w0 ); + VEC2 p1 = gui_editor_2dview_world3_to_screen( view, w1 ); + + I32 x0 = (I32)p0.x; + I32 y0 = (I32)p0.y; + I32 x1 = (I32)p1.x; + I32 y1 = (I32)p1.y; gui_draw_line( x0, y0, x1, y1, props->clr ); if( gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL ) ) { @@ -535,10 +592,14 @@ void gui_editor_2dview_draw_walls( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { // gizmos m->walls.each( fn( MAP_WALL* s ) { - I32 x0 = (I32)( x + (s->start.x - xoff) * scale ); - I32 y0 = (I32)( y + (s->start.y - yoff) * scale ); - I32 x1 = (I32)( x + (s->end.x - xoff) * scale ); - I32 y1 = (I32)( y + (s->end.y - yoff) * scale ); + VEC3 w0 = { s->start.x, s->start.y, s->start.z }; + VEC3 w1 = { s->end.x, s->end.y, s->end.z }; + VEC2 p0 = gui_editor_2dview_world3_to_screen( view, w0 ); + VEC2 p1 = gui_editor_2dview_world3_to_screen( view, w1 ); + I32 x0 = (I32)p0.x; + I32 y0 = (I32)p0.y; + I32 x1 = (I32)p1.x; + I32 y1 = (I32)p1.y; U8 sel = gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL ); U8 sel1 = gui_editor_2dview_is_gizmo_active( view, &s->start, EDITOR_SELECT_WVERTEX ); @@ -552,19 +613,12 @@ void gui_editor_2dview_draw_walls( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { void gui_editor_2dview_draw_sprites( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; - F32 scale = gui_editor_2dview_calc_scale( view ); - F32 xoff = m->mins.x + view->posx; - F32 yoff = m->mins.y + view->posy; - m->sprites.each( fn( MAP_SPRITE* s ) { F32 wantedsize = editor->spritesize; F32 aspect = s->size.x / s->size.y; F32 w = (aspect > 1.0f)? wantedsize : wantedsize * aspect; F32 h = (aspect < 1.0f)? wantedsize : wantedsize / aspect; - VEC2 pos = { - (F32)x + (s->pos.x - xoff) * scale, - (F32)y + (s->pos.y - yoff) * scale - }; + VEC2 pos = gui_editor_2dview_world3_to_screen( view, s->pos ); U8 sel= gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_SPRITE ); if( sel ) { @@ -582,27 +636,18 @@ void gui_editor_2dview_draw_sprites( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { } void gui_editor_2dview_draw_player( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { - WORLD_MAP* m = editor->map; if( !objl->pl ) return; - F32 scale = gui_editor_2dview_calc_scale( view ); - F32 xoff = m->mins.x + view->posx; - F32 yoff = m->mins.y + view->posy; - VEC3 pos = objl->pl->pos; - - VEC2 pos2d = { - (F32)x + (pos.x - xoff) * scale, - (F32)y + (pos.y - yoff) * scale - }; + VEC2 pos2d = gui_editor_2dview_world3_to_screen( view, pos ); F32 yaw = objl->pl->rot.y; VEC2 dir = m_radial_offset( yaw, 20.f ); VEC2 ray = pos2d + dir; - gui_draw_line( (I32)pos2d.x, (I32)pos2d.y, (I32)ray.x, (I32)ray.y, CLR::GREEN() ); + gui_draw_frect( (I32)pos2d.x-4, (I32)pos2d.y-4, 8, 8, CLR::WHITE() ); gui_draw_frect( (I32)pos2d.x-3, (I32)pos2d.y-3, 6, 6, CLR::RED() ); } @@ -610,13 +655,10 @@ void gui_editor_2dview_draw_player( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { void gui_editor_2dview_draw_origin( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; - F32 scale = gui_editor_2dview_calc_scale( view ); - F32 xoff = m->mins.x + view->posx; - F32 yoff = m->mins.y + view->posy; - VEC3 pos = m->startpos; - I32 screenx = (I32)( x + (pos.x - xoff) * scale ); - I32 screeny = (I32)( y + (pos.y - yoff) * scale ); + VEC2 screen = gui_editor_2dview_world3_to_screen( view, pos ); + I32 screenx = (I32)screen.x; + I32 screeny = (I32)screen.y; if( gui_editor_2dview_is_gizmo_active( view, m, EDITOR_SELECT_ORIGIN ) ) { I32 w, h; @@ -758,15 +800,13 @@ void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) { void gui_editor_2dview_input_select_drag_wall( GUI_EDITOR_2DVIEW* view ) { MAP_WALL* s = (MAP_WALL*)view->curdrag; - s->start.x = m_snap_to_grid( s->start.x, editor->grid ); - s->start.y = m_snap_to_grid( s->start.y, editor->grid ); - s->end.x = m_snap_to_grid( s->end.x, editor->grid ); - s->end.y = m_snap_to_grid( s->end.y, editor->grid ); + gui_editor_2dview_snap_plane( &s->start ); + gui_editor_2dview_snap_plane( &s->end ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { - s->start += mv; - s->end += mv; + gui_editor_2dview_add_plane_delta( &s->start, mv ); + gui_editor_2dview_add_plane_delta( &s->end, mv ); map_check_bounds( editor->map ); gui_editor_2dview_input_select_onmove( view ); @@ -774,13 +814,25 @@ void gui_editor_2dview_input_select_drag_wall( GUI_EDITOR_2DVIEW* view ) { } void gui_editor_2dview_input_select_drag_vertex( GUI_EDITOR_2DVIEW* view ) { - VEC2* v = (VEC2*)view->curdrag; - v->x = m_snap_to_grid( v->x, editor->grid ); - v->y = m_snap_to_grid( v->y, editor->grid ); + if( view->dragtype == EDITOR_SELECT_WVERTEX ) { + VEC3* v = (VEC3*)view->curdrag; + gui_editor_2dview_snap_plane( v ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + gui_editor_2dview_add_plane_delta( v, mv ); + map_check_bounds( editor->map ); + gui_editor_2dview_input_select_onmove( view ); + } + return; + } + + MAP_VERTEX* v = (MAP_VERTEX*)view->curdrag; + gui_editor_2dview_snap_plane( &v->pos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { - *v += mv; + gui_editor_2dview_add_plane_delta( &v->pos, mv ); map_check_bounds( editor->map ); gui_editor_2dview_input_select_onmove( view ); @@ -793,11 +845,8 @@ void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view ) { VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { p->vertices.each( fn( MAP_VERTEX* v ) { - v->pos.x = m_snap_to_grid( v->pos.x, editor->grid ); - v->pos.y = m_snap_to_grid( v->pos.y, editor->grid ); - - v->pos.x += mv.x; - v->pos.y += mv.y; + gui_editor_2dview_snap_plane( &v->pos ); + gui_editor_2dview_add_plane_delta( &v->pos, mv ); } ); map_polygon_calc_bounds( p ); @@ -809,12 +858,11 @@ void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view ) { void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view ) { MAP_SPRITE* s = (MAP_SPRITE*)view->curdrag; - s->pos.x = m_snap_to_grid( s->pos.x, editor->grid ); - s->pos.y = m_snap_to_grid( s->pos.y, editor->grid ); + gui_editor_2dview_snap_plane( &s->pos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { - s->pos += mv; + gui_editor_2dview_add_plane_delta( &s->pos, mv ); gui_editor_2dview_input_select_onmove( view ); } } @@ -822,12 +870,11 @@ void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view ) { void gui_editor_2dview_input_select_drag_origin( GUI_EDITOR_2DVIEW* view ) { WORLD_MAP* m = editor->map; - m->startpos.x = m_snap_to_grid( m->startpos.x, editor->grid ); - m->startpos.y = m_snap_to_grid( m->startpos.y, editor->grid ); + gui_editor_2dview_snap_plane( &m->startpos ); VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); if( !is_zero( mv ) ) { - m->startpos += mv; + gui_editor_2dview_add_plane_delta( &m->startpos, mv ); gui_editor_2dview_input_select_onmove( view ); } } @@ -890,10 +937,10 @@ void gui_editor_2dview_input_select_walls( GUI_EDITOR_2DVIEW* view ) { for( U32 i = 0; i < m->walls.size; ++i ) { MAP_WALL* w = &m->walls[i]; - VEC2 w1 = { w->start.x, w->start.y }; - VEC2 w2 = { w->end.x, w->end.y }; - w1 = gui_editor_2dview_world_to_screen( view, w1 ); - w2 = gui_editor_2dview_world_to_screen( view, w2 ); + VEC3 wp0 = { w->start.x, w->start.y, w->start.z }; + VEC3 wp1 = { w->end.x, w->end.y, w->end.z }; + VEC2 w1 = gui_editor_2dview_world3_to_screen( view, wp0 ); + VEC2 w2 = gui_editor_2dview_world3_to_screen( view, wp1 ); F32 d1 = vec_dist( mpos, w1 ); F32 d2 = vec_dist( mpos, w2 ); @@ -938,8 +985,7 @@ void gui_editor_2dview_input_select_polygons( GUI_EDITOR_2DVIEW* view ) { LIST plist; p->vertices.each( fn( MAP_VERTEX* v ) { - VEC2 world = { v->pos.x, v->pos.y }; - VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + VEC2 screen = gui_editor_2dview_world3_to_screen( view, v->pos ); F32 d = vec_dist( mpos, screen ); if( d < mindist ) { gui_editor_2dview_select( view, v, EDITOR_SELECT_PVERTEX ); @@ -975,8 +1021,7 @@ void gui_editor_2dview_input_select_sprites( GUI_EDITOR_2DVIEW* view ) { for( U32 i = 0; i < m->sprites.size; ++i ) { MAP_SPRITE* s = &m->sprites[i]; - VEC2 world = { s->pos.x, s->pos.y }; - VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + VEC2 screen = gui_editor_2dview_world3_to_screen( view, s->pos ); if( mpos.x >= screen.x - hsize && mpos.x <= screen.x + hsize && mpos.y >= screen.y - hsize && mpos.y <= screen.y + hsize ) @@ -1002,8 +1047,7 @@ void gui_editor_2dview_input_select_origin( GUI_EDITOR_2DVIEW* view ) { gui_cursor_pos( &mx, &my ); VEC2 mpos = { (F32)mx, (F32)my }; - VEC2 world = { m->startpos.x, m->startpos.y }; - VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + VEC2 screen = gui_editor_2dview_world3_to_screen( view, m->startpos ); I32 w, h; gui_draw_get_str_bounds( &w, &h, FNT_JPN12, "(origin)" ); @@ -1109,13 +1153,16 @@ void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) { VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); if( !view->held ) { - F32 basez = gui_editor_2dview_placement_z(); + F32 depth = gui_editor_2dview_placement_z(); + VEC3 wp = gui_editor_2dview_plane_to_world3( world, depth ); MAP_WALL neww; neww.start = neww.end = { - m_snap_to_grid( world.x, editor->grid ), - m_snap_to_grid( world.y, editor->grid ), - basez + wp.x, + wp.y, + wp.z }; + gui_editor_2dview_snap_plane( &neww.start ); + gui_editor_2dview_snap_plane( &neww.end ); neww.end.z = gui_editor_2dview_wall_height(); neww.propid = 0; @@ -1133,8 +1180,8 @@ void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) { VEC3* end = (VEC3*)view->curdrag; if( end ) { - end->x = m_snap_to_grid( world.x, editor->grid ); - end->y = m_snap_to_grid( world.y, editor->grid ); + gui_editor_2dview_set_plane( end, world ); + gui_editor_2dview_snap_plane( end ); } } @@ -1180,13 +1227,15 @@ void gui_editor_2dview_input_tool_sprite( GUI_EDITOR_2DVIEW* view ) { VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); if( !view->held ) { + VEC3 wpos = gui_editor_2dview_plane_to_world3( world, gui_editor_2dview_placement_z() ); MAP_SPRITE news; - news.pos = { world.x, world.y, 20.f }; + news.pos = wpos; + gui_editor_2dview_snap_plane( &news.pos ); news.size = { 20.f, 20.f }; news.clr = { 1.f, 1.f, 1.f, 1.f }; news.tex = 0; - I32 idx = editor->map->walls.size; + I32 idx = editor->map->sprites.size; editor->map->sprites.push( news ); view->curdrag = &editor->map->sprites[idx]; view->held = 1; @@ -1344,30 +1393,44 @@ void gui_editor_2dview_input_fn( void* ptr ) { } } +static U8 GRID_DEP_LABEL_TAG; +static U8 GRID_DEP_PROPVIEW_TAG; +static U8 GRID_DEP_TOOLVIEW_TAG; + void update_view_settings( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + if( !egui->gridlabel ) + return; sprintf( egui->gridlabel->name, "grid: %1.2f", e->grid ); } +void editor_grid_dependency_update_label( GAME_EDITOR* e ) { + update_view_settings( e ); +} + +void editor_grid_dependency_update_propview( GAME_EDITOR* e ) { + if( e->propgrid && e->gui.props ) + editor_update_properties_column( e ); +} + +void editor_grid_dependency_update_toolview( GAME_EDITOR* e ) { + if( e->gui.tool ) + gui_editor_toolview_update( e->gui.tool ); +} + void grid_increment_cb( void* ) { if( editor->grid < 16.f ) editor->grid *= 2.f; - update_view_settings( editor ); - - if( editor->propgrid ) - editor_update_properties_column( editor ); + editor_notify_grid_change( editor ); } void grid_decrement_cb( void* ) { if( editor->grid > 0.25f ) editor->grid *= 0.5f; - update_view_settings( editor ); - - if( editor->propgrid ) - editor_update_properties_column( editor ); + editor_notify_grid_change( editor ); } void grid_propgrid_cb( void* ) { - editor_update_properties_column( editor ); + editor_notify_grid_change( editor ); } void gui_editor_2dview_create_toolbar( GUI_EDITOR_2DVIEW* view ) { @@ -1375,6 +1438,9 @@ void gui_editor_2dview_create_toolbar( GUI_EDITOR_2DVIEW* view ) { I32 x = 150, y = view->h - 4; editor->gui.gridlabel = gui_label( x, y + 1, "grid: %1.2f", e->grid ); + editor_register_grid_dependency( e, &GRID_DEP_LABEL_TAG, editor_grid_dependency_update_label ); + editor_register_grid_dependency( e, &GRID_DEP_PROPVIEW_TAG, editor_grid_dependency_update_propview ); + editor_register_grid_dependency( e, &GRID_DEP_TOOLVIEW_TAG, editor_grid_dependency_update_toolview ); x += 70; gui_button( x, y, 18, 18, "+", grid_increment_cb ); gui_button( x + 23, y, 18, 18, "-", grid_decrement_cb ); @@ -1383,6 +1449,8 @@ void gui_editor_2dview_create_toolbar( GUI_EDITOR_2DVIEW* view ) { check->cb = grid_propgrid_cb; x += 120; gui_checkbox( x, y, "wireframe", &e->wireframe ); + + editor_notify_grid_change( e ); } GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) { diff --git a/src/game.cpp b/src/game.cpp index 663681f..2179255 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -18,6 +18,7 @@ #include "render/gl_batch.h" #include "util.h" +static void game_realtime_resize_repaint( void* userdata ); GAME_DATA* game_init( GL_DATA* gl ) { GAME_DATA* game = (GAME_DATA*)malloc( sizeof( GAME_DATA ) ); @@ -45,6 +46,8 @@ GAME_DATA* game_init( GL_DATA* gl ) { game->state.ingame = 1; #endif + gl_set_resize_repaint_callback( gl, game_realtime_resize_repaint, game ); + return game; } @@ -52,7 +55,7 @@ void game_create_batches( GAME_DATA *game ) { game->render.batch_3d = gl_batch_create( game->gl, game->shaders.gl3d, &gl_3d_batch_setup ); } -#ifdef DEBUG +#if defined(DEBUG) && !IS_EDITOR void game_draw_fpsoverlay( GAME_DATA* game ) { GL_DATA* gl = game->gl; char buf[256]; @@ -116,14 +119,65 @@ void game_draw( GAME_DATA* game ) { world_draw( game, objl->world, window, winsize ); } +static void game_realtime_resize_repaint( void* userdata ) { + GAME_DATA* game = (GAME_DATA*)userdata; + if( !game || !game->gl ) + return; + + GL_DATA* gl = game->gl; + GL_SHADER_PROGRAM* gl2d = game->shaders.gl2d; + + if( !assets_on_frame( game ) ) + return; + + gl_sync_window_size( gl ); + +#if IS_EDITOR + if( game->editor && game->editor->wnd ) { + if( game->editor->wnd->w != gl->canvas_size[0] + || game->editor->wnd->h != gl->canvas_size[1] ) { + editor_resize( game->editor, gl->canvas_size[0], gl->canvas_size[1] ); + } + } +#endif + + gl_beginframe( gl ); + gl_2d_frect( gl2d, s_tl(), s_br(), { 0.f, 0.f, 0.f, 1.f } ); + + if( game->state.ingame ) + game_draw( game ); + + gui_draw( game ); + +#if defined(DEBUG) && !IS_EDITOR + game_draw_fpsoverlay( game ); +#endif + + SDL_GL_SwapWindow( gl->window ); +} + void game_main_loop( GAME_DATA* game ) { GL_DATA* gl = game->gl; GL_SHADER_PROGRAM* gl2d = game->shaders.gl2d; + if( !OK( gl_poll_events( gl ) ) ) + exit( 0 ); + if( !assets_on_frame( game ) ) return; + gl_sync_window_size( gl ); gl_beginframe( gl ); + +#if IS_EDITOR + if( game->editor && game->editor->wnd ) { + if( game->editor->wnd->w != gl->canvas_size[0] + || game->editor->wnd->h != gl->canvas_size[1] ) { + editor_resize( game->editor, gl->canvas_size[0], gl->canvas_size[1] ); + } + } +#endif + gl_2d_frect( gl2d, s_tl(), s_br(), { 0.f, 0.f, 0.f, 1.f } ); player_input( game, objl->pl ); @@ -134,7 +188,7 @@ void game_main_loop( GAME_DATA* game ) { gui_onframe( game ); -#ifdef DEBUG +#if defined(DEBUG) && !IS_EDITOR game_draw_fpsoverlay( game ); #endif diff --git a/src/gui/button.cpp b/src/gui/button.cpp index 2b57772..1548e0e 100644 --- a/src/gui/button.cpp +++ b/src/gui/button.cpp @@ -5,15 +5,30 @@ void gui_button_draw_fn( void* ptr ) { I32 x = gui_relx( btn ); I32 y = gui_rely( btn ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + U8 inbounds = mx >= x && mx <= x + btn->w && my >= y && my <= y + btn->h; + U8 active = btn->held && inbounds && gui_mbutton_down( GUI_MBTNLEFT ); + U8 hover = inbounds && !active; + + CLR border = gui_is_fg_window( btn )? ui_clr.border : ui_clr.border_inactive; + if( active ) + border = { 0.f, 1.f, 0.f, 1.f }; + + CLR fill = ui_clr.bg_sec; + CLR txt = ui_clr.txt; + if( hover ) { + fill = ui_clr.border; + txt = CLR::BLACK(); + } - CLR col = gui_is_fg_window( btn )? ui_clr.border : ui_clr.border_inactive; - gui_draw_frect( x, y, btn->w, btn->h, col ); - gui_draw_frect( x+1, y+1, btn->w-2, btn->h-2, ui_clr.bg_sec ); + gui_draw_frect( x, y, btn->w, btn->h, border ); + gui_draw_frect( x+1, y+1, btn->w-2, btn->h-2, fill ); I32 middle = x + btn->w/2; I32 middle_y = y + btn->h/2 - 7; - gui_draw_str( middle, middle_y, ALIGN_C, FNT_JPN12, ui_clr.txt, btn->name ); + gui_draw_str( middle, middle_y, ALIGN_C, FNT_JPN12, txt, btn->name ); } void gui_button_input_fn( void* ptr ) { diff --git a/src/gui/floatinput.cpp b/src/gui/floatinput.cpp index 9f7fdfe..5746758 100644 --- a/src/gui/floatinput.cpp +++ b/src/gui/floatinput.cpp @@ -1,6 +1,60 @@ #include "base.h" #include +const I32 FLOATINPUT_STEPPER_W = 11; + +static U8 gui_floatinput_has_stepper( GUI_FLOATINPUT* input ) { + return input->w >= 46; +} + +static I32 gui_floatinput_content_w( GUI_FLOATINPUT* input ) { + I32 reserve = gui_floatinput_has_stepper( input ) ? ( FLOATINPUT_STEPPER_W + 1 ) : 0; + I32 cw = input->w - reserve; + return cw < 1 ? 1 : cw; +} + +static void gui_floatinput_clamp_and_snap( GUI_FLOATINPUT* input ) { + if( isfinite( input->min ) && *input->pval < input->min ) + *input->pval = input->wraparound ? input->max : input->min; + if( isfinite( input->max ) && *input->pval > input->max ) + *input->pval = input->wraparound ? input->min : input->max; + + F32 step = input->step; + if( !isfinite( step ) || fabsf( step ) < 0.000001f ) + return; + + F32 rmn = remainderf( *input->pval, step ); + *input->pval -= rmn; +} + +static void gui_floatinput_draw_stepper( GUI_FLOATINPUT* input ) { + if( !gui_floatinput_has_stepper( input ) ) + return; + + I32 x = gui_relx( input ); + I32 y = gui_rely( input ); + I32 w = input->w; + I32 h = input->h; + I32 sx = x + w - FLOATINPUT_STEPPER_W; + I32 sy = y + 1; + I32 sh = h - 2; + I32 top_h = sh / 2; + I32 bot_h = sh - top_h; + + CLR border = gui_is_fg_window( input )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( sx, sy, FLOATINPUT_STEPPER_W, sh, border ); + gui_draw_frect( sx + 1, sy + 1, FLOATINPUT_STEPPER_W - 2, sh - 2, ui_clr.bg_alt ); + gui_draw_line( sx + 1, sy + top_h, sx + FLOATINPUT_STEPPER_W - 1, sy + top_h, border ); + + I32 th = 0; + gui_draw_get_str_bounds( 0, &th, FNT_JPN12, "+" ); + I32 plus_y = sy + ( top_h - th ) / 2; + I32 minus_y = sy + top_h + ( bot_h - th ) / 2; + I32 cx = sx + FLOATINPUT_STEPPER_W / 2; + gui_draw_str( cx, plus_y, ALIGN_C, FNT_JPN12, ui_clr.txt, "+" ); + gui_draw_str( cx, minus_y, ALIGN_C, FNT_JPN12, ui_clr.txt, "-" ); +} + U8 gui_floatinput_is_bound_val( GUI_FLOATINPUT* input ) { if( input->wraparound ) return 0; @@ -26,7 +80,7 @@ CLR gui_floatinput_get_progress_clr( GUI_FLOATINPUT* input ) { void gui_floatinput_draw_bound( GUI_FLOATINPUT* input ) { I32 x = gui_relx( input ); I32 y = gui_rely( input ); - I32 w = input->w; + I32 w = gui_floatinput_content_w( input ); I32 h = input->h; F32 min = input->min; @@ -52,7 +106,7 @@ void gui_floatinput_draw_bound( GUI_FLOATINPUT* input ) { void gui_floatinput_draw_unbound( GUI_FLOATINPUT* input ) { I32 x = gui_relx( input ); I32 y = gui_rely( input ); - I32 w = input->w; + I32 w = gui_floatinput_content_w( input ); F32 val = *input->pval; @@ -104,6 +158,8 @@ void gui_floatinput_draw_fn( void* ptr ) { gui_floatinput_draw_unbound( input ); else gui_floatinput_draw_bound( input ); + + gui_floatinput_draw_stepper( input ); } void gui_floatinput_input_bound( GUI_FLOATINPUT* input ) { @@ -111,7 +167,7 @@ void gui_floatinput_input_bound( GUI_FLOATINPUT* input ) { return; I32 x = gui_relx( input ); - I32 w = input->w; + I32 w = gui_floatinput_content_w( input ); F32 min = input->min; F32 max = input->max; @@ -181,6 +237,7 @@ void gui_floatinput_input_fn( void* ptr ) { GUI_FLOATINPUT* input = (GUI_FLOATINPUT*)ptr; I32 m1 = gui_mbutton_down( 0 ); + I32 m2 = gui_mbutton_down( 1 ); I32 x = gui_relx( input ); I32 y = gui_rely( input ); @@ -190,10 +247,34 @@ void gui_floatinput_input_fn( void* ptr ) { I32 mx, my; gui_cursor_pos( &mx, &my ); U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + U8 has_stepper = gui_floatinput_has_stepper( input ); + I32 step_x = x + w - FLOATINPUT_STEPPER_W; + U8 in_stepper = has_stepper && inbounds && mx >= step_x; if( inbounds ) gui_floatinput_input_scroll( input ); + if( in_stepper ) { + if( !m1 && !m2 ) { + input->held = 0; + input->heldoutbounds = 0; + return; + } + + if( !input->held ) { + U8 fine = !!m2; + I32 split_y = y + 1 + ( h - 2 ) / 2; + U8 inc = my < split_y; + F32 step = fine ? 0.1f : 1.f; + *input->pval += inc ? step : -step; + gui_floatinput_clamp_and_snap( input ); + if( input->cb ) + input->cb( input ); + input->held = 1; + } + return; + } + if( !input->held && m1 && !inbounds ) input->heldoutbounds = 1; if( !input->heldoutbounds && m1 && inbounds ) { @@ -218,6 +299,7 @@ void gui_floatinput_input_fn( void* ptr ) { gui_floatinput_input_unbound( input ); else gui_floatinput_input_bound( input ); + gui_floatinput_clamp_and_snap( input ); if( input->cb && oldv != *input->pval ) { input->cb( input ); diff --git a/src/render/gl.cpp b/src/render/gl.cpp index e44a165..d18cd53 100644 --- a/src/render/gl.cpp +++ b/src/render/gl.cpp @@ -8,6 +8,10 @@ #include "../util.h" I32 SAMPLER_INDICES[255]; +const I32 GL_WINDOW_MIN_W = 800; +const I32 GL_WINDOW_MIN_H = 600; +const I32 GL_WINDOW_ASPECT_W = 4; +const I32 GL_WINDOW_ASPECT_H = 3; GL_DATA* gl_inst; @@ -15,6 +19,128 @@ GL_DATA* gl_instance() { return gl_inst; } +static void gl_apply_canvas_size( GL_DATA* gl, I32 width, I32 height ); +static void gl_handle_window_size_change( GL_DATA* gl, I32 width, I32 height ); +static void gl_update_screen_ratio_uniforms( GL_DATA* gl, I32 width, I32 height ); + +static void gl_query_canvas_size( GL_DATA* gl, I32* width, I32* height ) { + I32 w = 0; + I32 h = 0; + if( gl && gl->window ) { + SDL_GL_GetDrawableSize( gl->window, &w, &h ); + if( w <= 0 || h <= 0 ) + SDL_GetWindowSize( gl->window, &w, &h ); + } + + if( width ) *width = max( 1, w ); + if( height ) *height = max( 1, h ); +} + +static void gl_constrain_window_size( I32* width, I32* height ) { + if( !width || !height ) + return; + + I32 in_w = max( GL_WINDOW_MIN_W, *width ); + I32 in_h = max( GL_WINDOW_MIN_H, *height ); + + I32 keep_w_h = (I32)( ( (I64)in_w * GL_WINDOW_ASPECT_H + GL_WINDOW_ASPECT_W / 2 ) / GL_WINDOW_ASPECT_W ); + keep_w_h = max( GL_WINDOW_MIN_H, keep_w_h ); + + I32 keep_h_w = (I32)( ( (I64)in_h * GL_WINDOW_ASPECT_W + GL_WINDOW_ASPECT_H / 2 ) / GL_WINDOW_ASPECT_H ); + keep_h_w = max( GL_WINDOW_MIN_W, keep_h_w ); + + I32 d_keep_w = abs( keep_w_h - in_h ); + I32 d_keep_h = abs( keep_h_w - in_w ); + + if( d_keep_w <= d_keep_h ) { + *width = in_w; + *height = keep_w_h; + } else { + *width = keep_h_w; + *height = in_h; + } +} + +static void gl_handle_window_size_change( GL_DATA* gl, I32 width, I32 height ) { + if( !gl || !gl->window ) + return; + + I32 target_w = width; + I32 target_h = height; + if( target_w <= 0 || target_h <= 0 ) + SDL_GetWindowSize( gl->window, &target_w, &target_h ); + + gl_constrain_window_size( &target_w, &target_h ); + + I32 wnd_w, wnd_h; + SDL_GetWindowSize( gl->window, &wnd_w, &wnd_h ); + if( ( target_w != wnd_w || target_h != wnd_h ) && !gl->window_constraint_active ) { + gl->window_constraint_active = 1; + SDL_SetWindowSize( gl->window, target_w, target_h ); + gl->window_constraint_active = 0; + } + + I32 draw_w, draw_h; + gl_query_canvas_size( gl, &draw_w, &draw_h ); + gl_apply_canvas_size( gl, draw_w, draw_h ); +} + +static I32 SDLCALL gl_event_filter( void* userdata, SDL_Event* e ) { + GL_DATA* gl = (GL_DATA*)userdata; + if( !gl || !e ) + return 1; + + if( e->type == SDL_WINDOWEVENT ) { + if( e->window.event == SDL_WINDOWEVENT_SIZE_CHANGED + || e->window.event == SDL_WINDOWEVENT_RESIZED ) { + gl_handle_window_size_change( gl, e->window.data1, e->window.data2 ); + + if( gl->resize_repaint_cb && !gl->resize_repaint_active ) { + gl->resize_repaint_active = 1; + gl->resize_repaint_cb( gl->resize_repaint_userdata ); + gl->resize_repaint_active = 0; + } + } + } + + return 1; +} + +static void gl_apply_canvas_size( GL_DATA* gl, I32 width, I32 height ) { + width = max( 1, width ); + height = max( 1, height ); + + gl->canvas_size[0] = width; + gl->canvas_size[1] = height; + if( canvas ) { + canvas[0] = width; + canvas[1] = height; + } + + gl->clip_start = { 0.f, 0.f }; + gl->clip_dim = { (F32)width, (F32)height }; + + gl_update_screen_ratio_uniforms( gl, width, height ); + + glViewport( 0, 0, width, height ); + glUseProgram( 0 ); +} + +static void gl_update_screen_ratio_uniforms( GL_DATA* gl, I32 width, I32 height ) { + F32 screen_ratio[] = { + 2.f / (F32)max( 1, width ), + 2.f / (F32)max( 1, height ), + 1.f, + 1.f + }; + + gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { + glUseProgram( (*it)->id ); + I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" ); + glUniform4fv( ratio_location, 1, &screen_ratio[0] ); + } ); +} + GL_DATA* gl_create( I32* _canvas ) { if( gl_inst ) { dlog( "gl_create() : fatal: gl instance already exists\n" ); @@ -24,6 +150,10 @@ GL_DATA* gl_create( I32* _canvas ) { if( !font_mutex.align ) thread_mutex_init( &font_mutex ); + SDL_SetHint( SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, "1" ); + SDL_SetHint( SDL_HINT_WINDOWS_DPI_SCALING, "0" ); + SDL_SetHint( SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1" ); + if( !!SDL_Init( SDL_INIT_VIDEO | SDL_VIDEO_OPENGL ) ) { dlog( "gl_create() could not init SDL: %s\n", SDL_GetError() ); return 0; @@ -40,14 +170,18 @@ GL_DATA* gl_create( I32* _canvas ) { SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 ); + I32 start_w = _canvas[0]; + I32 start_h = _canvas[1]; + gl_constrain_window_size( &start_w, &start_h ); + GL_DATA* gl = new GL_DATA; gl->window = SDL_CreateWindow( "game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - _canvas[0], - _canvas[1], - SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN + start_w, + start_h, + SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE ); if( !gl->window ) { @@ -56,6 +190,8 @@ GL_DATA* gl_create( I32* _canvas ) { return 0; } + SDL_SetWindowMinimumSize( gl->window, GL_WINDOW_MIN_W, GL_WINDOW_MIN_H ); + gl->ctx = SDL_GL_CreateContext( gl->window ); if( !gl->ctx ) { dlog( "gl_init() could not create context: %s\n", SDL_GetError() ); @@ -85,10 +221,8 @@ GL_DATA* gl_create( I32* _canvas ) { SAMPLER_INDICES[i] = i; gl->programs.clear(); - memcpy( gl->canvas_size, _canvas, sizeof(I32) * 2 ); - gl->clip_start = { 0, 0 }; - gl->clip_dim = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] }; - glViewport( 0, 0, gl->canvas_size[0], gl->canvas_size[1] ); + gl_handle_window_size_change( gl, start_w, start_h ); + SDL_SetEventFilter( gl_event_filter, gl ); gl_inst = gl; return gl; @@ -105,6 +239,8 @@ void gl_gen_buffers( GL_DATA* gl ) { } void gl_destroy( GL_DATA *gl ) { + SDL_SetEventFilter( 0, 0 ); + gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { gl_program_destroy( gl, *it ); } ); gl->fonts.each( fn( GL_FONT** it ) { gl_font_destroy( gl, *it ); } ); gl->textures.each( fn( GL_TEX2D** it ) { gl_texture_destroy( gl, *it ); } ); @@ -120,27 +256,31 @@ void gl_destroy( GL_DATA *gl ) { void gl_update_window( GL_DATA* gl, I32* size ) { if( !gl->window ) return; - SDL_SetWindowSize( gl->window, size[0], size[1] ); + + I32 width = size ? size[0] : 0; + I32 height = size ? size[1] : 0; + gl_constrain_window_size( &width, &height ); + SDL_SetWindowSize( gl->window, width, height ); SDL_SetWindowPosition( gl->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED ); + gl_handle_window_size_change( gl, width, height ); +} - gl->canvas_size[0] = size[0]; - gl->canvas_size[1] = size[1]; +void gl_sync_window_size( GL_DATA* gl ) { + if( !gl || !gl->window ) + return; - gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { - F32 screen_ratio[] = { - 2.f / (F32)gl->canvas_size[0], - 2.f / (F32)gl->canvas_size[1], - 1.f, - 1.f - }; + SDL_PumpEvents(); + I32 w, h; + SDL_GetWindowSize( gl->window, &w, &h ); + gl_handle_window_size_change( gl, w, h ); +} - glUseProgram( (*it)->id ); - I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" ); - glUniform4fv( ratio_location, 1, &screen_ratio[0] ); - } ); +void gl_set_resize_repaint_callback( GL_DATA* gl, GL_RESIZE_REPAINT_CB cb, void* userdata ) { + if( !gl ) + return; - glViewport( 0, 0, gl->canvas_size[0], gl->canvas_size[1] ); - glUseProgram( 0 ); + gl->resize_repaint_cb = cb; + gl->resize_repaint_userdata = userdata; } STAT gl_shader_compile( GL_DATA* gl, GL_SHADER_DEF* shader ) { @@ -318,6 +458,29 @@ STAT gl_beginframe( GL_DATA* gl ) { return STAT_OK; } +STAT gl_poll_events( GL_DATA* gl ) { + SDL_Event e; + while( SDL_PollEvent( &e ) ) { + input_on_event( &e ); + + switch( e.type ) { + case SDL_QUIT: + return STAT_BREAK; + case SDL_WINDOWEVENT: { + if( e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED + || e.window.event == SDL_WINDOWEVENT_RESIZED ) { + gl_handle_window_size_change( gl, e.window.data1, e.window.data2 ); + } + } break; + case SDL_KEYDOWN: + return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK; + default: break; + } + } + + return STAT_OK; +} + STAT gl_endframe( GL_DATA* gl ) { SDL_GL_SwapWindow( gl->window ); @@ -333,20 +496,6 @@ STAT gl_endframe( GL_DATA* gl ) { gl->fps = 1.0f / gl->frametime; input_frame_end(); - - SDL_Event e; - while( SDL_PollEvent( &e ) ) { - input_on_event( &e ); - - switch( e.type ) { - case SDL_QUIT: - return STAT_BREAK; - case SDL_KEYDOWN: - return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK; - default: break; - } - } - return STAT_OK; } @@ -372,19 +521,7 @@ void gl_reset_clip( GL_DATA* gl ) { void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim ) { I32 vpy = (I32)gl->canvas_size[1] - start.y - dim.y; glViewport( (I32)start.x, vpy, (I32)dim.x, (I32)dim.y ); - - gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { - F32 screen_ratio[] = { - 2.f / (F32)dim.x, - 2.f / (F32)dim.y, - 1.f, - 1.f - }; - - glUseProgram( (*it)->id ); - I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" ); - glUniform4fv( ratio_location, 1, &screen_ratio[0] ); - } ); + gl_update_screen_ratio_uniforms( gl, (I32)dim.x, (I32)dim.y ); gl->viewport_start = start; gl->viewport_dim = dim; diff --git a/src/render/gl.h b/src/render/gl.h index e4e2d5a..dfb76b3 100644 --- a/src/render/gl.h +++ b/src/render/gl.h @@ -9,6 +9,7 @@ typedef struct GL_FONT *PGL_FONT; typedef struct GL_DATA *PGL_DATA; +typedef void(*GL_RESIZE_REPAINT_CB)( void* userdata ); struct VERTEX { VEC2 pos; @@ -71,6 +72,11 @@ typedef struct GL_DATA { MAT4* proj_matrix; GLuint vao; + + GL_RESIZE_REPAINT_CB resize_repaint_cb{}; + void* resize_repaint_userdata{}; + U8 resize_repaint_active{}; + U8 window_constraint_active{}; } *PGL_DATA; GL_DATA* gl_instance(); @@ -79,6 +85,7 @@ void gl_destroy( GL_DATA* gl ); void gl_gen_buffers( GL_DATA* gl ); STAT gl_beginframe( GL_DATA* gl ); STAT gl_endframe( GL_DATA* gl ); +STAT gl_poll_events( GL_DATA* gl ); STAT gl_shader_compile( GL_DATA* gl, GL_SHADER_DEF* shader ); void gl_shader_destroy( GL_DATA* gl, GL_SHADER_DEF* shader ); GL_SHADER_PROGRAM* gl_program_create( GL_DATA* gl, const char* name, U32 size = sizeof(GL_SHADER_PROGRAM) ); @@ -89,6 +96,8 @@ GL_TEX2D* gl_texture_from_file( GL_DATA* gl, const char* name ); GL_TEX2D* gl_texture_from_bitmap( GL_DATA* gl, const char* name, U8* bitmap, U32 width, U32 height ); void gl_texture_destroy( GL_DATA* gl, GL_TEX2D* tex ); void gl_update_window( GL_DATA* gl, I32* size ); +void gl_sync_window_size( GL_DATA* gl ); +void gl_set_resize_repaint_callback( GL_DATA* gl, GL_RESIZE_REPAINT_CB cb, void* userdata ); void gl_set_clip( GL_DATA* gl, VEC2 start, VEC2 dim ); void gl_get_clip( GL_DATA* gl, VEC2* start, VEC2* dim ); void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim ); diff --git a/src/render/gl_3d.cpp b/src/render/gl_3d.cpp index 40162e8..8449500 100644 --- a/src/render/gl_3d.cpp +++ b/src/render/gl_3d.cpp @@ -21,13 +21,20 @@ void gl_3d_projection_setup( GL_SHADER_PROGRAM *_gl3d, VEC3 pos, F32 fov_deg, F3 GL3D* gl3d = (GL3D*)_gl3d; MAT4 proj, yaw_rot, pitch_rot, view, transl, tmp; - F32 fov_rad = m_deg2rad( fov_deg ); + F32 fov_y_rad = m_deg2rad( fov_deg ); F32 yaw_rad = m_deg2rad( remainderf( yaw + 90.f, 360.f ) ); F32 pitch_rad = m_deg2rad( remainderf( pitch , 360.f ) ); - gl3d->aspect = winsize.x / winsize.y; + gl3d->aspect = winsize.y > 0.f ? winsize.x / winsize.y : 1.f; + if( gl3d->aspect <= 0.000001f ) + gl3d->aspect = 1.f; + + const F32 min_fovy = m_deg2rad( 1.f ); + const F32 max_fovy = m_deg2rad( 179.f ); + if( fov_y_rad < min_fovy ) fov_y_rad = min_fovy; + if( fov_y_rad > max_fovy ) fov_y_rad = max_fovy; gl3d->winsize = winsize; - mat4_perspective( &proj, fov_rad, gl3d->aspect, near, far ); + mat4_perspective( &proj, fov_y_rad, gl3d->aspect, near, far ); mat4_rotation_y( &yaw_rot, -yaw_rad ); mat4_rotation_x( &pitch_rot, -pitch_rad ); mat4_translation( &transl, -pos.x, -pos.z, -pos.y ); -- cgit v1.2.3