11#include "hud.h"
22#include "asset_path.h"
3+ #include "ulog_replay.h"
34#include "raylib.h"
45#include "raymath.h"
56
@@ -497,6 +498,37 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
497498 float dot_x = prog_x + prog_w * pb -> progress ;
498499 DrawCircle ((int )dot_x , (int )(prog_y + prog_h / 2.0f ), 3 * s , accent );
499500
501+ // Flight mode markers on timeline
502+ if (pb -> mode_changes && pb -> mode_change_count > 0 && pb -> duration_s > 0.0f ) {
503+ float fs_marker = 9 * s ;
504+ float last_label_x = -100.0f ; // track last drawn label to avoid overlap
505+ for (int i = 0 ; i < pb -> mode_change_count ; i ++ ) {
506+ float t = pb -> mode_changes [i ].time_s / pb -> duration_s ;
507+ if (t < 0.0f || t > 1.0f ) continue ;
508+ float mx = prog_x + prog_w * t ;
509+
510+ // Tick mark: white and on top once past, dim accent when ahead
511+ bool past = (t <= pb -> progress );
512+ Color tick_col = past
513+ ? (Color ){255 , 255 , 255 , 220 }
514+ : (Color ){accent .r , accent .g , accent .b , 80 };
515+ DrawCircle ((int )mx , (int )(prog_y + prog_h / 2.0f ), 2 * s , tick_col );
516+
517+ // Label (skip if too close to previous label)
518+ if (mx - last_label_x > 40 * s ) {
519+ const char * name = ulog_nav_state_name (pb -> mode_changes [i ].nav_state );
520+ Vector2 tw = MeasureTextEx (h -> font_label , name , fs_marker , 0.5f );
521+ float lx = mx - tw .x / 2.0f ;
522+ if (lx < prog_x ) lx = prog_x ;
523+ if (lx + tw .x > prog_x + prog_w ) lx = prog_x + prog_w - tw .x ;
524+ DrawTextEx (h -> font_label , name ,
525+ (Vector2 ){lx , prog_y - tw .y - 2 * s },
526+ fs_marker , 0.5f , tick_col );
527+ last_label_x = mx ;
528+ }
529+ }
530+ }
531+
500532 cx = prog_x + prog_w + 8 * s ;
501533
502534 // Duration
@@ -570,22 +602,18 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
570602 float sep1_x = adi_cx + inst_radius + sep_margin ; // instruments | NAV
571603 float sep3_x = timer_x - sep_margin ; // ENERGY | timer
572604
573- // Place sep2 to split the zone proportionally (3 NAV : 4 ENERGY)
605+ // Distribute all 7 telemetry items with equal step across the zone
574606 float tel_zone_w = sep3_x - sep1_x ;
575- float sep2_x = sep1_x + tel_zone_w * 3.0f / 7.0f ;
607+ float item_step = tel_zone_w / 7.0f ;
608+ float item_x0 = sep1_x + sep_margin ;
576609
577- // Evenly distribute NAV items (3) between sep1 and sep2
578- // space-evenly: gap = zone / (N+1), item[i] at gap*(i+1)
579- float nav_zone = sep2_x - sep1_x ;
580- float nav_gap = nav_zone / 4.0f ; // 3 items + 1 = 4 gaps
581- float nav_start = sep1_x + nav_gap ;
582- float nav_step = nav_gap ;
610+ float nav_start = item_x0 ;
611+ float nav_step = item_step ;
612+ float energy_start = item_x0 + 3 * item_step ;
613+ float energy_step = item_step ;
583614
584- // Evenly distribute ENERGY items (4) between sep2 and sep3
585- float energy_zone = sep3_x - sep2_x ;
586- float energy_gap = energy_zone / 5.0f ; // 4 items + 1 = 5 gaps
587- float energy_start = sep2_x + energy_gap ;
588- float energy_step = energy_gap ;
615+ // Separator between NAV and ENERGY groups
616+ float sep2_x = sep1_x + 3 * item_step ;
589617
590618 float nav_group_x = nav_start ; // used by secondary rows
591619
@@ -607,7 +635,10 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
607635 snprintf (b , sizeof (b ), "%03d" , ((int )v -> heading_deg % 360 + 360 ) % 360 );
608636 DrawTextEx (h -> font_value , b , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , value_color );
609637 Vector2 vw = MeasureTextEx (h -> font_value , b , fs_value , 0.5f );
610- DrawTextEx (h -> font_label , "deg" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
638+ Vector2 uw = MeasureTextEx (h -> font_label , "deg" , fs_unit , 0.5f );
639+ float boundary = item_x0 + 1 * item_step ;
640+ if (x + vw .x + 3 + uw .x < boundary - 4 * s )
641+ DrawTextEx (h -> font_label , "deg" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
611642 }
612643
613644 // ROLL
@@ -637,7 +668,10 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
637668 snprintf (b , sizeof (b ), "%.1f" , v -> altitude_rel );
638669 DrawTextEx (h -> font_value , b , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , value_color );
639670 Vector2 vw = MeasureTextEx (h -> font_value , b , fs_value , 0.5f );
640- DrawTextEx (h -> font_label , "m" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
671+ Vector2 uw = MeasureTextEx (h -> font_label , "m" , fs_unit , 0.5f );
672+ float boundary = item_x0 + 4 * item_step ;
673+ if (x + vw .x + 3 + uw .x < boundary - 4 * s )
674+ DrawTextEx (h -> font_label , "m" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
641675 }
642676
643677 // GS
@@ -648,7 +682,10 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
648682 snprintf (b , sizeof (b ), "%.1f" , v -> ground_speed );
649683 DrawTextEx (h -> font_value , b , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , value_color );
650684 Vector2 vw = MeasureTextEx (h -> font_value , b , fs_value , 0.5f );
651- DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
685+ Vector2 uw = MeasureTextEx (h -> font_label , "m/s" , fs_unit , 0.5f );
686+ float boundary = item_x0 + 5 * item_step ;
687+ if (x + vw .x + 3 + uw .x < boundary - 4 * s )
688+ DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
652689 }
653690
654691 // AS
@@ -660,7 +697,10 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
660697 snprintf (b , sizeof (b ), "%.1f" , v -> airspeed );
661698 DrawTextEx (h -> font_value , b , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , value_color );
662699 Vector2 vw = MeasureTextEx (h -> font_value , b , fs_value , 0.5f );
663- DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
700+ Vector2 uw = MeasureTextEx (h -> font_label , "m/s" , fs_unit , 0.5f );
701+ float boundary = item_x0 + 6 * item_step ;
702+ if (x + vw .x + 3 + uw .x < boundary - 4 * s )
703+ DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
664704 } else {
665705 DrawTextEx (h -> font_value , "--" , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , dim_color );
666706 }
@@ -678,7 +718,9 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
678718 snprintf (b , sizeof (b ), "%.1f%s" , v -> vertical_speed , arrow );
679719 DrawTextEx (h -> font_value , b , (Vector2 ){x , (float )value_y }, fs_value , 0.5f , vs_color );
680720 Vector2 vw = MeasureTextEx (h -> font_value , b , fs_value , 0.5f );
681- DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
721+ Vector2 uw = MeasureTextEx (h -> font_label , "m/s" , fs_unit , 0.5f );
722+ if (x + vw .x + 3 + uw .x < sep3_x - 4 * s )
723+ DrawTextEx (h -> font_label , "m/s" , (Vector2 ){x + vw .x + 3 , (float )(value_y + unit_y_off )}, fs_unit , 0.5f , dim_color );
682724 }
683725
684726 // Timer (sim time from HIL_STATE_QUATERNION)
@@ -772,36 +814,78 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
772814 float line_h = 24 * s ;
773815 float col_gap = 24 * s ;
774816
817+ // Grouped shortcut entries: NULL key = section header
775818 typedef struct { const char * key ; const char * action ; } shortcut_entry_t ;
776- shortcut_entry_t entries [] = {
777- {"C" , "Toggle camera mode (Chase / FPV)" },
778- {"V" , "Cycle view mode (Grid / Rez / Snow)" },
779- {"TAB" , "Cycle to next vehicle" },
780- {"[ / ]" , "Previous / next vehicle" },
781- {"1-9" , "Select vehicle directly" },
782- {"Shift+1-9" , "Toggle pin/unpin vehicle to HUD" },
783- {"H" , "Toggle HUD visibility" },
784- {"M" , "Cycle vehicle model" },
785- {"Left-drag" , "Orbit camera (chase mode)" },
786- {"Scroll" , "Zoom FOV" },
819+
820+ // Left column: VIEW + VEHICLE
821+ shortcut_entry_t left_col [] = {
822+ {NULL , "VIEW" },
823+ {"C" , "Camera mode (Chase / FPV)" },
824+ {"V" , "View mode (Grid / Rez / Snow)" },
825+ {"F" , "Terrain texture" },
826+ {"K" , "Arm colors (classic / modern)" },
827+ {"O" , "Orthographic side panel" },
828+ {"Ctrl+D" , "Debug overlay" },
829+ {"Alt+1-7" , "Ortho views (1=perspective)" },
830+ {NULL , "VEHICLE MODEL" },
831+ {"M" , "Switch variant (Shift: all)" },
832+ {NULL , "MULTI-VEHICLE" },
833+ {"TAB" , "Next vehicle" },
834+ {"[ / ]" , "Prev / next vehicle" },
835+ {"1-9" , "Select vehicle" },
836+ {"Sh+1-9" , "Pin / unpin to HUD" },
837+ };
838+
839+ // Right column: HUD + CAMERA + REPLAY
840+ shortcut_entry_t right_col [] = {
841+ {NULL , "HUD" },
842+ {"H" , "Toggle HUD" },
843+ {"T" , "Cycle trail mode" },
844+ {"G" , "Ground track projection" },
787845 {"?" , "Toggle this help" },
788- {"Space" , "Pause/resume replay" },
789- {"+/-" , "Replay speed" },
846+ {NULL , "CAMERA" },
847+ {"Drag" , "Orbit (chase mode)" },
848+ {"Scroll" , "Zoom FOV" },
849+ {"Alt+Scrl" , "Zoom ortho span" },
850+ {NULL , "REPLAY" },
851+ {"Space" , "Pause / resume" },
852+ {"+/-" , "Playback speed" },
790853 {"<-/->" , "Seek 5s (Shift: 30s)" },
791- {"L" , "Toggle replay loop" },
792- {"R" , "Restart replay" },
854+ {"L" , "Toggle loop" },
855+ {"I" , "Interpolation" },
856+ {"R" , "Restart" },
793857 };
794- int entry_count = sizeof (entries ) / sizeof (entries [0 ]);
795858
796- // Measure widths for alignment
859+ int left_count = sizeof (left_col ) / sizeof (left_col [0 ]);
860+ int right_count = sizeof (right_col ) / sizeof (right_col [0 ]);
861+ int max_rows = left_count > right_count ? left_count : right_count ;
862+
863+ float help_fs_group = 14 * s ;
864+ float group_top_pad = 6 * s ;
865+
866+ // Measure max key width across both columns
797867 float max_key_w = 0 ;
798- for (int i = 0 ; i < entry_count ; i ++ ) {
799- Vector2 kw = MeasureTextEx (h -> font_value , entries [i ].key , help_fs , 0.5f );
868+ for (int i = 0 ; i < left_count ; i ++ ) {
869+ if (!left_col [i ].key ) continue ;
870+ Vector2 kw = MeasureTextEx (h -> font_value , left_col [i ].key , help_fs , 0.5f );
871+ if (kw .x > max_key_w ) max_key_w = kw .x ;
872+ }
873+ for (int i = 0 ; i < right_count ; i ++ ) {
874+ if (!right_col [i ].key ) continue ;
875+ Vector2 kw = MeasureTextEx (h -> font_value , right_col [i ].key , help_fs , 0.5f );
800876 if (kw .x > max_key_w ) max_key_w = kw .x ;
801877 }
802878
803- float panel_w = max_key_w + col_gap + 320 * s ;
804- float panel_h = 40 * s + entry_count * line_h + 20 * s ;
879+ // Count group headers for extra padding
880+ int left_headers = 0 , right_headers = 0 ;
881+ for (int i = 0 ; i < left_count ; i ++ ) if (!left_col [i ].key ) left_headers ++ ;
882+ for (int i = 0 ; i < right_count ; i ++ ) if (!right_col [i ].key ) right_headers ++ ;
883+ int max_headers = left_headers > right_headers ? left_headers : right_headers ;
884+
885+ float col_w = max_key_w + col_gap + 220 * s ;
886+ float mid_gap = 32 * s ;
887+ float panel_w = col_w * 2 + mid_gap + 40 * s ;
888+ float panel_h = 40 * s + max_rows * line_h + max_headers * group_top_pad + 20 * s ;
805889 float panel_x = (screen_w - panel_w ) / 2.0f ;
806890 float panel_y = (screen_h - panel_h ) / 2.0f ;
807891
@@ -820,16 +904,29 @@ void hud_draw(const hud_t *h, const vehicle_t *vehicles,
820904 (Vector2 ){panel_x + (panel_w - tw .x ) / 2 , panel_y + 12 * s },
821905 help_fs_title , 0.5f , accent );
822906
823- // Entries
824- float ey = panel_y + 40 * s ;
825- float key_x = panel_x + 20 * s ;
826- float action_x = key_x + max_key_w + col_gap ;
827- for (int i = 0 ; i < entry_count ; i ++ ) {
828- DrawTextEx (h -> font_value , entries [i ].key ,
829- (Vector2 ){key_x , ey }, help_fs , 0.5f , accent );
830- DrawTextEx (h -> font_label , entries [i ].action ,
831- (Vector2 ){action_x , ey }, help_fs , 0.5f , value_color );
832- ey += line_h ;
907+ // Draw a column of grouped entries
908+ float ey_start = panel_y + 40 * s ;
909+ shortcut_entry_t * cols [] = { left_col , right_col };
910+ int counts [] = { left_count , right_count };
911+
912+ for (int c = 0 ; c < 2 ; c ++ ) {
913+ float key_x = panel_x + 20 * s + c * (col_w + mid_gap );
914+ float action_x = key_x + max_key_w + col_gap ;
915+ float ey = ey_start ;
916+ for (int i = 0 ; i < counts [c ]; i ++ ) {
917+ if (!cols [c ][i ].key ) {
918+ // Section header
919+ if (i > 0 ) ey += group_top_pad ;
920+ DrawTextEx (h -> font_label , cols [c ][i ].action ,
921+ (Vector2 ){key_x , ey }, help_fs_group , 0.5f , dim_color );
922+ } else {
923+ DrawTextEx (h -> font_value , cols [c ][i ].key ,
924+ (Vector2 ){key_x , ey }, help_fs , 0.5f , accent );
925+ DrawTextEx (h -> font_label , cols [c ][i ].action ,
926+ (Vector2 ){action_x , ey }, help_fs , 0.5f , value_color );
927+ }
928+ ey += line_h ;
929+ }
833930 }
834931 }
835932}
0 commit comments