summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoraura <nw@moneybot.cc>2026-03-04 00:51:18 +0100
committeraura <nw@moneybot.cc>2026-03-04 00:51:18 +0100
commit890bea19359649695df1b618a41ce580cf3dfbda (patch)
tree8bc10ae4e613b0ad57fae9a68cec38c7d59cf067 /src
parent61aea7311c2e1af78fd9da544499f2198f2da1dd (diff)
parentbe91342733fd56d1e7bafe72e82a8ac4dc67b79d (diff)
Merge branch 'ui-rework'
Diffstat (limited to 'src')
-rw-r--r--src/editor/editor.cpp399
-rw-r--r--src/editor/editor.h56
-rw-r--r--src/editor/gui.cpp1220
-rw-r--r--src/editor/properties.cpp122
-rw-r--r--src/editor/toolview.cpp103
-rw-r--r--src/editor/view2d.cpp317
-rw-r--r--src/editor/view3d.cpp8
-rw-r--r--src/game.cpp58
-rw-r--r--src/gui/button.cpp24
-rw-r--r--src/gui/floatinput.cpp88
-rw-r--r--src/gui/list.cpp32
-rw-r--r--src/render/gl.cpp237
-rw-r--r--src/render/gl.h9
-rw-r--r--src/render/gl_3d.cpp13
14 files changed, 2430 insertions, 256 deletions
diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp
index ad23d1e..3873913 100644
--- a/src/editor/editor.cpp
+++ b/src/editor/editor.cpp
@@ -4,6 +4,402 @@
GAME_EDITOR* editor = 0;
+void editor_clear_gui_state_refs( GAME_EDITOR* e ) {
+ if( !e )
+ return;
+
+ e->gui.new_map_popup = 0;
+
+ e->gui.v2d = 0;
+ e->gui.v3d = 0;
+ e->gui.gridlabel = 0;
+ e->gui.props = 0;
+ e->gui.tool = 0;
+
+ e->gui.assets = 0;
+ e->gui.status = 0;
+ e->gui.assets_scroll = 0;
+
+ e->gui.header_toolbar = 0;
+ e->gui.header_viewtype_label = 0;
+ e->gui.header_back = 0;
+ e->gui.header_mode_2d = 0;
+ e->gui.header_mode_3d = 0;
+ e->gui.header_mode_sim = 0;
+ e->gui.header_viewtype = 0;
+}
+
+static void editor_push_undo_action( GAME_EDITOR* e, const GAME_EDITOR::EDITOR_UNDO_ACTION& action ) {
+ if( !e )
+ return;
+
+ e->redo_actions.clear();
+ e->undo_actions.push( action );
+}
+
+static void editor_refresh_after_map_change( GAME_EDITOR* e ) {
+ if( !e || !e->map )
+ return;
+
+ map_check_bounds( e->map );
+ if( e->gui.props && e->propgrid )
+ editor_update_properties_column( e );
+ if( e->gui.tool )
+ gui_editor_toolview_update( e->gui.tool );
+}
+
+static U8 editor_clr_eq( const CLR& a, const CLR& b ) {
+ return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
+}
+
+static U8 editor_vertex_eq( const MAP_VERTEX& a, const MAP_VERTEX& b ) {
+ return a.pos == b.pos
+ && a.normal == b.normal
+ && a.uv == b.uv
+ && editor_clr_eq( a.clr, b.clr );
+}
+
+static U8 editor_wall_eq( const MAP_WALL& a, const MAP_WALL& b ) {
+ return a.start == b.start
+ && a.end == b.end
+ && a.uvstart == b.uvstart
+ && a.uvend == b.uvend
+ && a.propid == b.propid;
+}
+
+static U8 editor_poly_eq( const MAP_POLYGON& a, const MAP_POLYGON& b ) {
+ if( a.type != b.type || a.propid != b.propid )
+ return 0;
+ if( !( a.mins == b.mins ) || !( a.maxs == b.maxs ) )
+ return 0;
+ if( a.vertices.size != b.vertices.size )
+ return 0;
+
+ for( U32 i = 0; i < a.vertices.size; ++i ) {
+ if( !editor_vertex_eq( a.vertices.data[i], b.vertices.data[i] ) )
+ return 0;
+ }
+ return 1;
+}
+
+static I32 editor_clamp_valid_idx( I32 idx, I32 size ) {
+ if( size <= 0 )
+ return -1;
+ if( idx < 0 )
+ return 0;
+ if( idx >= size )
+ return size - 1;
+ return idx;
+}
+
+static I32 editor_idx_distance( I32 a, I32 b ) {
+ return a > b ? a - b : b - a;
+}
+
+template <typename MATCH_FN>
+static I32 editor_find_nearest_idx( I32 size, I32 expected_idx, MATCH_FN match ) {
+ if( size <= 0 )
+ return -1;
+
+ expected_idx = editor_clamp_valid_idx( expected_idx, size );
+ if( match( expected_idx ) )
+ return expected_idx;
+
+ I32 best = -1;
+ I32 best_dist = 0x7fffffff;
+ for( I32 i = 0; i < size; ++i ) {
+ if( !match( i ) )
+ continue;
+
+ I32 dist = editor_idx_distance( i, expected_idx );
+ if( best == -1 || dist < best_dist ) {
+ best = i;
+ best_dist = dist;
+ }
+ }
+
+ return best;
+}
+
+static I32 editor_find_wall_idx( WORLD_MAP* map, const MAP_WALL& want, I32 expected_idx ) {
+ if( !map )
+ return -1;
+
+ return editor_find_nearest_idx( (I32)map->walls.size, expected_idx, fn( I32 i ) {
+ return editor_wall_eq( map->walls[i], want );
+ } );
+}
+
+static I32 editor_find_poly_idx( WORLD_MAP* map, const MAP_POLYGON& want, I32 expected_idx ) {
+ if( !map )
+ return -1;
+
+ return editor_find_nearest_idx( (I32)map->polygons.size, expected_idx, fn( I32 i ) {
+ return editor_poly_eq( map->polygons[i], want );
+ } );
+}
+
+static void editor_mark_clear_wall_refs( GAME_EDITOR* e, MAP_WALL* w, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) {
+ if( e->gui.props ) {
+ if( e->gui.props->seltype == EDITOR_SELECT_WALL && e->gui.props->curselect == w )
+ *clear_props_select = 1;
+ if( e->gui.props->seltype == EDITOR_SELECT_WVERTEX
+ && ( e->gui.props->curselect == &w->start || e->gui.props->curselect == &w->end ) )
+ *clear_props_select = 1;
+ }
+
+ if( e->gui.v2d ) {
+ if( e->gui.v2d->seltype == EDITOR_SELECT_WALL && e->gui.v2d->curselect == w )
+ *clear_view_select = 1;
+ if( e->gui.v2d->seltype == EDITOR_SELECT_WVERTEX
+ && ( e->gui.v2d->curselect == &w->start || e->gui.v2d->curselect == &w->end ) )
+ *clear_view_select = 1;
+
+ if( e->gui.v2d->dragtype == EDITOR_SELECT_WALL && e->gui.v2d->curdrag == w )
+ *clear_view_drag = 1;
+ if( e->gui.v2d->dragtype == EDITOR_SELECT_WVERTEX
+ && ( e->gui.v2d->curdrag == &w->start || e->gui.v2d->curdrag == &w->end ) )
+ *clear_view_drag = 1;
+ }
+}
+
+static void editor_mark_clear_poly_refs( GAME_EDITOR* e, MAP_POLYGON* p, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) {
+ if( e->gui.props ) {
+ if( e->gui.props->seltype == EDITOR_SELECT_POLY && e->gui.props->curselect == p )
+ *clear_props_select = 1;
+ if( e->gui.props->seltype == EDITOR_SELECT_PVERTEX ) {
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ if( e->gui.props->curselect == v )
+ *clear_props_select = 1;
+ } );
+ }
+ }
+
+ if( e->gui.v2d ) {
+ if( e->gui.v2d->seltype == EDITOR_SELECT_POLY && e->gui.v2d->curselect == p )
+ *clear_view_select = 1;
+ if( e->gui.v2d->seltype == EDITOR_SELECT_PVERTEX ) {
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ if( e->gui.v2d->curselect == v )
+ *clear_view_select = 1;
+ } );
+ }
+
+ if( e->gui.v2d->dragtype == EDITOR_SELECT_POLY && e->gui.v2d->curdrag == p )
+ *clear_view_drag = 1;
+ if( e->gui.v2d->dragtype == EDITOR_SELECT_PVERTEX ) {
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ if( e->gui.v2d->curdrag == v )
+ *clear_view_drag = 1;
+ } );
+ }
+ }
+}
+
+void editor_undo_clear( GAME_EDITOR* e ) {
+ if( !e )
+ return;
+
+ e->undo_actions.clear();
+ e->redo_actions.clear();
+}
+
+void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 count ) {
+ if( !e || !e->map || count <= 0 )
+ return;
+
+ WORLD_MAP* map = e->map;
+ if( start_idx < 0 || start_idx >= (I32)map->walls.size )
+ return;
+
+ I32 available = (I32)map->walls.size - start_idx;
+ if( count > available )
+ count = available;
+ if( count <= 0 )
+ return;
+
+ GAME_EDITOR::EDITOR_UNDO_ACTION action{};
+ action.type = EDITOR_UNDO_CREATE_WALLS;
+ action.start_idx = start_idx;
+ for( I32 i = 0; i < count; ++i )
+ action.walls.push( map->walls[start_idx + i] );
+
+ editor_push_undo_action( e, action );
+}
+
+void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx ) {
+ if( !e || !e->map )
+ return;
+
+ WORLD_MAP* map = e->map;
+ if( start_idx < 0 || start_idx >= (I32)map->polygons.size )
+ return;
+
+ GAME_EDITOR::EDITOR_UNDO_ACTION action{};
+ action.type = EDITOR_UNDO_CREATE_POLY;
+ action.start_idx = start_idx;
+ action.polys.push( map->polygons[start_idx] );
+
+ editor_push_undo_action( e, action );
+}
+
+static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) {
+ if( !e || !e->map )
+ return 0;
+
+ WORLD_MAP* map = e->map;
+ U8 clear_props_select = 0;
+ U8 clear_view_select = 0;
+ U8 clear_view_drag = 0;
+
+ switch( action.type ) {
+ case EDITOR_UNDO_CREATE_WALLS: {
+ I32 count = (I32)action.walls.size;
+ for( I32 i = count - 1; i >= 0; --i ) {
+ if( !map->walls.size )
+ break;
+
+ I32 expected = action.start_idx + i;
+ I32 idx = editor_find_wall_idx( map, action.walls[i], expected );
+ if( idx < 0 )
+ idx = editor_clamp_valid_idx( expected, (I32)map->walls.size );
+
+ MAP_WALL* w = &map->walls[idx];
+ editor_mark_clear_wall_refs( e, w, &clear_props_select, &clear_view_select, &clear_view_drag );
+
+ map->walls.erase( idx );
+ }
+ } break;
+ case EDITOR_UNDO_CREATE_POLY: {
+ I32 count = (I32)action.polys.size;
+ if( count <= 0 )
+ return 0;
+ for( I32 i = count - 1; i >= 0; --i ) {
+ if( !map->polygons.size )
+ break;
+
+ I32 expected = action.start_idx + i;
+ I32 idx = i < (I32)action.polys.size ? editor_find_poly_idx( map, action.polys[i], expected ) : -1;
+ if( idx < 0 )
+ idx = editor_clamp_valid_idx( expected, (I32)map->polygons.size );
+
+ MAP_POLYGON* p = &map->polygons[idx];
+ editor_mark_clear_poly_refs( e, p, &clear_props_select, &clear_view_select, &clear_view_drag );
+
+ map->polygons.erase( idx );
+ }
+ } break;
+ default:
+ return 0;
+ }
+
+ if( e->gui.props && clear_props_select )
+ gui_editor_propview_select( e->gui.props, 0, EDITOR_SELECT_NONE );
+ if( e->gui.v2d ) {
+ if( clear_view_select ) {
+ e->gui.v2d->curselect = 0;
+ e->gui.v2d->seltype = EDITOR_SELECT_NONE;
+ }
+ if( clear_view_drag ) {
+ e->gui.v2d->curdrag = 0;
+ e->gui.v2d->dragtype = EDITOR_SELECT_NONE;
+ }
+ }
+
+ editor_refresh_after_map_change( e );
+
+ return 1;
+}
+
+static U8 editor_apply_undo_action_forward( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) {
+ if( !e || !e->map )
+ return 0;
+
+ WORLD_MAP* map = e->map;
+ switch( action.type ) {
+ case EDITOR_UNDO_CREATE_WALLS: {
+ if( !action.walls.size )
+ return 0;
+
+ action.walls.each( fn( MAP_WALL* w ) {
+ map->walls.push( *w );
+ } );
+ } break;
+ case EDITOR_UNDO_CREATE_POLY: {
+ if( !action.polys.size )
+ return 0;
+
+ action.polys.each( fn( MAP_POLYGON* p ) {
+ map->polygons.push( *p );
+ } );
+ } break;
+ default:
+ return 0;
+ }
+
+ editor_refresh_after_map_change( e );
+
+ return 1;
+}
+
+U8 editor_undo( GAME_EDITOR* e ) {
+ if( !e || !e->map || !e->undo_actions.size )
+ return 0;
+
+ GAME_EDITOR::EDITOR_UNDO_ACTION action = e->undo_actions.pop();
+ if( !editor_apply_undo_action_reverse( e, action ) ) {
+ e->undo_actions.push( action );
+ return 0;
+ }
+
+ e->redo_actions.push( action );
+ return 1;
+}
+
+U8 editor_redo( GAME_EDITOR* e ) {
+ if( !e || !e->map || !e->redo_actions.size )
+ return 0;
+
+ GAME_EDITOR::EDITOR_UNDO_ACTION action = e->redo_actions.pop();
+ if( !editor_apply_undo_action_forward( e, action ) ) {
+ e->redo_actions.push( action );
+ return 0;
+ }
+
+ e->undo_actions.push( action );
+ return 1;
+}
+
+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" );
@@ -30,6 +426,7 @@ GAME_EDITOR* editor_create( GAME_DATA* game ) {
}
STAT editor_close( GAME_EDITOR* e ) {
+ editor_undo_clear( e );
game_unload_map( e->game );
e->map = 0;
@@ -38,6 +435,7 @@ STAT editor_close( GAME_EDITOR* e ) {
I32 w = e->wnd->w, h = e->wnd->h;
gui_free( e->wnd );
+ editor_clear_gui_state_refs( e );
e->wnd = gui_editorwindow( w, h );
} );
@@ -57,6 +455,7 @@ STAT editor_load_map( GAME_EDITOR* e, const char* mapname ) {
return STAT_ERR;
e->game->state.map = e->map = m;
+ editor_undo_clear( e );
gui_push_callback( e, pfn( void* d ) {
editor_create_map_view( (GAME_EDITOR*)d );
} );
diff --git a/src/editor/editor.h b/src/editor/editor.h
index cd42a4e..ad43705 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,15 @@ enum EditorSelectType_t {
EDITOR_SELECT_SURFPROPS
};
+enum EditorUndoType_t {
+ EDITOR_UNDO_NONE = 0,
+ EDITOR_UNDO_CREATE_WALLS = 1,
+ EDITOR_UNDO_CREATE_POLY = 2
+};
+
+struct GAME_EDITOR;
+typedef void( *EDITOR_GRID_DEP_CALLBACK )( GAME_EDITOR* e );
+
struct GAME_EDITOR_TOOL {
U8 type;
@@ -68,6 +76,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_BASE* header_toolbar{};
+ GUI_LABEL* header_viewtype_label{};
+ GUI_BUTTON* header_back{};
+ GUI_BUTTON* header_mode_2d{};
+ GUI_BUTTON* header_mode_3d{};
+ GUI_BUTTON* header_mode_sim{};
+ GUI_BASE* header_viewtype{};
} gui;
@@ -78,7 +104,22 @@ struct GAME_EDITOR {
F32 spritesize{};
U8 drawbsp{};
+ struct GRID_DEPENDENCY {
+ void* tag{};
+ EDITOR_GRID_DEP_CALLBACK cb{};
+ };
+ LIST<GRID_DEPENDENCY> grid_dependencies{};
+
LIST<GUI_LIST_ENTRY> map_list{};
+
+ struct EDITOR_UNDO_ACTION {
+ U8 type{};
+ I32 start_idx{};
+ LIST<MAP_WALL> walls{};
+ LIST<MAP_POLYGON> polys{};
+ };
+ LIST<EDITOR_UNDO_ACTION> undo_actions{};
+ LIST<EDITOR_UNDO_ACTION> redo_actions{};
};
extern GAME_EDITOR* editor_create( struct GAME_DATA* game );
@@ -87,6 +128,10 @@ 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_clear_gui_state_refs( 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<GUI_LIST_ENTRY>* editor_get_map_list( GAME_EDITOR* e );
@@ -95,6 +140,11 @@ extern void editor_new_map_cb( void* );
extern void editor_create_map_view( GAME_EDITOR* e );
extern void editor_update_properties_column( GAME_EDITOR* e );
+extern void editor_undo_clear( GAME_EDITOR* e );
+extern void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 count );
+extern void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx );
+extern U8 editor_undo( GAME_EDITOR* e );
+extern U8 editor_redo( GAME_EDITOR* e );
struct GUI_EDITORWINDOW : GUI_WINDOW {};
struct GUI_EDITOR_3DVIEW : GUI_VIEW {
@@ -124,6 +174,8 @@ struct GUI_EDITOR_2DVIEW : GUI_VIEW {
U8 poly_drag;
VEC2 poly_start;
VEC2 poly_end;
+
+ I32 pending_wall_undo_idx;
};
struct GUI_EDITOR_PROPVIEW : GUI_VIEW {
@@ -136,6 +188,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..ea03e8f 100644
--- a/src/editor/gui.cpp
+++ b/src/editor/gui.cpp
@@ -1,8 +1,1090 @@
#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_MENU_Y = 1;
+const I32 EDITOR_LAYOUT_MENU_H = 22;
+const I32 EDITOR_LAYOUT_NAV_Y = EDITOR_LAYOUT_MENU_Y + EDITOR_LAYOUT_MENU_H + 4;
+const I32 EDITOR_LAYOUT_CONTENT_Y = EDITOR_LAYOUT_NAV_Y + 28;
+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};
+};
+
+struct GUI_EDITOR_TOOLBAR : GUI_BASE {
+ U8 held{};
+ I32 held_item{-1};
+ U8 file_open{};
+ U8 edit_open{};
+};
+
+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 U8 editor_menu_hover_mask_active = 0;
+static I32 editor_menu_hover_real_x = 0;
+static I32 editor_menu_hover_real_y = 0;
+
+enum EditorToolbarHit_t {
+ EDITOR_TOOLBAR_HIT_NONE = -1,
+ EDITOR_TOOLBAR_HIT_FILE = 0,
+ EDITOR_TOOLBAR_HIT_EDIT = 1,
+ EDITOR_TOOLBAR_HIT_VIEW = 2,
+ EDITOR_TOOLBAR_HIT_TOOLS = 3,
+ EDITOR_TOOLBAR_HIT_SAVE = 4,
+ EDITOR_TOOLBAR_HIT_UNDO = 5,
+ EDITOR_TOOLBAR_HIT_REDO = 6
+};
+
+static void editor_toolbar_set_open( GUI_EDITOR_TOOLBAR* bar, U8 file_open, U8 edit_open );
+
+static void editor_toolbar_close_menu() {
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)editor->gui.header_toolbar;
+ editor_toolbar_set_open( bar, 0, 0 );
+}
+
+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_toolbar_close_menu();
+ editor_close( editor );
+}
+
+static void editor_set_view_mode( I32 mode ) {
+ editor_toolbar_close_menu();
+ 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] = { "x/y", "x/z", "y/z" };
+
+ 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;
+}
+
+const I32 EDITOR_TOOLBAR_DROPDOWN_W = 74;
+
+static U8 editor_toolbar_pt_in_rect( I32 mx, I32 my, I32 x, I32 y, I32 w, I32 h ) {
+ return mx >= x && mx < x + w && my >= y && my < y + h;
+}
+
+static void editor_toolbar_set_open( GUI_EDITOR_TOOLBAR* bar, U8 file_open, U8 edit_open ) {
+ if( !bar )
+ return;
+
+ bar->file_open = file_open;
+ bar->edit_open = edit_open;
+}
+
+static void editor_toolbar_item_rect( GUI_EDITOR_TOOLBAR* bar, I32 idx, I32* rx, I32* ry, I32* rw, I32* rh ) {
+ static const I32 widths[4] = { 44, 44, 44, 52 };
+ I32 x = gui_relx( bar );
+ I32 y = gui_rely( bar );
+ I32 cx = x + 8;
+ for( I32 i = 0; i < idx; ++i )
+ cx += widths[i] + 4;
+
+ *rx = cx;
+ *ry = y + 2;
+ *rw = widths[idx];
+ *rh = max( 1, bar->h - 4 );
+}
+
+static void editor_toolbar_dropdown_row_rect( GUI_EDITOR_TOOLBAR* bar, I32 anchor_idx, I32 row, I32* rx, I32* ry, I32* rw, I32* rh ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_item_rect( bar, anchor_idx, &x, &y, &w, &h );
+ *rx = x;
+ *rh = max( 18, h );
+ *ry = gui_rely( bar ) + bar->h + row * *rh;
+ *rw = EDITOR_TOOLBAR_DROPDOWN_W;
+}
+
+static I32 editor_toolbar_hit_test( GUI_EDITOR_TOOLBAR* bar, I32 mx, I32 my ) {
+ for( I32 i = 0; i < 4; ++i ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_item_rect( bar, i, &x, &y, &w, &h );
+ if( editor_toolbar_pt_in_rect( mx, my, x, y, w, h ) )
+ return i;
+ }
+
+ if( bar->file_open ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_dropdown_row_rect( bar, EDITOR_TOOLBAR_HIT_FILE, 0, &x, &y, &w, &h );
+ if( editor_toolbar_pt_in_rect( mx, my, x, y, w, h ) )
+ return EDITOR_TOOLBAR_HIT_SAVE;
+ }
+
+ if( bar->edit_open ) {
+ for( I32 i = 0; i < 2; ++i ) {
+ I32 x = 0, y = 0, w = 0, h = 0;
+ editor_toolbar_dropdown_row_rect( bar, EDITOR_TOOLBAR_HIT_EDIT, i, &x, &y, &w, &h );
+ if( editor_toolbar_pt_in_rect( mx, my, x, y, w, h ) )
+ return i == 0 ? EDITOR_TOOLBAR_HIT_UNDO : EDITOR_TOOLBAR_HIT_REDO;
+ }
+ }
+
+ return EDITOR_TOOLBAR_HIT_NONE;
+}
+
+static void gui_editor_toolbar_draw_fn( void* ptr ) {
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)ptr;
+ I32 x = gui_relx( bar );
+ I32 y = gui_rely( bar );
+ I32 mx, my;
+ if( editor_menu_hover_mask_active ) {
+ mx = editor_menu_hover_real_x;
+ my = editor_menu_hover_real_y;
+ } else {
+ gui_cursor_pos( &mx, &my );
+ }
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ I32 hover = editor_toolbar_hit_test( bar, mx, my );
+ CLR hover_fill = CLR::blend( ui_clr.border, ui_clr.bg, 0.70f );
+ CLR active_fill = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
+
+ CLR border = gui_is_fg_window( bar ) ? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, bar->w, bar->h, ui_clr.bg_sec );
+
+ static const char* labels[4] = { "file", "edit", "view", "tools" };
+ for( I32 i = 0; i < 4; ++i ) {
+ I32 rx = 0, ry = 0, rw = 0, rh = 0;
+ editor_toolbar_item_rect( bar, i, &rx, &ry, &rw, &rh );
+ U8 is_hover = hover == i;
+ U8 is_active = bar->held && bar->held_item == i && m1;
+ U8 is_open = ( bar->file_open && i == EDITOR_TOOLBAR_HIT_FILE )
+ || ( bar->edit_open && i == EDITOR_TOOLBAR_HIT_EDIT );
+
+ CLR fill = ui_clr.bg_sec;
+ if( is_open )
+ fill = hover_fill;
+ else if( is_active )
+ fill = active_fill;
+ else if( is_hover )
+ fill = hover_fill;
+
+ if( is_open || is_active || is_hover )
+ gui_draw_frect( rx, ry, rw, rh, fill );
+ gui_draw_str( rx + 6, y + bar->h / 2 - 8, ALIGN_L, FNT_JPN12, ui_clr.txt, labels[i] );
+ }
+
+ if( bar->file_open ) {
+ I32 rx = 0, ry = 0, rw = 0, rh = 0;
+ editor_toolbar_dropdown_row_rect( bar, EDITOR_TOOLBAR_HIT_FILE, 0, &rx, &ry, &rw, &rh );
+ U8 is_hover = hover == EDITOR_TOOLBAR_HIT_SAVE;
+ U8 is_active = bar->held && bar->held_item == EDITOR_TOOLBAR_HIT_SAVE && m1;
+
+ gui_draw_frect( rx, ry, rw, rh, border );
+ CLR fill = ui_clr.bg_sec;
+ if( is_active )
+ fill = active_fill;
+ else if( is_hover )
+ fill = hover_fill;
+
+ gui_draw_frect( rx + 1, ry + 1, max( 1, rw - 2 ), max( 1, rh - 2 ), fill );
+ gui_draw_str( rx + 8, ry + rh / 2 - 8, ALIGN_L, FNT_JPN12, ui_clr.txt, "save" );
+ }
+
+ if( bar->edit_open ) {
+ const U8 enabled[2] = { editor->undo_actions.size > 0, editor->redo_actions.size > 0 };
+ const char* labels[2] = { "undo", "redo" };
+ const I32 hit_id[2] = { EDITOR_TOOLBAR_HIT_UNDO, EDITOR_TOOLBAR_HIT_REDO };
+
+ I32 panel_x = 0, panel_y = 0, panel_w = 0, panel_h = 0;
+ editor_toolbar_dropdown_row_rect( bar, EDITOR_TOOLBAR_HIT_EDIT, 0, &panel_x, &panel_y, &panel_w, &panel_h );
+ I32 row_h = panel_h;
+ gui_draw_frect( panel_x, panel_y, panel_w, row_h * 2, border );
+ gui_draw_frect( panel_x + 1, panel_y + 1, max( 1, panel_w - 2 ), max( 1, row_h * 2 - 2 ), ui_clr.bg_sec );
+
+ for( I32 i = 0; i < 2; ++i ) {
+ I32 rx = 0, ry = 0, rw = 0, rh = 0;
+ editor_toolbar_dropdown_row_rect( bar, EDITOR_TOOLBAR_HIT_EDIT, i, &rx, &ry, &rw, &rh );
+ U8 is_hover = hover == hit_id[i];
+ U8 is_active = bar->held && bar->held_item == hit_id[i] && m1;
+
+ CLR fill = ui_clr.bg_sec;
+ if( enabled[i] && is_active )
+ fill = active_fill;
+ else if( is_hover )
+ fill = hover_fill;
+ gui_draw_frect( rx + 1, ry + 1, max( 1, rw - 2 ), max( 1, rh - 2 ), fill );
+
+ CLR txt_clr = enabled[i] ? ui_clr.txt : ui_clr.txt_inactive;
+ gui_draw_str( rx + 8, ry + rh / 2 - 8, ALIGN_L, FNT_JPN12, txt_clr, labels[i] );
+ }
+ }
+}
+
+static void gui_editor_toolbar_input_fn( void* ptr ) {
+ GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)ptr;
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ I32 hit = editor_toolbar_hit_test( bar, mx, my );
+
+ if( m1 ) {
+ if( !bar->held ) {
+ bar->held = 1;
+ bar->held_item = hit;
+ if( hit == EDITOR_TOOLBAR_HIT_NONE && ( bar->file_open || bar->edit_open ) )
+ editor_toolbar_set_open( bar, 0, 0 );
+ }
+ return;
+ }
+
+ if( bar->held && bar->held_item == hit ) {
+ switch( hit ) {
+ case EDITOR_TOOLBAR_HIT_FILE:
+ editor_toolbar_set_open( bar, !bar->file_open, 0 );
+ break;
+ case EDITOR_TOOLBAR_HIT_EDIT:
+ editor_toolbar_set_open( bar, 0, !bar->edit_open );
+ break;
+ case EDITOR_TOOLBAR_HIT_SAVE:
+ editor_toolbar_set_open( bar, 0, 0 );
+ editor_save_map( editor );
+ break;
+ case EDITOR_TOOLBAR_HIT_UNDO:
+ editor_toolbar_set_open( bar, 0, 0 );
+ editor_undo( editor );
+ break;
+ case EDITOR_TOOLBAR_HIT_REDO:
+ editor_toolbar_set_open( bar, 0, 0 );
+ editor_redo( editor );
+ break;
+ case EDITOR_TOOLBAR_HIT_VIEW:
+ case EDITOR_TOOLBAR_HIT_TOOLS:
+ editor_toolbar_set_open( bar, 0, 0 );
+ break;
+ default:
+ break;
+ }
+ }
+
+ bar->held = 0;
+ bar->held_item = EDITOR_TOOLBAR_HIT_NONE;
+}
+
+static GUI_EDITOR_TOOLBAR* gui_editor_toolbar( I32 x, I32 y, I32 w, I32 h, const char* name ) {
+ if( !gui_check_target() )
+ return 0;
+
+ GUI_EDITOR_TOOLBAR* bar = new GUI_EDITOR_TOOLBAR;
+ bar->x = x;
+ bar->y = y;
+ bar->w = w;
+ bar->h = h;
+ bar->xbound = w;
+ bar->ybound = h + 52;
+ bar->draw_fn = gui_editor_toolbar_draw_fn;
+ bar->input_fn = gui_editor_toolbar_input_fn;
+ bar->held = 0;
+ bar->held_item = EDITOR_TOOLBAR_HIT_NONE;
+ bar->file_open = 0;
+ bar->edit_open = 0;
+ strcpy( bar->name, name );
+
+ GUI_VIEW* parent = gui_get_view();
+ bar->parent = parent;
+ parent->children.push( bar );
+ return bar;
+}
+
+static void editor_raise_header_toolbar( GAME_EDITOR* e ) {
+ if( !e || !e->map || !e->wnd || !e->gui.header_toolbar )
+ return;
+
+ GUI_BASE* bar = e->gui.header_toolbar;
+ GUI_BASE* parent = bar->parent;
+ if( !parent )
+ return;
+
+ I32 idx = parent->children.idx_of( bar );
+ if( idx == -1 || idx == (I32)parent->children.size - 1 )
+ return;
+
+ parent->children.erase( idx );
+ parent->children.push( bar );
+}
+
+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_toolbar = gui_editor_toolbar( 1, EDITOR_LAYOUT_MENU_Y, e->wnd->w - 3, EDITOR_LAYOUT_MENU_H, "editor toolbar" );
+
+ egui->header_back = gui_button( 10, EDITOR_LAYOUT_NAV_Y, 92, 20, "[ back ]", editor_header_back_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 = 7;
+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 - 1 - ( EDITOR_ASSETS_SCROLLBAR_GAP / 2 );
+ 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( ui_clr.border, ui_clr.bg, 0.70f );
+
+ gui_draw_frect( l.row_x, row_y, l.row_w, EDITOR_ASSETS_ROW_H - 2, rowbg );
+
+ 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 = 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, thumb_y + 1, EDITOR_ASSETS_SCROLLBAR_W, max( 1, thumb_h - 1 ), 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 );
+ editor_raise_header_toolbar( e );
+
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+ GUI_BASE* toolbar = egui->header_toolbar;
+ GUI_BUTTON* back = egui->header_back;
+ 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 menu_btn_y = EDITOR_LAYOUT_MENU_Y;
+ 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( toolbar, 1, menu_btn_y, e->wnd->w - 3, EDITOR_LAYOUT_MENU_H );
+
+ editor_set_bounds( back, nav_left_x, 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;
+ editor_raise_header_toolbar( editor );
+ GUI_BASE* toolbar = ( editor && editor->map ) ? editor->gui.header_toolbar : 0;
+ GUI_EDITOR_TOOLBAR* tbar = (GUI_EDITOR_TOOLBAR*)toolbar;
+ U8 menu_open = tbar && ( tbar->file_open || tbar->edit_open );
+ F32 saved_mx = input.mouse.pos.x;
+ F32 saved_my = input.mouse.pos.y;
+ if( menu_open ) {
+ editor_menu_hover_mask_active = 1;
+ editor_menu_hover_real_x = (I32)saved_mx;
+ editor_menu_hover_real_y = (I32)saved_my;
+ input.mouse.pos.x = -100000.f;
+ input.mouse.pos.y = -100000.f;
+ } else {
+ editor_menu_hover_mask_active = 0;
+ }
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 );
@@ -15,16 +1097,43 @@ void gui_editorwindow_draw_fn( void* ptr ) {
if( it->draw_fn ) it->draw_fn( it );
else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it );
} );
+
+ input.mouse.pos.x = saved_mx;
+ input.mouse.pos.y = saved_my;
+ editor_menu_hover_mask_active = 0;
+}
+
+static void gui_editorwindow_input_fn( void* ptr ) {
+ GUI_EDITORWINDOW* wnd = (GUI_EDITORWINDOW*)ptr;
+ editor_raise_header_toolbar( editor );
+
+ GUI_BASE* toolbar = ( editor && editor->map ) ? editor->gui.header_toolbar : 0;
+ if( toolbar && toolbar->enabled && toolbar->input_fn )
+ toolbar->input_fn( toolbar );
+
+ GUI_EDITOR_TOOLBAR* tbar = (GUI_EDITOR_TOOLBAR*)toolbar;
+ U8 menu_open = tbar && ( tbar->file_open || tbar->edit_open );
+ if( menu_open )
+ return;
+
+ wnd->children.each( fn( GUI_BASE** childptr ) {
+ GUI_BASE* child = *childptr;
+ if( !child || child == toolbar || !child->enabled || !child->input_fn )
+ return;
+
+ child->input_fn( child );
+ } );
}
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;
+ wnd->input_fn = gui_editorwindow_input_fn;
strcpy( wnd->name, "EDITORWINDOW" );
_gui.windows.push( wnd );
@@ -38,14 +1147,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<GUI_LIST_ENTRY>* 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 +1164,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 +1190,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 +1246,49 @@ void editor_create_map_view( GAME_EDITOR* e ) {
gui_free( *ptr );
} );
w->children.clear();
+ editor_clear_gui_state_refs( e );
+ 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..b68846e 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<VEC2> 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
@@ -389,7 +433,9 @@ void gui_editor_2dview_create_poly_from_drag( GUI_EDITOR_2DVIEW* view ) {
}
map_polygon_calc_bounds( &newp );
+ I32 poly_idx = editor->map->polygons.size;
editor->map->polygons.push( newp );
+ editor_undo_record_create_poly( editor, poly_idx );
gui_editor_2dview_check_bounds_preserve_view( view );
}
@@ -399,19 +445,25 @@ void gui_editor_2dview_create_wallpoly_from_drag( GUI_EDITOR_2DVIEW* view ) {
return;
I32 propid = gui_editor_2dview_find_or_create_wallpoly_propid();
+ I32 first_wall_idx = editor->map->walls.size;
- 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 );
}
+ I32 new_wall_count = editor->map->walls.size - first_wall_idx;
+ editor_undo_record_create_walls( editor, first_wall_idx, new_wall_count );
gui_editor_2dview_check_bounds_preserve_view( view );
}
@@ -420,6 +472,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 +484,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 +499,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 +512,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<VERTEX> 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 +538,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 +554,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 +568,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 +597,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 +618,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 +641,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 +660,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 +805,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 +819,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 +850,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 +863,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 +875,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 +942,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 +990,7 @@ void gui_editor_2dview_input_select_polygons( GUI_EDITOR_2DVIEW* view ) {
LIST<VEC2> 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 +1026,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 +1052,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)" );
@@ -1097,8 +1146,11 @@ void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) {
U8 m1 = gui_mbutton_down( 0 );
if( !m1 ) {
if( view->held ) {
+ if( view->pending_wall_undo_idx >= 0 )
+ editor_undo_record_create_walls( editor, view->pending_wall_undo_idx, 1 );
gui_editor_2dview_check_bounds_preserve_view( view );
}
+ view->pending_wall_undo_idx = -1;
view->held = 0;
view->curdrag = 0;
return;
@@ -1109,18 +1161,22 @@ 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;
I32 idx = editor->map->walls.size;
editor->map->walls.push( neww );
+ view->pending_wall_undo_idx = idx;
view->curdrag = &editor->map->walls[idx].end;
view->held = 1;
@@ -1133,8 +1189,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 +1236,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;
@@ -1289,12 +1347,31 @@ void gui_editor_view2d_delete_obj( GUI_EDITOR_2DVIEW* view ) {
void gui_editor_2dview_key_input( GUI_EDITOR_2DVIEW* view ) {
static U8 del_held = 0;
+ static U8 undo_held = 0;
+ static U8 redo_held = 0;
if( kb_down( SDLK_DELETE ) && !input.mouselock ) {
if( !del_held )
gui_editor_view2d_delete_obj( view );
del_held = 1;
} else del_held = 0;
+
+ U8 ctrl = kb_down( SDLK_LCTRL ) || kb_down( SDLK_RCTRL );
+ U8 z = kb_down( SDLK_z );
+ if( ctrl && z && !input.mouselock ) {
+ if( !undo_held )
+ editor_undo( editor );
+
+ undo_held = 1;
+ } else undo_held = 0;
+
+ U8 r = kb_down( SDLK_r );
+ if( ctrl && r && !input.mouselock ) {
+ if( !redo_held )
+ editor_redo( editor );
+
+ redo_held = 1;
+ } else redo_held = 0;
}
void gui_editor_2dview_input_fn( void* ptr ) {
@@ -1344,30 +1421,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 +1466,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 +1477,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 ) {
@@ -1401,6 +1497,7 @@ GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) {
view->poly_drag = 0;
view->poly_start = { 0.f, 0.f };
view->poly_end = { 0.f, 0.f };
+ view->pending_wall_undo_idx = -1;
GUI_BASE* parent = gui_get_view();
if( !parent )
diff --git a/src/editor/view3d.cpp b/src/editor/view3d.cpp
index 3729823..292760a 100644
--- a/src/editor/view3d.cpp
+++ b/src/editor/view3d.cpp
@@ -48,7 +48,7 @@ void gui_editor_3dview_draw_showpos( GUI_EDITOR_3DVIEW* view ) {
void gui_editor_3dview_draw_fn( void* ptr ) {
GUI_EDITOR_3DVIEW* view = (GUI_EDITOR_3DVIEW*)ptr;
- if( !objl->pl )
+ if( !editor || !editor->map || !objl->pl )
return;
I32 x = gui_relx( view );
@@ -82,6 +82,9 @@ void gui_editor_3dview_draw_fn( void* ptr ) {
void gui_editor_3dview_input_fn( void* ptr ) {
GUI_EDITOR_3DVIEW* view = (GUI_EDITOR_3DVIEW*)ptr;
+ if( !editor || !editor->map )
+ return;
+
if( !input.mouselock )
gui_base_input_fn( view );
@@ -108,6 +111,9 @@ void gui_editor_3dview_create_toolbar( GUI_EDITOR_3DVIEW* view ) {
I32 x = 1, y = view->h - 4;
gui_button( x, y, 90, 18, "compile bsp", pfn( void* b ) {
+ if( !editor || !editor->map )
+ return;
+
if( editor->map->bsp )
bsp_free( editor->map->bsp );
bsp_build_map( editor->map );
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..e66130b 100644
--- a/src/gui/button.cpp
+++ b/src/gui/button.cpp
@@ -5,15 +5,31 @@ 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;
+
+ CLR fill = ui_clr.bg_sec;
+ CLR txt = ui_clr.txt;
+ if( active ) {
+ fill = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
+ }
+ 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 <math.h>
+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/gui/list.cpp b/src/gui/list.cpp
index 90f2f49..2feff47 100644
--- a/src/gui/list.cpp
+++ b/src/gui/list.cpp
@@ -10,18 +10,33 @@ void gui_list_draw_fn( void* ptr ) {
I32 y = gui_rely( list );
gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, list->name );
- y += LIST_TITLE_OFFSET;
+ I32 panel_y = y + LIST_TITLE_OFFSET;
CLR col = gui_is_fg_window( list )? ui_clr.border : ui_clr.border_inactive;
- gui_draw_frect( x, y, list->w, list->h, col );
- gui_draw_frect( x+1, y+1, list->w-2, list->h-2, ui_clr.bg_sec );
+ gui_draw_frect( x, panel_y, list->w, list->h, col );
+ gui_draw_frect( x + 1, panel_y + 1, list->w - 2, list->h - 2, ui_clr.bg_sec );
+
+ I32 txt_h = 12;
+ gui_draw_get_str_bounds( 0, &txt_h, FNT_JPN12, "ag" );
I32 yoff = 0;
list->plist->each( fn( GUI_LIST_ENTRY* e ) {
U8 selected = e->val == *list->pval;
- CLR col = selected? ui_clr.txt : ui_clr.txt_inactive;
+ I32 row_x = x + 1;
+ I32 row_y = panel_y + 1 + yoff;
+ I32 row_w = list->w - 2;
+ if( row_w < 1 ) row_w = 1;
+ I32 row_h = LIST_ITEM_HEIGHT;
+
+ if( selected ) {
+ CLR sel_bg = CLR::blend( ui_clr.border, ui_clr.bg, 0.70f );
+ gui_draw_frect( row_x, row_y, row_w, row_h, sel_bg );
+ }
+
+ CLR col = selected ? ui_clr.txt : ui_clr.txt_inactive;
+ I32 txt_y = row_y + ( row_h - txt_h ) / 2;
- gui_draw_str( x + 4, y + yoff + 6, ALIGN_L, FNT_JPN12, col, e->title );
+ gui_draw_str( row_x + 3, txt_y, ALIGN_L, FNT_JPN12, col, e->title );
yoff += LIST_ITEM_HEIGHT;
} );
}
@@ -31,6 +46,7 @@ void gui_list_input_fn( void* ptr ) {
I32 x = gui_relx( list );
I32 y = gui_rely( list );
+ I32 panel_y = y + LIST_TITLE_OFFSET;
I32 w = list->w;
I32 h = list->h;
@@ -38,12 +54,12 @@ void gui_list_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 inbounds = mx >= x && mx <= x + w && my >= panel_y && my <= panel_y + h;
if( !inbounds )
return;
- I32 diff = my - y;
- I32 idx = diff / LIST_ITEM_HEIGHT - 1;
+ I32 diff = my - panel_y;
+ I32 idx = diff / LIST_ITEM_HEIGHT;
if( idx >= list->plist->size ) idx = list->plist->size - 1;
if( idx < 0 ) idx = 0;
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 );