#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; CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive; gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr ); gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg ); wnd->children.each( fn( GUI_BASE** ptr ) { GUI_BASE* it = *ptr; if( !it->enabled ) return; if( it->draw_fn ) it->draw_fn( it ); else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it ); } ); } GUI_EDITORWINDOW* gui_editorwindow_create( I32 w, I32 h ) { GUI_EDITORWINDOW* wnd = new GUI_EDITORWINDOW; wnd->x = 0; wnd->y = 0; wnd->xbound = wnd->w = w; wnd->ybound = wnd->h = h; wnd->locked = 1; wnd->draw_fn = gui_editorwindow_draw_fn; strcpy( wnd->name, "EDITORWINDOW" ); _gui.windows.push( wnd ); gui_set_window( wnd ); gui_set_view( 0 ); gui_view( 1, 1, w - 2, h - 2 ); return wnd; } GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ) { GUI_EDITORWINDOW* wnd = gui_editorwindow_create( w, h ); GAME_EDITOR* e = editor; LIST* map_list = editor_get_map_list( e ); 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; } void editor_update_properties_column( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; gui_editor_propview_update( egui->props ); } void editor_create_properties_column( GAME_EDITOR* e ) { GAME_EDITOR::EDITOR_GUI* egui = &e->gui; 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 ); } void settool( U8 t ) { editor->tool.type = t; if( editor->gui.v2d ) { editor->gui.v2d->poly_drag = 0; } if( editor->gui.tool ) { 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; 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" }; 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, 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 = 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 = 1; egui->v3d->enabled = 0; } void editor_create_map_view( GAME_EDITOR* e ) { if( !e->map ) { dlog( "editor_create_map_views() : no map loaded\n" ); return; } GUI_EDITORWINDOW* w = e->wnd; w->children.each( fn( GUI_BASE** ptr ) { 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* ) { gui_push_callback( pfn( void* ) { GUI_WINDOW* popup = editor->gui.new_map_popup; if( !popup ) return; gui_free( popup ); editor->gui.new_map_popup = 0; } ); } void editor_new_map_cb( void* ptr ) { if( editor->gui.new_map_popup ) return; GUI_WINDOW* cwnd = gui_get_window(); GUI_VIEW* view = gui_get_view(); I32 wx = 250; I32 wy = 140; I32 ww = 300; I32 wh = 200; editor->gui.new_map_popup = gui_window( wx, wy, ww, wh ); gui_title( "new map" ); GUI_TEXTBOX* tb = gui_textbox( 10, 20, ww - 20, 20, "name", 32 ); tb->active = 1; gui_button( ww - 50 - 70, wh - 20 - 10, 50, 20, "ok", pfn( void* ptr ) { GUI_BUTTON* btn = (GUI_BUTTON*)ptr; GUI_TEXTBOX* name = (GUI_TEXTBOX*)gui_find_node( btn->parent, "name" ); if( !name ) return; editor_new_map( editor, name->value ); close_new_map_popup( 0 ); } ); gui_button( ww - 50 - 10, wh - 20 - 10, 50, 20, "cancel", close_new_map_popup ); gui_set_window( cwnd ); gui_set_view( view ); } void editor_load_map_cb( void* ptr ) { GUI_LIST* list = (GUI_LIST*)gui_find_node( editor->wnd, "map list" ); GUI_LIST_ENTRY* e = gui_list_get_selected( list ); if( !e ) return; char full_path[256]; sprintf( full_path, "../assets/maps/%s", e->title ); if( editor->map ) editor_close( editor ); editor_load_map( editor, full_path ); }