Files
Cesarus85--ringfall-mjolnir-vr/source/interface/ui_widget.c
T
Stefan MaierandClaude Opus 5.5 0e6b006878 VR options: graphics, refresh rate and resolution rows
The panel's second page (MORE) gets "Graphics" (vr.graphics: Low,
Medium, High, Max), "Refresh rate" and "Resolution" rows, with a note
of what they do. The refresh rate steps through the rates the runtime
offers (XR_FB_display_refresh_rate: host_xr_refresh_rates, new host
imports with host_xr_set_refresh_rate) and changes at once (vr.c asks
for it when vr.refresh_rate changes); where the runtime offers none
(SteamVR on the Steam Frame, whose rate is SteamOS's) the row shows the
rate it runs at, from its frames' period, and stays as it is. The
resolution (vr.resolution_scale, 70 to 120%) is read as the app starts:
the row says when a restart is needed. vr.log_performance's late frames
follow the rate as it changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 19:30:32 +02:00

9325 lines
263 KiB
C

/*
UI_WIDGET.C
symbols in this file:
000D32C0 0020:
_code_000d32c0 (0000)
000D32E0 0020:
_code_000d32e0 (0000)
000D3300 0030:
_event_controller_index_compatible_with_widget (0000)
000D3330 0030:
_set_ui_plasma_effect_color (0000)
000D3360 00a0:
_ui_widgets_initialize (0000)
000D3400 0010:
_ui_widgets_safe_to_load (0000)
000D3410 0010:
_ui_widgets_set_fade_value (0000)
000D3420 0010:
_ui_widget_debug_show_path (0000)
000D3430 0020:
_widget_instance_count_children (0000)
000D3450 0050:
_widget_instance_get_nth_child (0000)
000D34A0 0030:
_ui_widget_realloc (0000)
000D34D0 0020:
_widget_free (0000)
000D34F0 0030:
_ui_widgets_active (0000)
000D3520 0070:
_ui_widgets_active_for_local_player (0000)
000D3590 0050:
_ui_widgets_inhibit_processing (0000)
000D35E0 0020:
_code_000d35e0 (0000)
000D3600 0490:
_draw_bitmap_in_rect (0000)
000D3A90 0020:
_widget_instance_get_topmost_parent (0000)
000D3AB0 0040:
_widget_instance_get_child_index_from_parent (0000)
000D3AF0 0060:
_widget_instance_set_visibility_recursive (0000)
000D3B50 0010:
_main_menu_active (0000)
000D3B60 0010:
_main_menu_is_active (0000)
000D3B70 0030:
_main_menu_screen_is_active (0000)
000D3BA0 0050:
_ui_set_next_level (0000)
000D3BF0 0010:
_ui_widget_load_progress_widget (0000)
000D3C00 0010:
_filesystem_check_thread_is_active (0000)
000D3C10 0040:
_code_000d3c10 (0000)
000D3C50 0030:
_display_error_when_main_menu_loaded (0000)
000D3C80 0090:
_display_error_deferred (0000)
000D3D10 0040:
_display_error_abort_to_dashboard_deferred (0000)
000D3D50 0070:
_ui_start_main_menu_music (0000)
000D3DC0 0060:
_ui_stop_main_menu_music (0000)
000D3E20 0010:
_ui_main_menu_music_active (0000)
000D3E30 0040:
_ui_widgets_disable_pause_game (0000)
000D3E70 0080:
_code_000d3e70 (0000)
000D3EF0 0060:
_code_000d3ef0 (0000)
000D3F50 0030:
_code_000d3f50 (0000)
000D3F80 0090:
_code_000d3f80 (0000)
000D4010 0080:
_code_000d4010 (0000)
000D4090 0050:
_code_000d4090 (0000)
000D40E0 0020:
_code_000d40e0 (0000)
000D4100 0080:
_code_000d4100 (0000)
000D4180 0040:
_code_000d4180 (0000)
000D41C0 0040:
_code_000d41c0 (0000)
000D4200 0050:
_code_000d4200 (0000)
000D4250 01a0:
_code_000d4250 (0000)
000D43F0 0070:
_code_000d43f0 (0000)
000D4460 0060:
_code_000d4460 (0000)
000D44C0 0060:
_code_000d44c0 (0000)
000D4520 0130:
_remap_sticks_for_local_player (0000)
000D4650 0030:
_code_000d4650 (0000)
000D4680 0010:
_code_000d4680 (0000)
000D4690 0010:
_code_000d4690 (0000)
000D46A0 0170:
_code_000d46a0 (0000)
000D4810 00d0:
_code_000d4810 (0000)
000D48E0 0020:
_code_000d48e0 (0000)
000D4900 01d0:
_code_000d4900 (0000)
000D4AD0 0030:
_code_000d4ad0 (0000)
000D4B00 0040:
_code_000d4b00 (0000)
000D4B40 0020:
_code_000d4b40 (0000)
000D4B60 0060:
_code_000d4b60 (0000)
000D4BC0 00a0:
_code_000d4bc0 (0000)
000D4C60 0050:
_get_ui_rgb_white (0000)
000D4CB0 0060:
_get_ui_argb_white (0000)
000D4D10 0050:
_code_000d4d10 (0000)
000D4D60 0040:
_modulate_pixel32_by_real_alpha (0000)
000D4DA0 02a0:
_ui_widget_delete (0000)
000D5040 0050:
_ui_widgets_close_all (0000)
000D5090 0090:
_ui_widgets_close_all_for_local_player (0000)
000D5120 0040:
_ui_widgets_delete_history (0000)
000D5160 0060:
_ui_widgets_pop_stack (0000)
000D51C0 0070:
_main_screen_shell_begin_fade (0000)
000D5230 0060:
_ui_play_audio_feedback_sound (0000)
000D5290 02a0:
_code_000d5290 (0000)
000D5530 0030:
_code_000d5530 (0000)
000D5560 0360:
_draw_string_and_hack_in_icons (0000)
000D58C0 0310:
_code_000d58c0 (0000)
000D5BD0 0440:
_code_000d5bd0 (0000)
000D6010 0050:
_code_000d6010 (0000)
000D6060 00a0:
_code_000d6060 (0000)
000D6100 00e0:
_code_000d6100 (0000)
000D61E0 0050:
_ui_widgets_dispose (0000)
000D6230 0200:
_widget_event_function_list_widget_goto_next_item (0000)
000D6430 0220:
_widget_event_function_list_widget_goto_previous_item (0000)
000D6650 04f0:
_code_000d6650 (0000)
000D6B40 02f0:
_code_000d6b40 (0000)
000D6E30 00b0:
_code_000d6e30 (0000)
000D6EE0 0180:
_render_ui_widgets_postgame (0000)
000D7060 0230:
_render_ui_widgets (0000)
000D7290 01f0:
_code_000d7290 (0000)
000D7480 07e0:
_code_000d7480 (0000)
000D7C60 0220:
_ui_widget_load_by_name_or_tag (0000)
000D7E80 0130:
_main_screen_shell_load (0000)
000D7FB0 00e0:
_network_game_reset_to_pregame_ui (0000)
000D8090 0380:
_display_error (0000)
000D8410 0090:
_display_error_abort_to_dashboard (0000)
000D84A0 0090:
_display_error_damaged_media (0000)
000D8530 0070:
_display_errors_deferred_until_cinematic_stop (0000)
000D85A0 0260:
_display_scenario_help (0000)
000D8800 02a0:
_code_000d8800 (0000)
000D8AA0 0130:
_code_000d8aa0 (0000)
000D8BD0 03c0:
_process_ui_widgets (0000)
00271380 0012:
??_C@_1BC@GEHLELKL@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA8?$AA?$AA@ (0000)
00271394 0012:
??_C@_1BC@DMBIFLPN@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA7?$AA?$AA@ (0000)
002713A8 0012:
??_C@_1BC@IEKEDMJI@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA6?$AA?$AA@ (0000)
002713BC 0012:
??_C@_1BC@JGBBJDHG@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA5?$AA?$AA@ (0000)
002713D0 0012:
??_C@_1BC@COKNPEBD@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA4?$AA?$AA@ (0000)
002713E4 0012:
??_C@_1BC@LDHKMMKK@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA3?$AA?$AA@ (0000)
002713F8 0012:
??_C@_1BC@LMGKLMP@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA2?$AA?$AA@ (0000)
0027140C 0012:
??_C@_1BC@BJHDAECB@?$AAc?$AAu?$AAs?$AAt?$AAo?$AAm?$AA?9?$AA1?$AA?$AA@ (0000)
00271420 000a:
??_C@_19MLOAAMON@?$AAl?$AAo?$AAo?$AAk?$AA?$AA@ (0000)
0027142C 000a:
??_C@_19OACEEDLD@?$AAm?$AAo?$AAv?$AAe?$AA?$AA@ (0000)
00271438 000a:
??_C@_19LHGBOOBH@?$AAb?$AAa?$AAc?$AAk?$AA?$AA@ (0000)
00271444 000e:
??_C@_1O@CNPOHNDJ@?$AAa?$AAc?$AAc?$AAe?$AAp?$AAt?$AA?$AA@ (0000)
00271454 000a:
??_C@_19MEDLKCJO@?$AAz?$AAo?$AAo?$AAm?$AA?$AA@ (0000)
00271460 000e:
??_C@_1O@KCCHFBLA@?$AAc?$AAr?$AAo?$AAu?$AAc?$AAh?$AA?$AA@ (0000)
00271470 0020:
??_C@_1CA@KFDCIJKK@?$AAr?$AAo?$AAt?$AAa?$AAt?$AAe?$AA?9?$AAg?$AAr?$AAe?$AAn?$AAa?$AAd?$AAe?$AAs?$AA?$AA@ (0000)
00271490 001e:
??_C@_1BO@POELNCJG@?$AAr?$AAo?$AAt?$AAa?$AAt?$AAe?$AA?9?$AAw?$AAe?$AAa?$AAp?$AAo?$AAn?$AAs?$AA?$AA@ (0000)
002714B0 001c:
??_C@_1BM@IKPPIMCO@?$AAu?$AAs?$AAe?$AA?9?$AAe?$AAq?$AAu?$AAi?$AAp?$AAm?$AAe?$AAn?$AAt?$AA?$AA@ (0000)
002714CC 000a:
??_C@_19OBFFCANM@?$AAj?$AAu?$AAm?$AAp?$AA?$AA@ (0000)
002714D8 0022:
??_C@_1CC@OGCINJG@?$AAi?$AAn?$AAt?$AAe?$AAg?$AAr?$AAa?$AAt?$AAe?$AAd?$AA?9?$AAl?$AAi?$AAg?$AAh?$AAt?$AA?$AA@ (0000)
002714FC 0020:
??_C@_1CA@BEHOBGA@?$AAp?$AAr?$AAi?$AAm?$AAa?$AAr?$AAy?$AA?9?$AAt?$AAr?$AAi?$AAg?$AAg?$AAe?$AAr?$AA?$AA@ (0000)
0027151C 001c:
??_C@_1BM@HECBOACM@?$AAt?$AAh?$AAr?$AAo?$AAw?$AA?9?$AAg?$AAr?$AAe?$AAn?$AAa?$AAd?$AAe?$AA?$AA@ (0000)
00271538 000e:
??_C@_1O@HCAHPFFC@?$AAa?$AAc?$AAt?$AAi?$AAo?$AAn?$AA?$AA@ (0000)
00271548 0018:
??_C@_1BI@IAFIHGKJ@?$AAr?$AAi?$AAg?$AAh?$AAt?$AA?9?$AAs?$AAt?$AAi?$AAc?$AAk?$AA?$AA@ (0000)
00271560 0016:
??_C@_1BG@GNIACJFB@?$AAl?$AAe?$AAf?$AAt?$AA?9?$AAs?$AAt?$AAi?$AAc?$AAk?$AA?$AA@ (0000)
00271578 0018:
??_C@_1BI@KFEEPENO@?$AAr?$AAi?$AAg?$AAh?$AAt?$AA?9?$AAt?$AAh?$AAu?$AAm?$AAb?$AA?$AA@ (0000)
00271590 0016:
??_C@_1BG@EIJMKLCG@?$AAl?$AAe?$AAf?$AAt?$AA?9?$AAt?$AAh?$AAu?$AAm?$AAb?$AA?$AA@ (0000)
002715A8 0018:
??_C@_1BI@KIILCNEB@?$AAb?$AAa?$AAc?$AAk?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002715C0 001a:
??_C@_1BK@CFIHHNDP@?$AAs?$AAt?$AAa?$AAr?$AAt?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002715DC 0016:
??_C@_1BG@CGEEKDJO@?$AAd?$AAp?$AAa?$AAd?$AA?9?$AAr?$AAi?$AAg?$AAh?$AAt?$AA?$AA@ (0000)
002715F4 0014:
??_C@_1BE@LFENLMOB@?$AAd?$AAp?$AAa?$AAd?$AA?9?$AAl?$AAe?$AAf?$AAt?$AA?$AA@ (0000)
00271608 0014:
??_C@_1BE@MAHEIGBI@?$AAd?$AAp?$AAa?$AAd?$AA?9?$AAd?$AAo?$AAw?$AAn?$AA?$AA@ (0000)
0027161C 0010:
??_C@_1BA@CNADAJOL@?$AAd?$AAp?$AAa?$AAd?$AA?9?$AAu?$AAp?$AA?$AA@ (0000)
0027162C 001c:
??_C@_1BM@DNLFOGBJ@?$AAr?$AAi?$AAg?$AAh?$AAt?$AA?9?$AAt?$AAr?$AAi?$AAg?$AAg?$AAe?$AAr?$AA?$AA@ (0000)
00271648 001a:
??_C@_1BK@IONNGAAO@?$AAl?$AAe?$AAf?$AAt?$AA?9?$AAt?$AAr?$AAi?$AAg?$AAg?$AAe?$AAr?$AA?$AA@ (0000)
00271664 001a:
??_C@_1BK@DHOCGHBB@?$AAw?$AAh?$AAi?$AAt?$AAe?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
00271680 001a:
??_C@_1BK@JINDKLEA@?$AAb?$AAl?$AAa?$AAc?$AAk?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
0027169C 0012:
??_C@_1BC@JDEDLPGE@?$AAy?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002716B0 0012:
??_C@_1BC@HMBBAJIF@?$AAx?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002716C4 0012:
??_C@_1BC@EADHJABC@?$AAb?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002716D8 0012:
??_C@_1BC@KKLBENHA@?$AAa?$AA?9?$AAb?$AAu?$AAt?$AAt?$AAo?$AAn?$AA?$AA@ (0000)
002716EC 000f:
??_C@_0P@OHNLNAJA@levels?2d40?2d40?$AA@ (0000)
002716FC 000f:
??_C@_0P@IAIGJJBI@levels?2d20?2d20?$AA@ (0000)
0027170C 000f:
??_C@_0P@HAMJOBDA@levels?2c40?2c40?$AA@ (0000)
0027171C 000f:
??_C@_0P@BHJEKILI@levels?2c20?2c20?$AA@ (0000)
0027172C 000f:
??_C@_0P@CEDKAMHM@levels?2c10?2c10?$AA@ (0000)
0027173C 000f:
??_C@_0P@ENPIGML@levels?2b40?2b40?$AA@ (0000)
0027174C 000f:
??_C@_0P@MEDHKOMA@levels?2b30?2b30?$AA@ (0000)
0027175C 000f:
??_C@_0P@DPFAEPEF@levels?2a50?2a50?$AA@ (0000)
0027176C 000f:
??_C@_0P@FIANAGMN@levels?2a30?2a30?$AA@ (0000)
0027177C 000f:
??_C@_0P@MMBGMDIK@levels?2a10?2a10?$AA@ (0000)
0027178C 0013:
??_C@_0BD@CKKOGMCD@widget_memory_pool?$AA@ (0000)
002717A0 0008:
__one_over_255 (0000)
_SECONDS_PER_MILLISECOND (0004)
002717A8 0025:
??_C@_0CF@BFKKDAFO@c?3?2halo?2SOURCE?2interface?2ui_widg@ (0000)
002717D0 0007:
??_C@_06KLKLDLLI@widget?$AA@ (0000)
002717D8 0024:
??_C@_0CE@JONHIOKA@expected?5a?5valid?5local_player_in@ (0000)
002717FC 001b:
??_C@_0BL@IGOIGKAA@widget_globals?4initialized?$AA@ (0000)
00271818 0004:
__real@38026b7e (0000)
0027181C 0004:
__real@43490000 (0000)
00271820 0004:
__real@38150d23 (0000)
00271824 0004:
__real@37e0c670 (0000)
00271828 0004:
__real@439b8000 (0000)
0027182C 0004:
__real@3806dd76 (0000)
00271830 000e:
??_C@_0O@CJGPHCNG@the_main_menu?$AA@ (0000)
00271840 000e:
??_C@_0O@LGOJGHAO@unknown?5level?$AA@ (0000)
00271850 0048:
??_C@_0EI@DFLNAEDH@the?5old?5loading?5progress?5screen?5@ (0000)
00271898 0059:
??_C@_0FJ@HLIPNJGE@there?5is?5already?5an?5error?5messag@ (0000)
002718F8 0051:
??_C@_0FB@JEKLNNGC@there?5is?5already?5a?5deferred?5erro@ (0000)
0027194C 0036:
??_C@_0DG@OLDDPPAF@?$CIindex?$DO?$DN0?$CJ?5?$CG?$CG?5?$CIindex?$DMMAXIMUM_NUM@ (0000)
00271988 0047:
??_C@_0EH@LLPKBLA@there?5is?5already?5a?5deferred?5dash@ (0000)
002719D0 001a:
??_C@_0BK@LDNBEGIO@title?5music?5tag?5not?5found?$AA@ (0000)
002719EC 0019:
??_C@_0BJ@DOPIPCPC@starting?5main?5menu?5music?$AA@ (0000)
00271A08 001a:
??_C@_0BK@GFHNHPPO@sound?2music?2title1?2title1?$AA@ (0000)
00271A24 0019:
??_C@_0BJ@OPCCAOJF@stopping?5main?5menu?5music?$AA@ (0000)
00271A40 0012:
??_C@_0BC@MKLFKPBF@duration_ticks?$DO?$DN0?$AA@ (0000)
00271A54 0034:
??_C@_0DE@HOKJOLHA@out?5of?5memory?$CB?5the?5UI?5screen?5his@ (0000)
00271A88 000c:
??_C@_0M@EABMFPBO@top?5?$CG?$CG?5data?$AA@ (0000)
00271A94 0019:
??_C@_0BJ@FOOJDKHD@tail_child?9?$DOnext?5?$DN?$DN?5NULL?$AA@ (0000)
00271AB0 0033:
??_C@_0DD@MMOLGAOL@?$CIchild?9?$DOprevious?5?$DN?$DN?5NULL?$CJ?5?$CG?$CG?5?$CIch@ (0000)
00271AE4 0018:
??_C@_0BI@LBDKLIFA@next?9?$DOprevious?5?$DN?$DN?5child?$AA@ (0000)
00271AFC 0018:
??_C@_0BI@LBKIDJB@child?9?$DOprevious?5?$DN?$DN?5NULL?$AA@ (0000)
00271B18 0041:
??_C@_0EB@BEJBEEHM@initial_indent?$DM0?5in?5render_state@ (0000)
00271B5C 001d:
??_C@_0BN@EEMEPEOK@31?5?$DN?$DN?5get_icon_type?$CIL?$CClook?$CC?$CJ?$AA@ (0000)
00271B7C 0024:
??_C@_0CE@DPIKLLNB@17?5?$DN?$DN?5get_icon_type?$CIL?$CCright?9stic@ (0000)
00271BA0 001d:
??_C@_0BN@GOCJIAID@30?5?$DN?$DN?5get_icon_type?$CIL?$CCmove?$CC?$CJ?$AA@ (0000)
00271BC0 0023:
??_C@_0CD@BJHDPKGJ@16?5?$DN?$DN?5get_icon_type?$CIL?$CCleft?9stick@ (0000)
00271BE8 0041:
??_C@_0EB@PKGKIBFB@?$CD?$CD?$CDWARNING?3?5possible?5memory?5leak@ (0000)
00271C2C 0022:
??_C@_0CC@MHEDEDIP@widget?5pause?5counter?5out?5of?5whac@ (0000)
00271C50 0025:
??_C@_0CF@EFHCMBNF@event?5handler?5failed?5to?5spawn?5wi@ (0000)
00271C78 004b:
??_C@_0EL@BONJAKAK@a?5?8widget?5deleted?8?5event?5handler@ (0000)
00271CC4 0025:
??_C@_0CF@OIBDABJ@widget?5?$CG?$CG?5widget_globals?4initial@ (0000)
00271CEC 001a:
??_C@_0BK@EFAHKEKA@sound?2sfx?2ui?2flag_failure?$AA@ (0000)
00271D08 0012:
??_C@_0BC@LFDBPFAD@sound?2sfx?2ui?2back?$AA@ (0000)
00271D1C 0015:
??_C@_0BF@FGMCPHMP@sound?2sfx?2ui?2forward?$AA@ (0000)
00271D34 0014:
??_C@_0BE@EOGFPNHD@sound?2sfx?2ui?2cursor?$AA@ (0000)
00271D48 003a:
??_C@_0DK@MJICNCDL@invalid?5controller?5index?5specifi@ (0000)
00271D88 005d:
??_C@_0FN@LKLJLEJ@_list_items_generated_from_strin@ (0000)
00271DE8 0069:
??_C@_0GJ@EGCLHEFN@no?5child?5widget?5references?5are?5n@ (0000)
00271E58 0063:
??_C@_0GD@BFCMHBJK@_list_items_generated_from_strin@ (0000)
00271EBC 0026:
??_C@_0CG@GCOFCMPN@icon_index?$DO?$DN0?5?$CG?$CG?5icon_index?$DMNUM_@ (0000)
00271EE4 0006:
??_C@_05MAJJAKPI@FALSE?$AA@ (0000)
00271EEC 0004:
??_C@_13EJFHHPOP@?$AA?$CF?$AA?$AA@ (0000)
00271EF0 0047:
??_C@_0EH@BDEOCECI@failed?5to?5render?5text?5box?5widget@ (0000)
00271F38 0042:
??_C@_0EC@OINFLBMA@failed?5to?5render?5text?5box?5widget@ (0000)
00271F7C 0020:
??_C@_1CA@EBMNPDDP@?$AA?$DM?$AAo?$AAu?$AAt?$AA?5?$AAo?$AAf?$AA?5?$AAm?$AAe?$AAm?$AAo?$AAr?$AAy?$AA?$DO?$AA?$AA@ (0000)
00271FA0 0049:
??_C@_0EJ@ICFFKBCK@failed?5to?5render?5spinner?5list?5it@ (0000)
00271FF0 0044:
??_C@_0EE@GFHAOED@failed?5to?5render?5spinner?5list?5it@ (0000)
00272034 0029:
??_C@_0CJ@CLCMGDOK@failed?5to?5find?5event?5focus?5targe@ (0000)
00272060 0046:
??_C@_0EG@GAAKHNIE@failed?5to?5spawn?5thread?5for?5files@ (0000)
002720A8 003c:
??_C@_0DM@DBODFMFA@begining?5filesystem?5checks?5?$CG?5sav@ (0000)
002720E4 002b:
??_C@_0CL@DAIOFIBL@widget_globals?4initialization_th@ (0000)
00272110 003d:
??_C@_0DN@CLOOAICL@failed?5to?5set?5focus?5to?5the?5next?5@ (0000)
00272150 006d:
??_C@_0GN@CDIJNPDM@spinner?5lists?5with?5more?5that?51?5v@ (0000)
002721C0 0033:
??_C@_0DD@MMCOEHPE@spinner?5lists?5must?5be?5either?51?9?5@ (0000)
002721F8 0041:
??_C@_0EB@HAOLGJEB@failed?5to?5set?5focus?5to?5the?5?$CD?$CFd?5l@ (0000)
0027223C 0034:
??_C@_0DE@PKDHGMBO@calling?5a?5list?5widget?5function?5o@ (0000)
00272270 0016:
??_C@_0BG@ENJMIAKH@widget?9?$DOfocused_child?$AA@ (0000)
00272288 0048:
??_C@_0EI@GEFPBJMP@couldn?8t?5load?5conditional?5widget@ (0000)
002722D0 0029:
??_C@_0CJ@FPJLGLJO@condition?5handler?5failed?5to?5spaw@ (0000)
00272300 0045:
??_C@_0EF@JLKENOEN@failed?5to?5open?5widget?5because?5th@ (0000)
00272348 0019:
??_C@_0BJ@KIDHBPBC@previous?9?$DOnext?5?$DN?$DN?5widget?$AA@ (0000)
00272364 0019:
??_C@_0BJ@FGBHJFEB@next?9?$DOprevious?5?$DN?$DN?5widget?$AA@ (0000)
00272380 0044:
??_C@_0EE@NFLPKLKO@failed?5to?5close?5widget?5because?5e@ (0000)
002723C8 0045:
??_C@_0EF@EEMBCNDP@failed?5to?5reload?5widget?5because?5@ (0000)
00272410 004e:
??_C@_0EO@PGEAPKEN@failed?5to?5give?5focus?5to?5a?5widget@ (0000)
00272460 001e:
??_C@_0BO@HFOFCHPP@event?5handler?5function?5failed?$AA@ (0000)
00272480 002a:
??_C@_0CK@GOFLJEI@failed?5to?5run?5ui?5widget?5event?5sc@ (0000)
002724AC 000c:
??_C@_0M@EJOMDMOI@ui?2small_ui?$AA@ (0000)
002724B8 0016:
??_C@_0BG@ELECBMPG@window_bounds?5?$CB?$DN?5NULL?$AA@ (0000)
002724D0 001f:
??_C@_0BP@JAOAINDB@failed?5to?5load?5widget?5children?$AA@ (0000)
002724F0 009a:
??_C@_0JK@BLPAHHHK@if?5the?5_widget_pass_handled_even@ (0000)
0027258C 0021:
??_C@_0CB@BADDKMAA@widget?9?$DOlocal_player_index?$DN?$DNNONE@ (0000)
002725B0 0037:
??_C@_0DH@DCPBMEID@widget?5?$CG?$CG?5definition?5?$CG?$CG?5event?5?$CG?$CG@ (0000)
002725E8 002c:
??_C@_0CM@OJPOGECC@ui_widget_definition?5tag?5?8?$CFs?8?1?$CFd@ (0000)
00272614 002c:
??_C@_0CM@MEFFCNMB@failed?5to?5create?5new?5widget?$DL?5out@ (0000)
00272640 0035:
??_C@_0DF@PBOMEHIM@?$CIwidget_stack?$DO?$DN0?$CJ?5?$CG?$CG?5?$CIwidget_sta@ (0000)
00272678 0026:
??_C@_0CG@GBPNNCLI@?$CIname?5?$CB?$DN?5NULL?$CJ?5?$HM?$HM?5?$CItag_index?5?$CB?$DN?5@ (0000)
002726A0 002a:
??_C@_0CK@OFOHOPCB@failed?5to?5initialize?5the?5virtual@ (0000)
002726CC 0028:
??_C@_0CI@EBHLJEJP@failed?5to?5load?5main?5screen?5shell@ (0000)
002726F4 001d:
??_C@_0BN@PLHBGNEF@ui?2shell?2main_menu?2main_menu?$AA@ (0000)
00272714 0018:
??_C@_0BI@HFFEIMKP@xbox?5command?5line?$DN?5?8?$CFs?8?$AA@ (0000)
0027272C 0006:
??_C@_05GLGMCIFH@xdemo?$AA@ (0000)
00272734 002f:
??_C@_0CP@FMNMOLIB@failed?5to?5load?5networked?5pregame@ (0000)
00272768 005b:
??_C@_0FL@CANKNMEF@ui?2shell?2main_menu?2multiplayer_t@ (0000)
002727C4 002a:
??_C@_0CK@BADOGGKF@failed?5to?5load?5map?5select?5postga@ (0000)
002727F0 0060:
??_C@_0GA@GFIHBLPH@ui?2shell?2main_menu?2multiplayer_t@ (0000)
00272850 0035:
??_C@_0DF@GJABDHAE@failed?5to?5load?5pregame?5screen?5af@ (0000)
00272888 0063:
??_C@_0GD@HIMIFBNM@ui?2shell?2main_menu?2multiplayer_t@ (0000)
002728EC 0025:
??_C@_0CF@GLGHHELH@widget?5pause?5counter?5is?5out?5of?5w@ (0000)
00272914 002f:
??_C@_0CP@BGKPJLCC@expected?5a?5text?5box?5widget?5in?5th@ (0000)
00272944 0032:
??_C@_0DC@JNAKMNPN@error?5screen?5widget?5tag?5not?5laye@ (0000)
00272978 0020:
??_C@_0CA@PLEKCLAA@failed?5to?5display?5error?5message?$AA@ (0000)
00272998 0048:
??_C@_0EI@MDMAJGJE@there?5is?5already?5an?5error?5messag@ (0000)
002729E0 0032:
??_C@_0DC@JLPDPOMG@aborting?5to?5the?5main?5menu?5root?0?5@ (0000)
00272A18 0044:
??_C@_0EE@HGHDGOBG@?$CIwidget_stack?$DO?$DN0?$CJ?5?$CG?$CG?5?$CIwidget_sta@ (0000)
00272A5C 001b:
??_C@_0BL@ILOCFJOB@invalid?5local?5player?5count?$AA@ (0000)
00272A78 0028:
??_C@_0CI@NLLLFMIK@ui?2shell?2error?2error_nonmodal_qt@ (0000)
00272AA0 0025:
??_C@_0CF@IPCJKMOA@ui?2shell?2error?2error_modal_qtrsc@ (0000)
00272AC8 0029:
??_C@_0CJ@MDGMGIIG@ui?2shell?2error?2error_nonmodal_ha@ (0000)
00272AF4 0026:
??_C@_0CG@GOFPHCDA@ui?2shell?2error?2error_modal_halfs@ (0000)
00272B1C 0029:
??_C@_0CJ@COJPHONK@ui?2shell?2error?2error_nonmodal_fu@ (0000)
00272B48 0026:
??_C@_0CG@IDKMGEGM@ui?2shell?2error?2error_modal_fulls@ (0000)
00272B70 0059:
??_C@_0FJ@LCJINDPJ@there?5is?5already?5a?5deferred?9for?9@ (0000)
00272BCC 001b:
??_C@_0BL@DHJKOIEB@failed?5to?5load?5?8?$CFs?8?5widget?$AA@ (0000)
00272BE8 001b:
??_C@_0BL@PIJOAKFD@expected?5a?5text?5box?5widget?$AA@ (0000)
00272C04 003b:
??_C@_0DL@MFGPNPO@ui?2shell?2error?2error_abort_to_da@ (0000)
00272C40 0028:
??_C@_0CI@JFEBNJBP@ui?2shell?2error?2error_abort_to_da@ (0000)
00272C68 0017:
??_C@_0BH@FIKKMDKG@Noooooooooooooooooo?$CB?$CB?$CB?$AA@ (0000)
00272C80 003a:
??_C@_0DK@KGMPEFEN@can?8t?5display?5scenario?5help?5beca@ (0000)
00272CBC 003d:
??_C@_0DN@BOLHGMPP@can?8t?5display?5scenario?5help?$DL?5unk@ (0000)
00272CFC 0023:
??_C@_0CD@DGCLPNFD@failed?5to?5load?5in?9game?5help?5dial@ (0000)
00272D20 002f:
??_C@_0CP@ODEPBIAI@expected?5text?5box?5widget?5in?5play@ (0000)
00272D50 0036:
??_C@_0DG@EMGDHLEG@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272D88 0004:
??_C@_03JGNLDLAI@d40?$AA@ (0000)
00272D8C 0036:
??_C@_0DG@EIOOAHPE@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272DC4 0004:
??_C@_03JCFGEHLK@d20?$AA@ (0000)
00272DC8 0036:
??_C@_0DG@NBLEEDPP@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272E00 0004:
??_C@_03LAMADLB@c40?$AA@ (0000)
00272E04 0036:
??_C@_0DG@NFDJDPEN@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272E3C 0004:
??_C@_03PIBHPAD@c20?$AA@ (0000)
00272E40 0036:
??_C@_0DG@NHHPIBBE@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272E78 0004:
??_C@_03NMHMBFK@c10?$AA@ (0000)
00272E7C 0036:
??_C@_0DG@GJAICEJK@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272EB4 0004:
??_C@_03LDLAGENE@b40?$AA@ (0000)
00272EB8 0036:
??_C@_0DG@GMEHDCBP@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272EF0 0004:
??_C@_03LGPPHCFB@b30?$AA@ (0000)
00272EF4 0036:
??_C@_0DG@HKHPOBED@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272F2C 0004:
??_C@_03KAMHKBAN@a50?$AA@ (0000)
00272F30 0036:
??_C@_0DG@HOPCJNPB@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272F68 0004:
??_C@_03KEEKNNLP@a30?$AA@ (0000)
00272F6C 0036:
??_C@_0DG@HNHGEJJP@ui?2shell?2solo_game?2player_help?2p@ (0000)
00272FA4 0004:
??_C@_03KHMOAJNB@a10?$AA@ (0000)
00272FA8 0010:
??_C@_0BA@CDPMFNHN@string_index?$DO?$DN0?$AA@ (0000)
00272FB8 002d:
??_C@_0CN@FAHAPHCM@failed?5to?5load?5full?5screen?5pause@ (0000)
00272FE8 0029:
??_C@_0CJ@OJHKNGAB@ui?2shell?2solo_game?2pause_game?2pa@ (0000)
00273018 0053:
??_C@_0FD@MJAJHNPK@the?5ui?5seems?5to?5be?5confused?4?4?4?5a@ (0000)
0027306C 002e:
??_C@_0CO@LCDPHEGN@failed?5to?5load?5split?5screen?5paus@ (0000)
0027309C 0036:
??_C@_0DG@NEJLJPFI@ui?2shell?2solo_game?2pause_game?2pa@ (0000)
002730D4 0030:
??_C@_0DA@JLINLCDA@invalid?5local?5player?5count?5for?5m@ (0000)
00273104 0033:
??_C@_0DD@CLAJLOAB@ui?2shell?2multiplayer_game?2pause_@ (0000)
00273138 0033:
??_C@_0DD@NDMLPFLH@ui?2shell?2multiplayer_game?2pause_@ (0000)
0027316C 002d:
??_C@_0CN@BIDFDGIG@failed?5to?5load?5multiplayer?5pause@ (0000)
0027319C 0033:
??_C@_0DD@KPKKNAGM@ui?2shell?2multiplayer_game?2pause_@ (0000)
002731D0 002a:
??_C@_0CK@MAHDJCLA@invalid?5widget?5controller?5index?5@ (0000)
002731FC 0037:
??_C@_0DH@EDIJCICF@waiting?5for?5?$CFd?5ticks?5before?5disp@ (0000)
00301DA0 413c:
_data_00301da0 (0000)
_widget_memory_pool (4034)
_dashboard_abort_error (403c)
00454240 08b0:
_bss_00454240 (0000)
*/
/* ---------- headers */
struct widget_instance;
#include "cseries.h"
#include "errors.h"
#include "bitmaps/bitmap_group.h"
#include "bitmaps/bitmaps.h"
#include "bink/bink_playback.h"
#include "bungie_net/common/thread.h"
#include "cache/cache_files.h"
#include "cache/texture_cache.h"
#include "cseries/cseries_windows.h"
#include "cutscene/cinematics.h"
#include "event_manager.h"
#include "game/game_engine.h"
#include "game/game_globals.h"
#include "game/players.h"
#include "hs/hs.h"
#include "input/input.h"
#include "input/input_abstraction.h"
#include "interface/attract_mode.h"
#include "interface/hud.h"
#include "interface/hud_definitions.h"
#include "interface/hud_draw.h"
#include "bitmaps/bitmap_color_conversion.h"
#include "interface/interface.h"
#include "interface/player_ui.h"
#include "interface/progress_bar.h"
#include "interface/ui_widget_game_data_input_functions.h"
#include "interface/ui_widget_event_handler_functions.h"
#include "interface/ui_widget_text_search_and_replace_functions.h"
#include "interface/virtual_keyboard.h"
#include "main/main.h"
#include "memory/stack_memory_pool.h"
#include "networking/network_client_manager.h"
#include "networking/network_connection.h"
#include "networking/network_game_globals.h"
#include "networking/network_server_manager.h"
#include "network_coop.h" /* port: port/linux/game/network_coop.c */
#include "rasterizer/rasterizer.h"
#include "saved games/player_profile.h"
#include "saved games/playlist_profile.h"
#include "saved games/saved_game_files.h"
#include "scenario/scenario.h"
#include "shell/shell_xbox.h"
#include "sound/game_sound.h"
#include "sound/sound_definitions.h"
#include "sound/sound_manager.h"
#include "tag_files/tag_files.h"
#include "tag_files/tag_groups.h"
#include "text/draw_string.h"
#include "text/text_group.h"
#include "text/unicode.h"
#include "ui_widget.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#endif
/* ---------- constants */
enum
{
/* port: 16 KB on the Xbox; the PC version's screens (port/assets/menus)
have many more widgets */
WIDGET_MEMORY_POOL_SIZE = 0x40000,
MAXIMUM_WIDGET_MEMORY_POOL_BLOCKS = 4096
};
enum
{
_ui_widget_type_container,
_ui_widget_type_text_box,
_ui_widget_type_spinner_list,
_ui_widget_type_column_list,
_ui_widget_type_game_model,
_ui_widget_type_movie,
_ui_widget_type_custom,
NUMBER_OF_UI_WIDGET_TYPES
};
enum
{
_ui_audio_feedback_none,
_ui_audio_feedback_cursor,
_ui_audio_feedback_forward,
_ui_audio_feedback_back,
_ui_audio_feedback_flag_failure,
NUMBER_OF_UI_AUDIO_FEEDBACK_SOUNDS
};
enum
{
_icon_a_button,
_icon_b_button,
_icon_x_button,
_icon_y_button,
_icon_black_button,
_icon_white_button,
_icon_left_trigger,
_icon_right_trigger,
_icon_dpad_up,
_icon_dpad_down,
_icon_dpad_left,
_icon_dpad_right,
_icon_start_button,
_icon_back_button,
_icon_left_thumb,
_icon_right_thumb,
_icon_left_stick,
_icon_right_stick,
_icon_action,
_icon_throw_grenade,
_icon_primary_trigger,
_icon_integrated_light,
_icon_jump,
_icon_use_equipment,
_icon_rotate_weapons,
_icon_rotate_grenades,
_icon_crouch,
_icon_zoom,
_icon_accept,
_icon_back,
_icon_move,
_icon_look,
_icon_custom_1,
_icon_custom_2,
_icon_custom_3,
_icon_custom_4,
_icon_custom_5,
_icon_custom_6,
_icon_custom_7,
_icon_custom_8,
NUMBER_OF_ICON_TYPES
};
enum
{
/* only the icon types below _icon_action name a button bitmap of their own;
the rest are resolved through the local player's control preferences */
NUM_ICONS = _icon_action
};
enum
{
_hud_icon_use_text_bit,
_hud_icon_use_color_bit,
_hud_icon_absolute_width_bit,
NUMBER_OF_HUD_ICON_FLAGS
};
enum
{
UI_WIDGET_DEFINITION_TAG = 'DeLa',
FONT_GROUP_TAG = 'font'
};
enum
{
_widget_controller0,
_widget_controller1,
_widget_controller2,
_widget_controller3,
_widget_controller_any,
NUMBER_OF_WIDGET_CONTROLLERS
};
enum
{
/* only the bits this file tests are named; when
_widget_always_use_tag_controller_index_bit is set a definition asking for
any player gets no controller at all, when it is clear the same request
inherits the invoking widget's controller */
_widget_pass_unhandled_events_to_children_bit = 0,
_widget_pause_game_time_bit = 1,
_widget_flash_background_bitmap_bit = 2,
_widget_dpad_updown_tabs_thru_children_bit = 3,
_widget_dpad_leftright_tabs_thru_children_bit = 4,
_widget_dpad_updown_tabs_thru_list_items_bit = 5,
_widget_dpad_leftright_tabs_thru_list_items_bit = 6,
_widget_dont_focus_a_specific_child_bit = 7,
_widget_pass_unhandled_events_to_all_children_bit = 8,
_widget_render_regardless_of_controller_index_bit = 9,
_widget_pass_handled_events_to_all_children_bit = 10,
_widget_return_to_main_menu_if_no_history_bit = 11,
_widget_always_use_tag_controller_index_bit = 12,
_widget_always_render_with_nifty_fx_bit = 13,
_widget_dont_push_history_data_bit = 14
};
enum
{
_child_widget_use_custom_controller_index_bit = 0,
NUMBER_OF_CHILD_WIDGET_FLAGS
};
enum
{
_conditional_widget_load_if_event_handler_function_fails_bit = 0,
NUMBER_OF_CONDITIONAL_WIDGET_FLAGS
};
enum
{
_event_handler_close_current_widget_bit,
_event_handler_close_other_widget_bit,
_event_handler_close_all_widgets_bit,
_event_handler_open_widget_bit,
_event_handler_reload_self_bit,
_event_handler_reload_widget_bit,
_event_handler_give_focus_to_widget_bit,
_event_handler_run_function_bit,
_event_handler_replace_with_other_widget_bit,
_event_handler_go_back_to_previous_widget_bit,
_event_handler_run_scenario_script_bit,
_event_handler_look_for_conditional_widget_on_failure_bit,
NUMBER_OF_EVENT_HANDLER_FLAGS
};
enum
{
_list_items_generated_in_code,
_list_items_generated_from_string_list_tag,
_list_items_only_one_tooltip_entry,
_list_single_preview_box_no_scroll,
NUMBER_OF_LIST_FLAGS
};
enum
{
/* only the bit this file tests is named */
_text_box_flashing_text_bit = 2
};
enum
{
_text_justification_left,
_text_justification_right,
_text_justification_center,
NUMBER_OF_TEXT_JUSTIFICATIONS
};
enum
{
/* EVENT_MANAGER.C owns this enumeration and publishes none of it */
_event_type_null,
_event_type_left_stick,
_event_type_right_stick,
_event_type_button,
NUMBER_OF_EVENT_TYPES
};
enum
{
/* the button event types are the gamepad button indices; the enumeration
runs 0..33 and only the types this file names are listed */
_widget_event_b_button = _gamepad_analog_button_b,
_widget_event_dpad_up = _gamepad_binary_button_dpad_up,
_widget_event_dpad_down = _gamepad_binary_button_dpad_down,
_widget_event_dpad_left = _gamepad_binary_button_dpad_left,
_widget_event_dpad_right = _gamepad_binary_button_dpad_right,
_widget_event_back_button = _gamepad_binary_button_back,
_widget_event_left_stick_up = NUMBER_OF_GAMEPAD_BUTTONS,
_widget_event_left_stick_down,
_widget_event_left_stick_left,
_widget_event_left_stick_right,
_widget_event_right_stick_up,
_widget_event_right_stick_down,
_widget_event_right_stick_left,
_widget_event_right_stick_right,
_widget_event_created,
_widget_event_deleted
};
enum
{
/* a held dpad direction repeats no faster than this */
DPAD_EVENT_REPEAT_MILLISECONDS = 250,
NUMBER_OF_DPAD_DIRECTIONS =
_widget_event_dpad_right - _widget_event_dpad_up + 1
};
enum
{
/* deferred errors wait this long into a level before they are shown */
DEFERRED_ERROR_DELAY_TICKS = 30
};
enum
{
WIDGET_DELETED_PLAYER_CONTROL_INHIBIT_FLAGS = 0x0FFF
};
enum
{
/* named for the errors each result raises; SAVED_GAME_FILES.H publishes no
enumeration of its own */
_file_system_check_result_none,
_file_system_check_result_not_enough_free_space,
_file_system_check_result_too_many_saved_games,
NUMBER_OF_FILE_SYSTEM_CHECK_RESULTS
};
/* ---------- macros */
#define SIGN(n) ((n) >= 0 ? 1 : -1)
/* The public definition header intentionally exposes only the shared prefix;
this TU owns the complete January layout used below. */
#define ui_widget_definition_get(tag_index) \
((struct ui_widget_definition *)tag_get(UI_WIDGET_DEFINITION_TAG, (tag_index)))
/* ---------- structures */
struct stack_memory_pool_block;
struct stack_memory_pool
{
char const *name;
byte *base_address;
long size;
long maximum_block_count;
long next_block_index;
long bytes_used;
long maximum_bytes_used;
unsigned long block_count;
unsigned long maximum_block_count_used;
long largest_block_size;
boolean disable_compaction;
byte unused29[3];
struct stack_memory_pool_block *first_block;
struct stack_memory_pool_block *last_block;
struct stack_memory_pool_block *blocks[1];
};
struct stack_memory_pool_medium
{
struct stack_memory_pool pool;
struct stack_memory_pool_block *blocks[MAXIMUM_WIDGET_MEMORY_POOL_BLOCKS - 1];
};
typedef char verify_icon_hud_element_definition_size[
sizeof(struct icon_hud_element_definition) == 0x10 ? 1 : -1];
typedef char verify_hud_globals_button_icons_offset[
offsetof(struct hud_globals_definition, messaging.button_icons) == 0xC4 ? 1 : -1];
typedef char verify_interface_tag_references_definition_size[
sizeof(struct game_globals_interface_tag_references) == 0x130 ? 1 : -1];
/* narrow views of the 'DeLa' widget definition tag and of the three block
elements this file walks; only the members this file reaches are named and
every other span is left explicitly unknown */
struct ui_widget_event_handler_reference
{
long flags;
short event_type;
short function;
struct tag_reference widget_tag;
struct tag_reference sound_effect;
char script[32];
};
struct ui_widget_child_reference
{
struct tag_reference widget_tag;
char name[32];
long flags;
short custom_controller_index;
short vertical_offset;
short horizontal_offset;
byte unknown03A[0x50 - 0x3A];
};
struct ui_widget_conditional_reference
{
struct tag_reference widget_tag;
char name[32];
long flags;
short custom_controller_index;
byte unknown036[0x50 - 0x36];
};
struct ui_widget_game_data_input_reference
{
short function;
byte unknown002[0x24 - 0x02];
};
struct ui_widget_search_and_replace_reference
{
char search_string[32];
short replace_function;
};
struct ui_widget_definition
{
short type;
short controller_index;
char name[32];
rectangle2d bounds;
long flags;
long milliseconds_to_auto_close;
long auto_close_fade_time;
struct tag_reference background_bitmap;
struct tag_block game_data_inputs;
struct tag_block event_handlers;
struct tag_block search_and_replace_functions;
byte unknown06C[0xEC - 0x6C];
struct tag_reference text_label_string_list;
struct tag_reference text_font;
real_argb_color text_color;
short justification;
word text_box_flags;
byte unknown120[0x12E - 0x120];
short string_list_index;
short horizontal_offset;
short vertical_offset;
byte unknown134[0x150 - 0x134];
long list_flags;
struct tag_reference list_header_bitmap;
struct tag_reference list_footer_bitmap;
rectangle2d list_header_bounds;
rectangle2d list_footer_bounds;
byte unknown184[0x1A4 - 0x184];
struct tag_reference extended_description_widget;
byte unknown1B4[0x2D4 - 0x1B4];
struct tag_block conditional_widgets;
byte unknown2E0[0x3E0 - 0x2E0];
struct tag_block child_widgets;
};
typedef char verify_ui_widget_game_data_input_reference_size[
sizeof(struct ui_widget_game_data_input_reference) == 0x24 ? 1 : -1];
typedef char verify_ui_widget_search_and_replace_reference_size[
sizeof(struct ui_widget_search_and_replace_reference) == 0x22 ? 1 : -1];
typedef char verify_ui_widget_child_reference_size[
sizeof(struct ui_widget_child_reference) == 0x50 ? 1 : -1];
typedef char verify_ui_widget_conditional_reference_size[
sizeof(struct ui_widget_conditional_reference) == 0x50 ? 1 : -1];
typedef char verify_ui_widget_event_handler_reference_size[
sizeof(struct ui_widget_event_handler_reference) == 0x48 ? 1 : -1];
typedef char verify_ui_widget_definition_bounds_offset[
offsetof(struct ui_widget_definition, bounds) == 0x24 ? 1 : -1];
typedef char verify_ui_widget_definition_flags_offset[
offsetof(struct ui_widget_definition, flags) == 0x2C ? 1 : -1];
typedef char verify_ui_widget_definition_game_data_inputs_offset[
offsetof(struct ui_widget_definition, game_data_inputs) == 0x48 ? 1 : -1];
typedef char verify_ui_widget_definition_search_and_replace_offset[
offsetof(struct ui_widget_definition, search_and_replace_functions) == 0x60 ? 1 : -1];
typedef char verify_ui_widget_definition_text_font_offset[
offsetof(struct ui_widget_definition, text_font) == 0xFC ? 1 : -1];
typedef char verify_ui_widget_definition_text_color_offset[
offsetof(struct ui_widget_definition, text_color) == 0x10C ? 1 : -1];
typedef char verify_ui_widget_definition_justification_offset[
offsetof(struct ui_widget_definition, justification) == 0x11C ? 1 : -1];
typedef char verify_ui_widget_definition_text_box_flags_offset[
offsetof(struct ui_widget_definition, text_box_flags) == 0x11E ? 1 : -1];
typedef char verify_ui_widget_definition_string_list_index_offset[
offsetof(struct ui_widget_definition, string_list_index) == 0x12E ? 1 : -1];
typedef char verify_ui_widget_definition_horizontal_offset_offset[
offsetof(struct ui_widget_definition, horizontal_offset) == 0x130 ? 1 : -1];
typedef char verify_ui_widget_definition_list_header_bitmap_offset[
offsetof(struct ui_widget_definition, list_header_bitmap) == 0x154 ? 1 : -1];
typedef char verify_ui_widget_definition_list_header_bounds_offset[
offsetof(struct ui_widget_definition, list_header_bounds) == 0x174 ? 1 : -1];
typedef char verify_ui_widget_definition_event_handlers_offset[
offsetof(struct ui_widget_definition, event_handlers) == 0x54 ? 1 : -1];
typedef char verify_ui_widget_definition_background_bitmap_offset[
offsetof(struct ui_widget_definition, background_bitmap) == 0x38 ? 1 : -1];
typedef char verify_ui_widget_definition_text_label_string_list_offset[
offsetof(struct ui_widget_definition, text_label_string_list) == 0xEC ? 1 : -1];
typedef char verify_ui_widget_definition_list_flags_offset[
offsetof(struct ui_widget_definition, list_flags) == 0x150 ? 1 : -1];
typedef char verify_ui_widget_definition_extended_description_offset[
offsetof(struct ui_widget_definition, extended_description_widget) == 0x1A4 ? 1 : -1];
typedef char verify_ui_widget_definition_conditional_widgets_offset[
offsetof(struct ui_widget_definition, conditional_widgets) == 0x2D4 ? 1 : -1];
typedef char verify_ui_widget_definition_child_widgets_offset[
offsetof(struct ui_widget_definition, child_widgets) == 0x3E0 ? 1 : -1];
typedef char verify_ui_widget_definition_size[
sizeof(struct ui_widget_definition) == 0x3EC ? 1 : -1];
struct ui_widget_deferred_error
{
short error_code;
short local_player_index;
boolean modal;
boolean pause_game_time;
};
struct ui_widget_deferred_cinematic_error
{
short error_code;
boolean modal;
boolean pause_game_time;
};
struct widget_stack_data
{
long previous_widget_tag;
long focused_child_parent_widget_tag;
short focused_child_index;
short local_player_index;
};
struct widget_stack_node
{
struct widget_stack_data data;
struct widget_stack_node *next;
};
struct ui_widget_runtime_globals_prefix
{
struct widget_instance *active_widgets[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
struct widget_stack_node *widget_stack[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
unsigned long current_system_milliseconds;
long pause_disabled_ticks;
short main_menu_deferred_error_code;
short pause_game_time_count;
real fade_to_black;
struct ui_widget_deferred_error deferred_errors[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
short deferred_dashboard_error_code;
boolean deferred_dashboard_optional;
byte reserved004B;
struct ui_widget_deferred_cinematic_error deferred_cinematic_errors[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
struct thread_reference *initialization_thread;
short filesystem_check_result;
boolean initialized;
boolean dont_load_children_recursive;
boolean debug_show_path;
boolean processing_inhibited;
boolean main_menu_music_active;
boolean sound_paused;
};
struct ui_widget_bss_prefix
{
wchar_t string_data[1024];
struct ui_widget_runtime_globals_prefix widget_globals;
boolean we_are_at_the_main_menu;
byte unknown869[0x870 - 0x869];
unsigned long dpad_event_times[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS][NUMBER_OF_DPAD_DIRECTIONS];
};
typedef char verify_ui_widget_fade_to_black_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
fade_to_black) == 0x2C ? 1 : -1];
typedef char verify_ui_widget_pause_disabled_ticks_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
pause_disabled_ticks) == 0x24 ? 1 : -1];
typedef char verify_ui_widget_main_menu_deferred_error_code_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
main_menu_deferred_error_code) == 0x28 ? 1 : -1];
typedef char verify_ui_widget_deferred_dashboard_error_code_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
deferred_dashboard_error_code) == 0x48 ? 1 : -1];
typedef char verify_ui_widget_deferred_dashboard_optional_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
deferred_dashboard_optional) == 0x4A ? 1 : -1];
typedef char verify_ui_widget_initialization_thread_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
initialization_thread) == 0x5C ? 1 : -1];
typedef char verify_ui_widget_initialized_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
initialized) == 0x62 ? 1 : -1];
typedef char verify_ui_widget_debug_show_path_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
debug_show_path) == 0x64 ? 1 : -1];
typedef char verify_ui_widget_processing_inhibited_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
processing_inhibited) == 0x65 ? 1 : -1];
typedef char verify_ui_widget_main_menu_music_active_offset[
offsetof(
struct ui_widget_runtime_globals_prefix,
main_menu_music_active) == 0x66 ? 1 : -1];
typedef char verify_ui_widget_runtime_globals_prefix_size[
sizeof(struct ui_widget_runtime_globals_prefix) == 0x68 ? 1 : -1];
typedef char verify_ui_widget_globals_offset[
offsetof(
struct ui_widget_bss_prefix,
widget_globals) == 0x800 ? 1 : -1];
typedef char verify_ui_widget_main_menu_active_offset[
offsetof(
struct ui_widget_bss_prefix,
we_are_at_the_main_menu) == 0x868 ? 1 : -1];
typedef char verify_ui_widget_dpad_event_times_offset[
offsetof(
struct ui_widget_bss_prefix,
dpad_event_times) == 0x870 ? 1 : -1];
struct widget_animation_data
{
short current_frame_index;
short first_frame_index;
short last_frame_index;
short number_of_sprite_frames;
};
struct widget_instance
{
long definition_tag_index;
char const *name;
short local_player_index;
short horizontal_offset;
short vertical_offset;
short type;
boolean visible;
boolean render_regardless_of_controller_index;
boolean disabled;
boolean pause_game_time;
boolean delete_recursion_lock;
boolean widget_is_error_dialog;
boolean close_if_local_player_controller_present;
byte pad17;
long creation_time;
unsigned long milliseconds_to_auto_close;
unsigned long auto_close_fade_time;
real alpha_modifier;
struct widget_instance *previous;
struct widget_instance *next;
struct widget_instance *parent;
struct widget_instance *child;
struct widget_instance *focused_child;
union
{
struct
{
wchar_t *text;
short string_list_index;
} text_box;
struct
{
short selected_index;
/* counted back toward zero one step per rendered frame; the two
tab functions start it at +15 and -15 and the column list renderer
clears it */
short last_list_tab_direction;
void *list_items;
word number_of_items;
struct widget_instance *extended_description;
wchar_t *item_text;
} list;
} parameters;
struct widget_animation_data animation;
};
typedef char verify_widget_instance_size[
sizeof(struct widget_instance) == 0x58 ? 1 : -1];
typedef char verify_widget_instance_animation_offset[
offsetof(struct widget_instance, animation) == 0x50 ? 1 : -1];
typedef char verify_widget_instance_creation_time_offset[
offsetof(struct widget_instance, creation_time) == 0x18 ? 1 : -1];
typedef char verify_widget_instance_alpha_modifier_offset[
offsetof(struct widget_instance, alpha_modifier) == 0x24 ? 1 : -1];
typedef char verify_widget_instance_next_offset[
offsetof(struct widget_instance, next) == 0x2C ? 1 : -1];
typedef char verify_widget_instance_focused_child_offset[
offsetof(struct widget_instance, focused_child) == 0x38 ? 1 : -1];
typedef char verify_widget_instance_text_box_string_list_index_offset[
offsetof(struct widget_instance, parameters.text_box.string_list_index) == 0x40 ? 1 : -1];
/* ---------- prototypes */
static boolean transition_to_game_in_progress(
void);
static __inline real compute_offset_coordinate(
long time,
real delta_per_second);
static short get_icon_type(
wchar_t const *string);
static void render_state_text(
rectangle2d *bounds,
rectangle2d *cursor_bounds,
wchar_t const *text);
static void render_state_bitmap(
rectangle2d *bounds,
rectangle2d *cursor_bounds,
pixel32 color,
struct icon_hud_element_definition *icon);
static boolean should_flip_sticks_for_local_player(
short local_player_index);
static unsigned long __stdcall filesystem_initialization_thread_proc(
void *input);
static void perform_filesystem_initialization(
void);
static void ui_widget_delete_children_recursive(
struct widget_instance *widget);
static struct widget_instance *ui_widget_launch_widget(
struct widget_instance *widget,
long new_widget_tag_index);
static __inline boolean widget_instance_can_handle_events(
struct widget_instance *widget);
static struct widget_instance *widget_instance_find_by_tag_index_recursive(
struct widget_instance *widget,
long tag_index);
static void widget_instance_give_focus_directly(
struct widget_instance *widget,
struct widget_instance *new_focus);
static void widget_instance_give_focus_by_tag(
struct widget_instance *widget,
long tag_index,
short local_player_index);
static __inline struct widget_instance *widget_instance_get_tail_child_widget(
struct widget_instance *widget);
static void ui_widget_add_child(
struct widget_instance *parent,
struct widget_instance *child);
static void push_widget(
struct widget_stack_node **top,
struct widget_stack_data *data);
static boolean widget_instance_can_receive_events(
struct widget_instance *widget);
static void widget_instance_set_focused_child_by_index(
long tag_index,
struct widget_instance *widget,
short child_index);
static void widget_instance_go_back_to_previous(
struct widget_instance *widget);
static __inline struct widget_instance *widget_instance_find_by_tag_index(
long tag_index);
static void widget_instance_reload_recursive(
struct widget_instance *widget);
static void ui_widget_reload_by_tag(
long tag_index);
static void event_handler_dispatch(
struct widget_instance *widget,
struct ui_widget_definition *definition,
struct event_record *event,
struct ui_widget_event_handler_reference *handler,
boolean *calling_widget_deleted);
static boolean ui_widget_load_children_recursive(
struct widget_instance *widget,
struct ui_widget_definition *definition);
/* port: whether the tag is one of the menus' (port/linux/game/menu_tags.c) */
boolean pc_menu_tag(
long tag_index);
static void widget_instance_initialize(
struct widget_instance *widget,
struct widget_instance *parent,
struct ui_widget_definition *definition,
long tag_index,
short local_player_index,
short widget_stack);
static __inline real widget_instance_get_cumulative_alpha_modifier(
struct widget_instance *widget);
static boolean widget_instance_text_box_is_focused(
struct widget_instance *widget);
static boolean string_has_icons_to_draw(
wchar_t *string);
static long search_and_replace(
wchar_t *search,
wchar_t *replace,
wchar_t **string);
static void widget_instance_render_text_box(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus);
static void widget_instance_render_spinner_list(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus);
static void widget_instance_render_column_list(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus);
static void widget_instance_render_recursive(
struct widget_instance *widget,
rectangle2d *clip_rect,
point2d offset,
boolean focus,
boolean use_nifty_plasma_fx);
static __inline void widget_instance_update_animation_parameters(
struct widget_instance *widget);
static __inline void spinner_list_update(
struct widget_instance *widget);
static void column_list_update(
struct widget_instance *widget,
struct ui_widget_definition *definition);
static void widget_instance_tab_to_next_valid_widget(
struct widget_instance *widget);
static void widget_instance_tab_to_previous_valid_widget(
struct widget_instance *widget);
static void widget_instance_process_one_event_recursive(
struct widget_instance *widget,
struct ui_widget_definition *definition,
struct event_record *event,
boolean *return_widget_deleted);
static boolean ui_check_for_pause_game(
void);
static long spinner_string_list_extra_count(
long string_list_index);
static wchar_t *spinner_string_list_get_string(
long string_list_index,
short string_index);
/* ---------- globals */
/* port: text boxes' string list indices from here are the descriptions of
spinners' extra items (kills_to_win_extra_descriptions) */
#define SPINNER_EXTRA_DESCRIPTION_BASE 0x5000
/* ... and the pixels a spinner with extra items is wider (for three digits) */
#define SPINNER_EXTRA_WIDTH 12
/* port: the strings a spinner's string list has past the tag's own, as more
items: the higher kills to win of the Slayer game type editor
(ui_widget_event_handler_functions.c saves and loads them) */
static wchar_t const *const kills_to_win_extra_strings[] =
{
L"75", L"100", L"150", L"200", L"250", L"500",
};
/* ... their descriptions, as a text box's string list index of
SPINNER_EXTRA_DESCRIPTION_BASE and up (ui_widget_spinner_extra_description) */
static wchar_t const *const kills_to_win_extra_descriptions[] =
{
L"Seventy-five kills to win. Settle in for a long\r\nfight.",
L"A hundred kills to win. Made for big games.",
L"A hundred and fifty kills to win. Only a crowded\r\nserver gets there.",
L"Two hundred kills to win. Bring friends. Lots of\r\nthem.",
L"Two hundred and fifty kills to win.",
L"Five hundred kills to win. You'll be here a while.",
};
/* port: an error message of the port's own text (display_error_text_deferred):
the text waiting for its dialog, then the dialog's text box showing it */
static wchar_t const *ui_widget_port_error_pending_text = NULL;
static wchar_t const *ui_widget_port_error_text = NULL;
static struct widget_instance *ui_widget_port_error_text_box = NULL;
static struct ui_widget_bss_prefix ui_widget_globals_storage;
#define string_data ui_widget_globals_storage.string_data
#define widget_globals ui_widget_globals_storage.widget_globals
#define we_are_at_the_main_menu ui_widget_globals_storage.we_are_at_the_main_menu
#define dpad_event_times ui_widget_globals_storage.dpad_event_times
real_argb_color ui_plasma_effect_color;
short local_player_index_for_draw_string_and_hack_in_icons;
/* January defines this and never references it, as we do not */
real const _one_over_255 = 1.0f / 255.0f;
real const SECONDS_PER_MILLISECOND = 0.001f;
static struct stack_memory_pool_medium __medium_widget_memory_pool =
{
{
"widget_memory_pool",
NULL,
0,
MAXIMUM_WIDGET_MEMORY_POOL_BLOCKS
}
};
struct stack_memory_pool *widget_memory_pool = &__medium_widget_memory_pool.pool;
static boolean main_screen_shell_first_load = TRUE;
short dashboard_abort_error = NONE;
static char const *scenario_paths[10] =
{
"levels\\a10\\a10",
"levels\\a30\\a30",
"levels\\a50\\a50",
"levels\\b30\\b30",
"levels\\b40\\b40",
"levels\\c10\\c10",
"levels\\c20\\c20",
"levels\\c40\\c40",
"levels\\d20\\d20",
"levels\\d40\\d40"
};
static boolean icon_is_special[NUM_ICONS] =
{
FALSE, /* a-button */
FALSE, /* b-button */
FALSE, /* x-button */
FALSE, /* y-button */
FALSE, /* black-button */
FALSE, /* white-button */
TRUE, /* left-trigger */
TRUE, /* right-trigger */
FALSE, /* dpad-up */
FALSE, /* dpad-down */
FALSE, /* dpad-left */
FALSE, /* dpad-right */
FALSE, /* start-button */
FALSE, /* back-button */
TRUE, /* left-thumb */
TRUE, /* right-thumb */
TRUE, /* left-stick */
TRUE /* right-stick */
};
static wchar_t const *icon_names[NUMBER_OF_ICON_TYPES] =
{
L"a-button",
L"b-button",
L"x-button",
L"y-button",
L"black-button",
L"white-button",
L"left-trigger",
L"right-trigger",
L"dpad-up",
L"dpad-down",
L"dpad-left",
L"dpad-right",
L"start-button",
L"back-button",
L"left-thumb",
L"right-thumb",
L"left-stick",
L"right-stick",
L"action",
L"throw-grenade",
L"primary-trigger",
L"integrated-light",
L"jump",
L"use-equipment",
L"rotate-weapons",
L"rotate-grenades",
L"crouch",
L"zoom",
L"accept",
L"back",
L"move",
L"look",
L"custom-1",
L"custom-2",
L"custom-3",
L"custom-4",
L"custom-5",
L"custom-6",
L"custom-7",
L"custom-8"
};
/* indexed by icon type - _icon_action. The first ten entries are game control
indices for game_input_preferences.game_control_to_xbox_buttons (that enumeration
is private to INPUT_ABSTRACTION.C); the last four are icon types used directly
when the icon does not depend on the local player's control preferences. */
static char button_mappings[_icon_custom_1 - _icon_action] =
{
2, /* action -> action */
6, /* throw-grenade -> grenade */
7, /* primary-trigger -> primary trigger */
5, /* integrated-light -> flashlight */
0, /* jump -> jump */
4, /* use-equipment -> melee */
3, /* rotate-weapons -> switch weapons */
1, /* rotate-grenades -> switch grenades */
11, /* crouch -> zoom */
10, /* zoom -> crouch */
_icon_a_button, /* accept */
_icon_b_button, /* back */
_icon_left_stick, /* move */
_icon_right_stick /* look */
};
static real global_ui_white_red = 0.8f;
static real global_ui_white_green = 0.8f;
static real global_ui_white_blue = 0.8f;
/* ---------- public code */
void set_ui_plasma_effect_color(
real alpha,
real red,
real green,
real blue)
{
ui_plasma_effect_color.alpha = alpha;
ui_plasma_effect_color.red = red;
ui_plasma_effect_color.green = green;
ui_plasma_effect_color.blue = blue;
return;
}
boolean event_controller_index_compatible_with_widget(
struct event_record const *event,
struct widget_instance const *widget)
{
short widget_controller_index;
widget_controller_index = widget->local_player_index;
return widget_controller_index == NONE ||
widget_controller_index == event->controller_index;
}
void ui_widgets_safe_to_load(
boolean safe)
{
return;
}
void ui_widgets_inhibit_processing(
boolean inhibit)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1174,
widget_globals.initialized);
widget_globals.processing_inhibited = inhibit;
return;
}
static __inline real compute_offset_coordinate(
long time,
real delta_per_second)
{
real scaled_delta = delta_per_second * 0.001f;
return (real)fmod(
scaled_delta * time,
1.0);
}
void draw_bitmap_in_rect(
struct bitmap_data *bitmap,
rectangle2d *rect,
rectangle2d *bitmap_rect,
rectangle2d *clip_rect,
pixel32 argb,
struct rasterizer_dynamic_screen_geometry_parameters *multitexture_params,
boolean no_plasma)
{
if (bitmap && rect)
{
real_argb_color plasma_fade = ui_plasma_effect_color;
real_rgb_color map_tint = { 0.9f, 0.9f, 0.9f };
real map_fade = 0.9f;
rectangle2d temp;
real_point2d points[NUMBER_OF_POINTS_PER_RECTANGLE];
struct dynamic_screen_vertex vertices[NUMBER_OF_POINTS_PER_RECTANGLE];
struct rasterizer_dynamic_screen_geometry_parameters parameters;
real bitmap_width;
real bitmap_height;
real texture_width;
real texture_height;
real_point2d map0_offset;
real_point2d map1_offset;
short rectangle_x0;
short rectangle_y0;
short rectangle_width;
short rectangle_height;
short source_width;
short source_height;
short vertex_index;
if (!bitmap_rect)
{
temp.x0 = 0;
temp.y0 = 0;
temp.x1 = bitmap->width;
temp.y1 = bitmap->height;
bitmap_rect = &temp;
}
rectangle_width = rect->x1 - rect->x0;
rectangle_height = rect->y1 - rect->y0;
rectangle_x0 = rect->x0;
rectangle_y0 = rect->y0;
source_width = bitmap_rect->x1 - bitmap_rect->x0;
source_height = bitmap_rect->y1 - bitmap_rect->y0;
points[0].x = (real)rectangle_x0;
points[0].y = (real)rectangle_y0;
points[1].x = (real)(rectangle_x0 + rectangle_width);
points[1].y = (real)rectangle_y0;
points[2].x = (real)(rectangle_x0 + rectangle_width);
points[2].y = (real)(rectangle_y0 + rectangle_height);
points[3].x = (real)rectangle_x0;
points[3].y = (real)(rectangle_y0 + rectangle_height);
if (clip_rect)
{
if (clip_rect->x0 > rect->x0)
{
points[0].x = points[3].x = (real)clip_rect->x0;
}
if (clip_rect->x1 < rect->x1)
{
points[1].x = points[2].x = (real)clip_rect->x1;
}
if (clip_rect->y0 > rect->y0)
{
points[0].y = points[1].y = (real)clip_rect->y0;
}
if (clip_rect->y1 < rect->y1)
{
points[2].y = points[3].y = (real)clip_rect->y1;
}
}
bitmap_width = MAX(1.0f, (real)bitmap->width);
texture_width = MIN(
(real)source_width / bitmap_width,
1.0f);
bitmap_height = MAX(1.0f, (real)bitmap->height);
texture_height = MIN(
(real)source_height / bitmap_height,
1.0f);
for (vertex_index = 0;
vertex_index < NUMBER_OF_POINTS_PER_RECTANGLE;
vertex_index++)
{
vertices[vertex_index].color = argb;
vertices[vertex_index].texture_coordinates.x =
(vertex_index % 3) ? texture_width : 0.0f;
vertices[vertex_index].texture_coordinates.y =
(vertex_index > 1) ? texture_height : 0.0f;
vertices[vertex_index].position = points[vertex_index];
}
csmemset(&parameters, 0, sizeof(parameters));
if (no_plasma)
{
parameters.map_texture_scale[0].j = 1.0f;
parameters.map_texture_scale[0].i = 1.0f;
parameters.map_scale[0].j = 1.0f;
parameters.map_scale[0].i = 1.0f;
parameters.map[0] = bitmap;
}
else
{
struct bitmap_data *plasma_bitmap = TAG_BLOCK_GET_ELEMENT(
&bitmap_group_get(
interface_get_tag_index(_interface_bitmap_iface_map3))->bitmaps,
0,
struct bitmap_data);
long time = system_milliseconds();
map0_offset.x =
compute_offset_coordinate(time, 0.03215434f) * 311.0f;
map0_offset.y =
compute_offset_coordinate(time, 0.026795285f) * 311.0f;
map1_offset.x =
-compute_offset_coordinate(time, 0.035536603f);
map1_offset.x *= 201.0f;
map1_offset.y =
-compute_offset_coordinate(time, 0.031094525f);
map1_offset.y *= 201.0f;
parameters.map[0] = plasma_bitmap;
parameters.map0_to_1_blend_function = 5;
parameters.map_scale[0].i = 1.0f;
parameters.map_scale[0].j = 1.0f;
parameters.map_wrapped[0] = TRUE;
parameters.map_anchor_screen[0] = TRUE;
parameters.map_texture_scale[0].i = 1.0f / 311.0f;
parameters.map_texture_scale[0].j = 1.0f / 311.0f;
parameters.map_tint[0] = &map_tint;
parameters.map_fade[0] = &map_fade;
parameters.map_offset[0] = &map0_offset;
parameters.map[1] = plasma_bitmap;
parameters.map1_to_2_blend_function = 0;
parameters.map_scale[1].i = 1.0f;
parameters.map_scale[1].j = 1.0f;
parameters.map_wrapped[1] = TRUE;
parameters.map_anchor_screen[1] = TRUE;
parameters.map_texture_scale[1].i = 1.0f / 201.0f;
parameters.map_texture_scale[1].j = 1.0f / 201.0f;
parameters.map_tint[1] = &map_tint;
parameters.map_fade[1] = &map_fade;
parameters.map_offset[1] = &map1_offset;
parameters.plasma_fade = plasma_fade;
parameters.map_fade[2] = NULL;
parameters.map_texture_scale[2].j = 1.0f;
parameters.map_texture_scale[2].i = 1.0f;
parameters.map_scale[2].j = 1.0f;
parameters.map_scale[2].i = 1.0f;
parameters.map[2] = bitmap;
parameters.doing_plasma_effect = TRUE;
}
parameters.meter_parameters = NULL;
parameters.point_sampled = FALSE;
parameters.framebuffer_blend_function = 0;
rasterizer_psuedo_dynamic_screen_quad_draw(&parameters, vertices);
}
return;
}
void ui_widgets_set_fade_value(
real value)
{
widget_globals.fade_to_black = value;
return;
}
void ui_widget_debug_show_path(
boolean show)
{
widget_globals.debug_show_path = show;
return;
}
int widget_instance_count_children(
struct widget_instance *widget)
{
int count;
struct widget_instance *child;
count = 0;
if (widget)
{
for (child = widget->child; child; child = child->next)
count++;
}
return count;
}
struct widget_instance *widget_instance_get_nth_child(
struct widget_instance *widget,
int n)
{
int i;
struct widget_instance *result;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1050,
widget);
result = widget->child;
for (i = 0; i < n && result; i++)
result = result->next;
return result;
}
void widget_instance_set_visibility_recursive(
struct widget_instance *widget,
boolean visible)
{
struct widget_instance *child;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1859,
widget);
widget->visible = visible;
for (child = widget->child; child; child = child->next)
widget_instance_set_visibility_recursive(child, visible);
return;
}
void *ui_widget_realloc(
void *pointer,
word size,
char const *file,
unsigned long line)
{
return pool_resize_pointer(
widget_memory_pool,
pointer,
size,
file,
line);
}
void widget_free(
void *ptr)
{
dispose_pointer(widget_memory_pool, ptr);
return;
}
void main_menu_active(
boolean active)
{
we_are_at_the_main_menu = active;
return;
}
boolean main_menu_is_active(
void)
{
return we_are_at_the_main_menu;
}
void ui_widget_load_progress_widget(
void)
{
error(
_error_silent,
"the old loading progress screen has been replaced with glowy halo gravy");
return;
}
boolean filesystem_check_thread_is_active(
void)
{
return widget_globals.initialization_thread != NULL;
}
void display_error_when_main_menu_loaded(
short error_code)
{
if (widget_globals.main_menu_deferred_error_code == NONE)
{
widget_globals.main_menu_deferred_error_code = error_code;
return;
}
error(
_error_silent,
"there is already an error message queued for display at the main menu; ignoring this one");
return;
}
void display_error_abort_to_dashboard_deferred(
short error_code,
boolean optional)
{
if (widget_globals.deferred_dashboard_error_code == NONE)
{
widget_globals.deferred_dashboard_error_code = error_code;
widget_globals.deferred_dashboard_optional = optional;
return;
}
error(
_error_silent,
"there is already a deferred dashbaord error queued; ignoring this one!");
return;
}
boolean ui_main_menu_music_active(
void)
{
return widget_globals.main_menu_music_active;
}
void ui_widgets_disable_pause_game(
long duration_ticks)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2519,
duration_ticks>=0);
widget_globals.pause_disabled_ticks = duration_ticks;
return;
}
struct widget_instance *widget_instance_get_topmost_parent(
struct widget_instance *widget)
{
while (widget->parent)
widget = widget->parent;
return widget;
}
int widget_instance_get_child_index_from_parent(
struct widget_instance *widget)
{
int result = NONE;
if (widget->parent)
{
struct widget_instance *child = widget->parent->child;
int index = 0;
while (child)
{
if (child == widget)
{
result = index;
break;
}
child = child->next;
index++;
}
}
return result;
}
pixel32 modulate_pixel32_by_real_alpha(
pixel32 argb,
real alpha)
{
real modulated_alpha = (argb >> 24) * alpha;
return (fast_ftol(modulated_alpha) << 24) | (argb & 0x00FFFFFF);
}
void ui_set_next_level(
short level)
{
long level_index = level;
if (level_index != NONE)
{
if (level_index >= 0 && level_index <= 9)
{
main_set_map_name(main_get_solo_level_name(level));
main_disallow_persistent_storage();
}
else
{
error(_error_silent, "unknown level");
main_goto_main_menu();
}
}
else
{
main_roll_credits();
}
return;
}
boolean ui_widgets_active(
void)
{
boolean result = FALSE;
if (widget_globals.initialized)
{
long local_player_index;
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
if (widget_globals.active_widgets[local_player_index])
{
result = TRUE;
break;
}
}
}
return result;
}
boolean main_menu_screen_is_active(
void)
{
if (we_are_at_the_main_menu == TRUE &&
widget_globals.active_widgets[0] &&
strcmp(widget_globals.active_widgets[0]->name, "the_main_menu") == 0)
{
return TRUE;
}
return FALSE;
}
static void *pool_alloc(
unsigned long size)
{
return match_malloc("c:\\halo\\SOURCE\\interface\\ui_widget.c", 117, size);
}
static void pool_free(
void *pointer)
{
match_free("c:\\halo\\SOURCE\\interface\\ui_widget.c", 118, pointer);
return;
}
void ui_widgets_initialize(
void)
{
boolean success = TRUE;
byte *base_address;
long local_player_index;
base_address = pool_alloc(WIDGET_MEMORY_POOL_SIZE);
if (base_address)
{
widget_memory_pool->base_address = base_address;
widget_memory_pool->size = WIDGET_MEMORY_POOL_SIZE;
}
else
{
success = FALSE;
}
stack_memory_pool_reset(widget_memory_pool);
memset(&widget_globals, 0, sizeof(widget_globals));
widget_globals.main_menu_deferred_error_code = NONE;
widget_globals.deferred_dashboard_error_code = NONE;
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
widget_globals.deferred_errors[local_player_index].error_code = NONE;
widget_globals.deferred_cinematic_errors[local_player_index].error_code = NONE;
}
widget_globals.initialized = success;
widget_globals.fade_to_black = -1.0f;
return;
}
void ui_widgets_dispose(
void)
{
ui_widgets_close_all();
if (widget_memory_pool->base_address)
pool_free(widget_memory_pool->base_address);
widget_memory_pool->base_address = NULL;
widget_memory_pool->size = 0;
memset(&widget_globals, 0, sizeof(widget_globals));
return;
}
static void pop_widget(
struct widget_stack_node **top,
struct widget_stack_data *data)
{
struct widget_stack_node *node;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2556,
top && data);
node = *top;
*data = node->data;
*top = node->next;
dispose_pointer(widget_memory_pool, node);
return;
}
static void dispose_widget_stack(
struct widget_stack_node **top)
{
while (*top)
{
struct widget_stack_node *node = *top;
*top = node->next;
dispose_pointer(widget_memory_pool, node);
}
return;
}
void ui_widget_delete(
struct widget_instance *widget)
{
struct ui_widget_definition *definition;
long handler_index;
long widget_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
469,
widget && widget_globals.initialized);
if (widget->delete_recursion_lock)
return;
widget->delete_recursion_lock = TRUE;
if (widget->local_player_index != NONE && !widget->parent)
player_control_inhibit_buttons(
widget->local_player_index,
WIDGET_DELETED_PLAYER_CONTROL_INHIBIT_FLAGS,
TRUE);
definition = ui_widget_definition_get(widget->definition_tag_index);
for (handler_index = 0;
handler_index < definition->event_handlers.count;
handler_index++)
{
struct ui_widget_event_handler_reference *handler =
(struct ui_widget_event_handler_reference *)definition->event_handlers.address + handler_index;
if (handler->event_type == _widget_event_deleted &&
TEST_FLAG(handler->flags, _event_handler_run_function_bit))
{
boolean widget_deleted = FALSE;
boolean handled = ui_widget_event_handler_function_invoke(
widget,
NULL,
handler->function,
&widget_deleted);
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
520,
!widget_deleted,
"a 'widget deleted' event handler tried to delete the widget being deleted!");
if (handled == TRUE &&
TEST_FLAG(handler->flags, _event_handler_open_widget_bit))
{
long new_widget_tag_index = handler->widget_tag.index;
if (new_widget_tag_index != NONE &&
!ui_widget_launch_widget(widget, new_widget_tag_index))
{
error(_error_silent, "event handler failed to spawn widget");
}
}
}
}
if (widget->pause_game_time == TRUE)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
544,
widget_globals.pause_game_time_count > 0,
"widget pause counter out of whack");
if (--widget_globals.pause_game_time_count == 0)
{
if (game_time_get_paused())
{
game_time_set_paused(FALSE);
if (we_are_at_the_main_menu)
{
main_menu_ensure_player_queues_exist();
game_time_dispose_from_old_map();
game_time_initialize_for_new_map();
game_time_start();
}
}
if (widget_globals.sound_paused == TRUE)
{
sound_pause(FALSE);
widget_globals.sound_paused = FALSE;
}
}
}
ui_widget_delete_children_recursive(widget);
if (widget->previous)
widget->previous->next = widget->next;
if (widget->next)
widget->next->previous = widget->previous;
if (widget->parent && widget->parent->child == widget)
widget->parent->child = widget->next;
switch (widget->type)
{
case _ui_widget_type_text_box:
if (widget->parameters.text_box.text)
dispose_pointer(widget_memory_pool, widget->parameters.text_box.text);
if (widget == ui_widget_port_error_text_box)
{
ui_widget_port_error_text_box = NULL;
ui_widget_port_error_text = NULL;
}
break;
case _ui_widget_type_spinner_list:
case _ui_widget_type_column_list:
if (widget->parameters.list.list_items)
{
error(
_error_silent,
"###WARNING: possible memory leak disposing of a list widget (%s)",
definition->name ? definition->name : "<unknown>");
}
if (widget->parameters.list.item_text)
dispose_pointer(widget_memory_pool, widget->parameters.list.item_text);
if (widget->parameters.list.extended_description)
ui_widget_delete(widget->parameters.list.extended_description);
break;
}
dispose_pointer(widget_memory_pool, widget);
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
if (widget_globals.active_widgets[widget_index] == widget)
{
widget_globals.active_widgets[widget_index] = NULL;
break;
}
}
return;
}
static void ui_widget_delete_children_recursive(
struct widget_instance *widget)
{
struct widget_instance *child = widget->child;
while (child)
{
struct widget_instance *next = child->next;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2750,
child->previous == NULL);
if (next)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2754,
next->previous == child);
}
ui_widget_delete(child);
if (next)
next->previous = NULL;
child = next;
}
return;
}
static struct widget_instance *ui_widget_launch_widget(
struct widget_instance *widget,
long new_widget_tag_index)
{
struct ui_widget_definition *definition = ui_widget_definition_get(new_widget_tag_index);
struct widget_instance *root;
struct widget_instance *new_widget;
short local_player_index;
/* port: the menus of multiplayer with other machines (not split screen's
or co-op's) open only on maps of a build that plays multiplayer with the
others (cache_files.c, cache_files_multiplayer_region); otherwise the
player is told why, and the menu stays */
{
static char const multiplayer_menus[] = "ui\\shell\\main_menu\\multiplayer_type_select\\connected\\";
char const *name = tag_get_name(new_widget_tag_index);
char build[0x20];
if (name &&
!csstrncmp(name, multiplayer_menus, sizeof(multiplayer_menus) - 1) &&
!cache_files_multiplayer_region(build))
{
cache_files_show_multiplayer_unavailable(NULL, build);
return NULL;
}
}
if (TEST_FLAG(definition->flags, _widget_always_use_tag_controller_index_bit))
{
switch (definition->controller_index)
{
case _widget_controller0:
local_player_index = 0;
break;
case _widget_controller1:
local_player_index = 1;
break;
case _widget_controller2:
local_player_index = 2;
break;
case _widget_controller3:
local_player_index = 3;
break;
case _widget_controller_any:
local_player_index = NONE;
break;
default:
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
5380,
FALSE,
"invalid widget controller index specified");
break;
}
}
else
{
switch (definition->controller_index)
{
case _widget_controller0:
local_player_index = 0;
break;
case _widget_controller1:
local_player_index = 1;
break;
case _widget_controller2:
local_player_index = 2;
break;
case _widget_controller3:
local_player_index = 3;
break;
case _widget_controller_any:
local_player_index = widget->local_player_index;
break;
default:
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
5392,
FALSE,
"invalid widget controller index specified");
break;
}
}
root = widget_instance_get_topmost_parent(widget);
new_widget = ui_widget_load_by_name_or_tag(
NULL,
new_widget_tag_index,
NULL,
local_player_index,
root->definition_tag_index,
widget->parent ? widget->parent->definition_tag_index : NONE,
(short)widget_instance_get_child_index_from_parent(widget));
if (!new_widget)
error(_error_silent, "event handler failed to spawn widget");
return new_widget;
}
static __inline boolean widget_instance_can_handle_events(
struct widget_instance *widget)
{
struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index);
if (!widget->disabled &&
(definition->event_handlers.count > 0 ||
widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list))
{
return TRUE;
}
return FALSE;
}
/* port: a label in the PC version's lists (port/assets/menus): an item that
takes no events and has nothing in it that does, which its game passes over
(its rows of settings take none themselves, but their spinners do), or one
hidden (a list's rows past its items: port/linux/game/menu_functions.c) or
disabled (the server browser's column titles, which sort nothing) */
static boolean widget_instance_port_is_label(
struct widget_instance *widget)
{
return (!widget_instance_can_handle_events(widget) && !widget->child) || !widget->visible || widget->disabled;
}
static struct widget_instance *widget_instance_find_by_tag_index_recursive(
struct widget_instance *widget,
long tag_index)
{
struct widget_instance *result = NULL;
if (widget->definition_tag_index == tag_index)
{
result = widget;
}
else
{
struct widget_instance *child;
for (child = widget->child; child && !result; child = child->next)
{
if (child->definition_tag_index == tag_index)
result = child;
else
result = widget_instance_find_by_tag_index_recursive(child, tag_index);
}
}
return result;
}
static void widget_instance_give_focus_directly(
struct widget_instance *widget,
struct widget_instance *new_focus)
{
struct widget_instance *focused_child =
widget_instance_get_topmost_parent(widget)->focused_child;
if (new_focus->disabled == TRUE)
{
struct widget_instance *substitute;
for (substitute = new_focus->next; substitute; substitute = substitute->next)
{
if (widget_instance_can_handle_events(substitute))
break;
}
if (!substitute && new_focus->parent)
{
for (substitute = new_focus->parent->child; substitute; substitute = substitute->next)
{
if (widget_instance_can_handle_events(substitute))
break;
}
if (substitute == new_focus->parent->focused_child)
{
for (substitute = new_focus->previous; substitute; substitute = substitute->previous)
{
if (widget_instance_can_handle_events(substitute))
break;
}
}
}
if (substitute)
new_focus = substitute;
}
if (focused_child)
{
if (new_focus &&
focused_child->parent == new_focus->parent &&
focused_child->parent)
{
new_focus->parent->focused_child = new_focus;
return;
}
while (focused_child)
{
focused_child->parent->focused_child = NULL;
focused_child = focused_child->focused_child;
}
}
while (new_focus->parent)
{
new_focus->parent->focused_child = new_focus;
new_focus = new_focus->parent;
}
return;
}
static void widget_instance_give_focus_by_tag(
struct widget_instance *widget,
long tag_index,
short local_player_index)
{
struct widget_instance *root = widget_instance_get_topmost_parent(widget);
struct widget_instance *new_focus = widget_instance_find_by_tag_index_recursive(root, tag_index);
if (new_focus)
widget_instance_give_focus_directly(root, new_focus);
else
error(_error_silent, "failed to find event focus target widget");
return;
}
boolean widget_event_function_list_widget_goto_next_item(
struct widget_instance *widget,
struct event_record *event,
boolean *widget_deleted)
{
struct ui_widget_definition *definition;
struct widget_instance *child;
long item_index;
boolean result = TRUE;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1230,
widget);
definition = ui_widget_definition_get(widget->definition_tag_index);
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1233,
widget->type == _ui_widget_type_spinner_list || widget->type == _ui_widget_type_column_list,
"calling a list widget function on a non-list widget");
if (widget->parameters.list.list_items && widget->parameters.list.number_of_items > 0)
{
item_index = widget->parameters.list.selected_index + 1;
if (item_index >= widget->parameters.list.number_of_items)
item_index = 0;
if (widget->type == _ui_widget_type_column_list)
{
child = widget_instance_get_nth_child(widget, item_index);
if (child)
{
widget_instance_give_focus_by_tag(
widget,
child->definition_tag_index,
widget->local_player_index);
widget->parameters.list.selected_index = (short)item_index;
}
else
{
error(
_error_silent,
"failed to set focus to the #%d list item of a column list widget",
item_index);
result = FALSE;
}
}
else if (widget->type == _ui_widget_type_spinner_list)
{
if (definition->child_widgets.count > 1)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1268,
definition->child_widgets.count == 3,
"spinner lists must be either 1- or 3-wide... sorry");
if (widget->focused_child == widget->child ||
widget->focused_child == widget->child->next)
{
if (widget->focused_child->next)
widget_instance_give_focus_directly(widget, widget->focused_child->next);
}
}
widget->parameters.list.selected_index = (short)item_index;
}
}
else
{
if (widget->type == _ui_widget_type_spinner_list)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1288,
definition->child_widgets.count <= 1,
"spinner lists with more that 1 visible item need to have code-generated lists associated with them... sorry.");
}
if (widget->type == _ui_widget_type_spinner_list &&
TEST_FLAG(definition->list_flags, _list_items_generated_from_string_list_tag) &&
definition->child_widgets.count == 0)
{
widget->parameters.list.selected_index++;
if (widget->parameters.list.selected_index == widget->parameters.list.number_of_items)
widget->parameters.list.selected_index = 0;
}
else
{
child = NULL;
if (widget->focused_child)
{
item_index = widget->parameters.list.selected_index + 1;
child = widget->focused_child->next;
if (item_index == widget->parameters.list.number_of_items)
child = NULL;
}
if (!child)
{
child = widget->child;
item_index = 0;
}
/* port: the PC version's lists (port/assets/menus) pass over the
children that take no events (their labels and lines), as its
game does */
if (child && widget->type == _ui_widget_type_column_list &&
pc_menu_tag(widget->definition_tag_index))
{
long tries = 0;
while (widget_instance_port_is_label(child) && tries++ < 256)
{
child = child->next;
item_index++;
if (!child)
{
child = widget->child;
item_index = 0;
}
}
}
if (child)
{
widget_instance_give_focus_by_tag(
widget,
child->definition_tag_index,
widget->local_player_index);
widget->parameters.list.selected_index = (short)item_index;
}
else
{
error(
_error_silent,
"failed to set focus to the next list item of a column widget");
result = FALSE;
}
}
}
if (result)
widget->parameters.list.last_list_tab_direction = 15;
return result;
}
boolean widget_event_function_list_widget_goto_previous_item(
struct widget_instance *widget,
struct event_record *event,
boolean *widget_deleted)
{
struct ui_widget_definition *definition;
struct widget_instance *child;
long item_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1359,
widget);
definition = ui_widget_definition_get(widget->definition_tag_index);
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1362,
widget->type == _ui_widget_type_spinner_list || widget->type == _ui_widget_type_column_list,
"calling a list widget function on a non-list widget");
if (widget->parameters.list.list_items && widget->parameters.list.number_of_items > 0)
{
item_index = widget->parameters.list.selected_index - 1;
if (item_index < 0)
item_index = widget->parameters.list.number_of_items - 1;
if (widget->type == _ui_widget_type_column_list)
{
child = widget_instance_get_nth_child(widget, item_index);
if (child)
{
widget_instance_give_focus_by_tag(
widget,
child->definition_tag_index,
widget->local_player_index);
widget->parameters.list.selected_index = (short)item_index;
}
else
{
error(
_error_silent,
"failed to set focus to the #%d list item of a column list widget",
item_index);
return FALSE;
}
}
else if (widget->type == _ui_widget_type_spinner_list)
{
if (definition->child_widgets.count > 1)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1396,
definition->child_widgets.count == 3,
"spinner lists must be either 1- or 3-wide... sorry");
if (widget->focused_child != widget->child)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1405,
widget->focused_child);
if (widget->focused_child->previous)
widget_instance_give_focus_directly(widget, widget->focused_child->previous);
}
}
widget->parameters.list.selected_index = (short)item_index;
}
}
else
{
if (widget->type == _ui_widget_type_spinner_list)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1420,
definition->child_widgets.count <= 1,
"spinner lists with more that 1 visible item need to have code-generated lists associated with them... sorry.");
}
if (widget->type == _ui_widget_type_spinner_list &&
TEST_FLAG(definition->list_flags, _list_items_generated_from_string_list_tag) &&
definition->child_widgets.count == 0)
{
widget->parameters.list.selected_index--;
if (widget->parameters.list.selected_index < 0)
widget->parameters.list.selected_index = widget->parameters.list.number_of_items - 1;
}
else
{
child = NULL;
if (widget->focused_child)
{
item_index = widget->parameters.list.selected_index - 1;
child = widget->focused_child->previous;
}
if (!child)
{
child = widget->child;
item_index = 0;
while (child->next)
{
child = child->next;
item_index++;
}
}
/* port: as goto_next_item, over the PC version's labels */
if (widget->type == _ui_widget_type_column_list && pc_menu_tag(widget->definition_tag_index))
{
long tries = 0;
while (widget_instance_port_is_label(child) && tries++ < 256)
{
child = child->previous;
item_index--;
if (!child)
{
child = widget->child;
item_index = 0;
while (child->next)
{
child = child->next;
item_index++;
}
}
}
}
widget_instance_give_focus_by_tag(
widget,
child->definition_tag_index,
widget->local_player_index);
widget->parameters.list.selected_index = (short)item_index;
}
}
widget->parameters.list.last_list_tab_direction = -15;
return TRUE;
}
void ui_widgets_close_all(
void)
{
long local_player_index;
/* port: the virtual keyboard goes with the widgets (while the widget
whose text it edits is still there): left open, it drew on after a game
loaded, with the menu map's font, which the game's tags no longer have
(a player typing when the host started the game) */
if (virtual_keyboard_active())
virtual_keyboard_close();
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
if (widget_globals.active_widgets[local_player_index])
ui_widget_delete(widget_globals.active_widgets[local_player_index]);
if (widget_globals.widget_stack[local_player_index])
dispose_widget_stack(&widget_globals.widget_stack[local_player_index]);
}
return;
}
void ui_widgets_close_all_for_local_player(
short local_player_index)
{
long widget_index;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1154,
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS,
"expected a valid local_player_index");
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
struct widget_instance *widget = widget_globals.active_widgets[widget_index];
if (widget && widget->local_player_index == local_player_index)
{
ui_widget_delete(widget);
if (widget_globals.widget_stack[widget_index])
dispose_widget_stack(&widget_globals.widget_stack[widget_index]);
}
}
return;
}
void ui_widgets_delete_history(
void)
{
long local_player_index;
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
if (widget_globals.widget_stack[local_player_index])
dispose_widget_stack(&widget_globals.widget_stack[local_player_index]);
}
return;
}
void ui_widgets_pop_stack(
short local_player_index)
{
struct widget_stack_data data;
if (local_player_index == NONE)
{
local_player_index = 0;
}
else
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1204,
(local_player_index>=0) && (local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS));
}
if (widget_globals.widget_stack[local_player_index])
pop_widget(&widget_globals.widget_stack[local_player_index], &data);
return;
}
void main_screen_shell_begin_fade(
unsigned long fade_duration_milliseconds)
{
long local_player_index;
ui_stop_main_menu_music();
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
if (widget_globals.active_widgets[local_player_index] &&
!widget_globals.active_widgets[local_player_index]->widget_is_error_dialog)
{
widget_globals.active_widgets[local_player_index]->auto_close_fade_time = fade_duration_milliseconds;
widget_globals.active_widgets[local_player_index]->milliseconds_to_auto_close =
widget_globals.current_system_milliseconds -
widget_globals.active_widgets[local_player_index]->creation_time + 100;
if (widget_globals.widget_stack[local_player_index])
dispose_widget_stack(&widget_globals.widget_stack[local_player_index]);
}
}
return;
}
static void play_sound_tag(
long sound_tag_index)
{
if (sound_tag_index != NONE)
unspatialized_impulse_sound_new(sound_tag_index, 1.0f);
return;
}
void ui_play_audio_feedback_sound(
short audio_feedback)
{
switch (audio_feedback)
{
case _ui_audio_feedback_cursor:
play_sound_tag(tag_loaded(SOUND_DEFINITION_TAG, "sound\\sfx\\ui\\cursor"));
break;
case _ui_audio_feedback_forward:
play_sound_tag(tag_loaded(SOUND_DEFINITION_TAG, "sound\\sfx\\ui\\forward"));
break;
case _ui_audio_feedback_back:
play_sound_tag(tag_loaded(SOUND_DEFINITION_TAG, "sound\\sfx\\ui\\back"));
break;
case _ui_audio_feedback_flag_failure:
play_sound_tag(tag_loaded(SOUND_DEFINITION_TAG, "sound\\sfx\\ui\\flag_failure"));
break;
}
return;
}
static __inline struct widget_instance *widget_instance_get_tail_child_widget(
struct widget_instance *widget)
{
struct widget_instance *child = widget->child;
if (child)
{
while (child->next)
child = child->next;
}
return child;
}
static void ui_widget_add_child(
struct widget_instance *parent,
struct widget_instance *child)
{
struct widget_instance *tail_child = NULL;
struct widget_instance *sibling = parent->child;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2719,
(child->previous == NULL) && (child->next == NULL));
while (sibling)
{
tail_child = sibling;
sibling = sibling->next;
}
if (tail_child)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2729,
tail_child->next == NULL);
tail_child->next = child;
child->previous = tail_child;
}
else
{
parent->child = child;
}
return;
}
static void push_widget(
struct widget_stack_node **top,
struct widget_stack_data *data)
{
struct widget_stack_node *node = pool_new_pointer(
widget_memory_pool,
sizeof(struct widget_stack_node),
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2532);
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2534,
top && data);
if (node)
{
node->data = *data;
node->next = *top;
*top = node;
}
else
{
match_vwarn(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2544,
FALSE,
"out of memory! the UI screen history will be hosed.");
}
return;
}
static boolean widget_instance_can_receive_events(
struct widget_instance *widget)
{
if (widget->disabled)
return FALSE;
if (widget->parent)
{
/* NOTE January carries the parent's definition over from one ancestor to
the next: the flags it tests belong to the ancestor BELOW the one whose
type it tests, from the second iteration onward. Reproduced, not
repaired - the June 2011 rebuild of this function decompiles to the same
carried-over definition pointer */
struct ui_widget_definition *definition =
ui_widget_definition_get(widget->parent->definition_tag_index);
struct widget_instance *parent;
boolean result = TRUE;
for (parent = widget->parent; parent && result; parent = parent->parent)
{
struct ui_widget_definition *parent_definition =
ui_widget_definition_get(parent->definition_tag_index);
result = TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
parent->type == _ui_widget_type_spinner_list ||
parent->type == _ui_widget_type_column_list;
definition = parent_definition;
}
return result;
}
return TRUE;
}
static void widget_instance_set_focused_child_by_index(
long tag_index,
struct widget_instance *widget,
short child_index)
{
struct widget_instance *parent;
if (tag_index == NONE)
return;
parent = widget_instance_find_by_tag_index_recursive(widget, tag_index);
if (!parent)
return;
if (child_index >= 0)
{
struct widget_instance *child;
long index;
for (child = parent->child, index = 0; child; child = child->next, index++)
{
if (child->type == _ui_widget_type_spinner_list &&
ui_widget_definition_get(child->definition_tag_index)->child_widgets.count > 1)
{
return;
}
if (index == child_index)
{
widget_instance_give_focus_directly(widget, child);
if (child->parent &&
(child->parent->type == _ui_widget_type_spinner_list ||
child->parent->type == _ui_widget_type_column_list))
{
child->parent->parameters.list.selected_index = (short)index;
}
return;
}
}
}
else
{
/* with no index the found widget takes the focus itself, unless it is a
multiple-item spinner list, which owns the focus of its own items */
if (widget_instance_can_receive_events(parent) &&
!(parent->type == _ui_widget_type_spinner_list &&
ui_widget_definition_get(parent->definition_tag_index)->child_widgets.count > 1))
{
widget_instance_give_focus_directly(widget, parent);
}
}
return;
}
static void widget_instance_go_back_to_previous(
struct widget_instance *widget)
{
struct widget_stack_data data;
short widget_stack = (widget->local_player_index == NONE) ? 0 : widget->local_player_index;
short previous_local_player_index = NONE;
long previous_widget_tag;
if (widget_globals.widget_stack[widget_stack])
{
pop_widget(&widget_globals.widget_stack[widget_stack], &data);
previous_local_player_index = data.local_player_index;
previous_widget_tag = data.previous_widget_tag;
}
else
{
previous_widget_tag = NONE;
}
ui_widget_delete(widget_instance_get_topmost_parent(widget));
if (previous_widget_tag != NONE)
{
struct widget_instance *new_widget = ui_widget_load_by_name_or_tag(
NULL,
previous_widget_tag,
NULL,
previous_local_player_index,
NONE,
NONE,
NONE);
if (new_widget)
{
widget_instance_set_focused_child_by_index(
data.focused_child_parent_widget_tag,
new_widget,
data.focused_child_index);
}
}
return;
}
static __inline struct widget_instance *widget_instance_find_by_tag_index(
long tag_index)
{
struct widget_instance *result = NULL;
long widget_index;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS && !result;
widget_index++)
{
if (widget_globals.active_widgets[widget_index])
{
result = widget_instance_find_by_tag_index_recursive(
widget_globals.active_widgets[widget_index],
tag_index);
}
}
return result;
}
static void event_handler_dispatch(
struct widget_instance *widget,
struct ui_widget_definition *definition,
struct event_record *event,
struct ui_widget_event_handler_reference *handler,
boolean *calling_widget_deleted)
{
boolean widget_deleted = FALSE;
boolean success = TRUE;
boolean function_failed = FALSE;
long audio_feedback = _ui_audio_feedback_none;
boolean close_widget_after = FALSE;
boolean close_current = FALSE;
boolean close_all = FALSE;
if (TEST_FLAG(handler->flags, _event_handler_run_scenario_script_bit) &&
handler->script[0])
{
if (!hs_evaluate_by_name(handler->script))
error(_error_silent, "failed to run ui widget event script '%s'", handler->script);
}
if (TEST_FLAG(handler->flags, _event_handler_run_function_bit) &&
!widget_deleted &&
!ui_widget_event_handler_function_invoke(
widget,
event,
handler->function,
&widget_deleted))
{
error(_error_silent, "event handler function failed");
function_failed = TRUE;
}
else
{
if (TEST_FLAG(handler->flags, _event_handler_give_focus_to_widget_bit) &&
!widget_deleted)
{
if (handler->widget_tag.index != NONE)
{
widget_instance_give_focus_by_tag(
widget,
handler->widget_tag.index,
widget->local_player_index);
audio_feedback = _ui_audio_feedback_cursor;
}
else
{
error(
_error_silent,
"failed to give focus to a widget because event_handler->ui_widget_tag == NONE");
success = FALSE;
}
}
if (TEST_FLAG(handler->flags, _event_handler_reload_self_bit) &&
!widget_deleted)
{
widget_instance_reload_recursive(widget);
}
if (TEST_FLAG(handler->flags, _event_handler_reload_widget_bit) &&
!widget_deleted)
{
if (handler->widget_tag.index != NONE)
{
ui_widget_reload_by_tag(handler->widget_tag.index);
}
else
{
error(
_error_silent,
"failed to reload widget because event_handler->ui_widget_tag == NONE");
success = FALSE;
}
}
if (TEST_FLAG(handler->flags, _event_handler_close_current_widget_bit) &&
!widget_deleted)
{
close_widget_after = TRUE;
}
if (TEST_FLAG(handler->flags, _event_handler_close_other_widget_bit) &&
!widget_deleted &&
handler->widget_tag.index != NONE)
{
struct widget_instance *other_widget =
widget_instance_find_by_tag_index(handler->widget_tag.index);
if (other_widget)
{
if (other_widget == widget)
close_current = TRUE;
else
ui_widget_delete(other_widget);
}
else
{
error(
_error_silent,
"failed to close widget because event_handler->ui_widget_tag == NONE");
success = FALSE;
}
}
if (TEST_FLAG(handler->flags, _event_handler_close_all_widgets_bit) &&
!widget_deleted)
{
close_all = TRUE;
}
/* port: not from a widget its function deleted (it went back:
menu_functions.c's profile_save_changes), which the Xbox's opened
from regardless */
if (TEST_FLAG(handler->flags, _event_handler_open_widget_bit) &&
!widget_deleted &&
handler->widget_tag.index != NONE)
{
if (!ui_widget_launch_widget(widget, handler->widget_tag.index))
{
error(_error_silent, "event handler failed to spawn widget");
success = FALSE;
}
else
{
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_forward;
widget_deleted = TRUE;
}
}
if (TEST_FLAG(handler->flags, _event_handler_replace_with_other_widget_bit) &&
!widget_deleted &&
handler->widget_tag.index != NONE)
{
struct widget_instance *new_widget = ui_widget_load_by_name_or_tag(
NULL,
handler->widget_tag.index,
widget,
widget->local_player_index,
NONE,
NONE,
NONE);
if (new_widget)
{
struct widget_instance *parent = widget->parent;
struct widget_instance *next = widget->next;
struct widget_instance *previous = widget->previous;
long widget_index;
if (new_widget->previous)
new_widget->previous->next = NULL;
new_widget->previous = NULL;
new_widget->parent = NULL;
new_widget->horizontal_offset += widget->horizontal_offset;
new_widget->vertical_offset += widget->vertical_offset;
if (parent)
{
new_widget->parent = parent;
if (parent->child == widget)
parent->child = new_widget;
if (parent->focused_child == widget)
parent->focused_child = new_widget;
}
if (next)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
3721,
next->previous == widget);
next->previous = new_widget;
}
new_widget->next = next;
if (previous)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
3728,
previous->next == widget);
previous->next = new_widget;
}
new_widget->previous = previous;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
if (widget_globals.active_widgets[widget_index] == new_widget)
{
widget_globals.active_widgets[widget_index] = NULL;
break;
}
}
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_forward;
widget->previous = NULL;
widget->next = NULL;
widget->parent = NULL;
close_current = TRUE;
}
else
{
error(
_error_silent,
"failed to open widget because the specified widget tag was not found");
success = FALSE;
}
}
if (TEST_FLAG(handler->flags, _event_handler_go_back_to_previous_widget_bit))
{
widget_instance_go_back_to_previous(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_back;
widget_deleted = TRUE;
}
if (handler->sound_effect.index != NONE)
unspatialized_impulse_sound_new(handler->sound_effect.index, 1.0f);
if (close_all)
{
long widget_index;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
if (widget_globals.active_widgets[widget_index])
ui_widget_delete(widget_globals.active_widgets[widget_index]);
while (widget_globals.widget_stack[widget_index])
{
struct widget_stack_data data;
pop_widget(&widget_globals.widget_stack[widget_index], &data);
}
}
widget_deleted = TRUE;
}
else if (close_widget_after)
{
ui_widget_delete(widget_instance_get_topmost_parent(widget));
widget_deleted = TRUE;
}
else if (close_current)
{
ui_widget_delete(widget);
widget_deleted = TRUE;
}
}
if (!success || function_failed)
{
if (TEST_FLAG(handler->flags, _event_handler_look_for_conditional_widget_on_failure_bit))
{
long conditional_index;
for (conditional_index = 0;
conditional_index < definition->conditional_widgets.count;
conditional_index++)
{
struct ui_widget_conditional_reference *conditional =
(struct ui_widget_conditional_reference *)definition->conditional_widgets.address +
conditional_index;
if (function_failed == TRUE &&
TEST_FLAG(
conditional->flags,
_conditional_widget_load_if_event_handler_function_fails_bit))
{
if (!widget_deleted)
{
if (conditional->widget_tag.index != NONE)
{
if (!ui_widget_launch_widget(widget, conditional->widget_tag.index))
error(_error_silent, "condition handler failed to spawn widget");
else
widget_deleted = TRUE;
}
}
else
{
error(
_error_silent,
"couldn't load conditional widget because the calling widget was deleted");
}
}
}
}
}
ui_play_audio_feedback_sound(audio_feedback);
*calling_widget_deleted = widget_deleted;
return;
}
static boolean ui_widget_load_children_recursive(
struct widget_instance *widget,
struct ui_widget_definition *definition)
{
boolean result = TRUE;
long child_index;
if (TEST_FLAG(definition->list_flags, _list_items_generated_from_string_list_tag))
{
struct string_list *string_list;
long string_index;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2592,
widget->type == _ui_widget_type_spinner_list,
"_list_items_generated_from_string_list_tag flag should only be set for 1-wide spinner list widgets");
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2594,
definition->child_widgets.count == 0,
"no child widget references are needed to define list items when generating a list from a string list tag");
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2596,
definition->text_label_string_list.index != NONE,
"_list_items_generated_from_string_list_tag flag was set but no string list tag was specified");
string_list = unicode_string_list_definition_get(definition->text_label_string_list.index);
widget_globals.dont_load_children_recursive = TRUE;
for (string_index = 0;
string_index < string_list->strings.count +
spinner_string_list_extra_count(definition->text_label_string_list.index);
string_index++)
{
struct widget_instance *child = ui_widget_load_by_name_or_tag(
NULL,
widget->definition_tag_index,
widget,
widget->local_player_index,
NONE,
NONE,
NONE);
if (!child)
{
result = FALSE;
break;
}
ui_widget_add_child(widget, child);
widget->parameters.list.number_of_items++;
}
widget_globals.dont_load_children_recursive = FALSE;
}
for (child_index = 0;
child_index < definition->child_widgets.count;
child_index++)
{
struct ui_widget_child_reference *reference =
(struct ui_widget_child_reference *)definition->child_widgets.address + child_index;
short controller_index = widget->local_player_index;
if (TEST_FLAG(reference->flags, _child_widget_use_custom_controller_index_bit))
{
if (reference->custom_controller_index >= 0 &&
reference->custom_controller_index < MAXIMUM_GAMEPADS)
{
controller_index = reference->custom_controller_index;
}
else
{
error(
_error_silent,
"invalid controller index specified for child widget (#%d)",
reference->custom_controller_index);
}
}
if (reference->widget_tag.index != NONE)
{
struct widget_instance *child = ui_widget_load_by_name_or_tag(
NULL,
reference->widget_tag.index,
widget,
controller_index,
NONE,
NONE,
NONE);
if (!child)
{
result = FALSE;
break;
}
child->horizontal_offset = reference->horizontal_offset + widget->horizontal_offset;
child->vertical_offset = reference->vertical_offset + widget->vertical_offset;
ui_widget_add_child(widget, child);
}
}
if (widget->type == _ui_widget_type_column_list &&
definition->extended_description_widget.index != NONE)
{
widget->parameters.list.extended_description = ui_widget_load_by_name_or_tag(
NULL,
definition->extended_description_widget.index,
widget,
widget->local_player_index,
NONE,
NONE,
NONE);
if (widget->parameters.list.extended_description)
{
if (widget->parameters.list.extended_description->previous)
widget->parameters.list.extended_description->previous->next = NULL;
widget->parameters.list.extended_description->previous = NULL;
widget->parameters.list.extended_description->parent = NULL;
}
}
if (!TEST_FLAG(definition->flags, _widget_dont_focus_a_specific_child_bit))
{
boolean focus_a_child = FALSE;
if (widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list)
{
widget->parameters.list.selected_index = 0;
widget->parameters.list.last_list_tab_direction = 0;
focus_a_child = TRUE;
}
else if (TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit))
{
focus_a_child = TRUE;
}
if (focus_a_child)
{
struct widget_instance *child;
/* port: the PC version's lists (port/assets/menus) start on their
first child that takes events, past their labels */
boolean skip_labels = widget->type == _ui_widget_type_column_list &&
pc_menu_tag(widget->definition_tag_index);
short index = 0;
for (child = widget->child; child; child = child->next, index++)
{
if (((widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list) &&
(!skip_labels || !widget_instance_port_is_label(child))) ||
widget_instance_can_handle_events(child))
{
widget->focused_child = child;
if (skip_labels)
widget->parameters.list.selected_index = index;
break;
}
}
if (!widget->focused_child && skip_labels)
widget->focused_child = widget->child;
}
}
return result;
}
static void widget_instance_initialize(
struct widget_instance *widget,
struct widget_instance *parent,
struct ui_widget_definition *definition,
long tag_index,
short local_player_index,
short widget_stack)
{
long handler_index;
memset(widget, 0, sizeof(struct widget_instance));
if (TEST_FLAG(definition->list_flags, _list_items_generated_from_string_list_tag) &&
parent &&
tag_index == parent->definition_tag_index)
{
widget->type = _ui_widget_type_text_box;
}
widget->definition_tag_index = tag_index;
widget->name = definition->name;
widget->local_player_index = local_player_index;
widget->type = definition->type;
widget->visible = TRUE;
widget->render_regardless_of_controller_index =
TEST_FLAG(definition->flags, _widget_render_regardless_of_controller_index_bit);
/* port: a network co-op game never pauses (it opens the campaign's pause
screen, which would) */
widget->pause_game_time = TEST_FLAG(definition->flags, _widget_pause_game_time_bit) &&
!network_coop_active();
widget->creation_time = widget_globals.current_system_milliseconds;
widget->milliseconds_to_auto_close = MAX(definition->milliseconds_to_auto_close, 0);
widget->auto_close_fade_time = MAX(definition->auto_close_fade_time, 0);
widget->alpha_modifier = 1.0f;
widget->parent = parent;
switch (widget->type)
{
case _ui_widget_type_text_box:
widget->parameters.text_box.string_list_index = NONE;
break;
}
if (definition->background_bitmap.index != NONE)
{
widget->animation.number_of_sprite_frames = TAG_BLOCK_GET_ELEMENT(
&bitmap_group_get(definition->background_bitmap.index)->sequences,
0,
struct bitmap_group_sequence)->bitmap_count;
}
if (!widget_globals.dont_load_children_recursive)
{
if (!ui_widget_load_children_recursive(widget, definition))
error(_error_silent, "failed to load widget children");
}
for (handler_index = 0;
handler_index < definition->event_handlers.count;
handler_index++)
{
struct ui_widget_event_handler_reference *handler =
(struct ui_widget_event_handler_reference *)definition->event_handlers.address + handler_index;
if (handler->event_type == _widget_event_created)
{
struct event_record event = {0};
boolean widget_deleted;
event.controller_index = widget->local_player_index;
event_handler_dispatch(widget, definition, &event, handler, &widget_deleted);
}
}
if (!widget->focused_child)
{
struct widget_instance *child;
for (child = widget->child; child; child = child->next)
{
if (widget_instance_can_handle_events(child))
widget_instance_give_focus_directly(widget, child);
}
}
if (widget->pause_game_time == TRUE)
{
widget_globals.pause_game_time_count++;
if (!game_time_get_paused())
game_time_set_paused(TRUE);
if (!widget_globals.sound_paused && !we_are_at_the_main_menu)
{
sound_pause(TRUE);
widget_globals.sound_paused = TRUE;
}
}
return;
}
/* port: the PC version's events that this engine never sends (its custom
activation), for the menus' functions (port/linux/game/menu_functions.c):
runs the widget's handlers for the event, else the first descendant's that
has any (depth first); FALSE if none has */
boolean ui_widget_port_dispatch_event(
struct widget_instance *widget,
short event_type,
short controller_index,
boolean *deleted)
{
struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index);
struct widget_instance *child;
boolean found = FALSE;
long handler_index;
/* (deleted: a handler deleted the widget's screen, opening another or
going back; the callers' widgets are gone with it) */
*deleted = FALSE;
for (handler_index = 0; handler_index < definition->event_handlers.count; handler_index++)
{
struct ui_widget_event_handler_reference *handler =
(struct ui_widget_event_handler_reference *)definition->event_handlers.address + handler_index;
if (handler->event_type == event_type)
{
struct event_record event = {0};
boolean widget_deleted = FALSE;
event.controller_index = controller_index;
event_handler_dispatch(widget, definition, &event, handler, &widget_deleted);
found = TRUE;
if (widget_deleted)
{
*deleted = TRUE;
return TRUE;
}
}
}
for (child = widget->child; child && !found; child = child->next)
{
found = ui_widget_port_dispatch_event(child, event_type, controller_index, deleted);
if (*deleted)
break;
}
return found;
}
/* port: the number of a list's focused item: among all its children, but
in the PC version's lists (port/assets/menus) among those that take events,
past their labels, as its game counts them */
short ui_widget_port_list_index(
struct widget_instance *list_widget)
{
boolean skip_labels = pc_menu_tag(list_widget->definition_tag_index);
struct widget_instance *child;
short index = 0;
for (child = list_widget->child; child && child != list_widget->focused_child; child = child->next)
{
if (!skip_labels || !widget_instance_port_is_label(child))
index++;
}
return index;
}
/* port: back to the screen before, for the menus' functions */
void ui_widget_port_go_back(
struct widget_instance *widget)
{
widget_instance_go_back_to_previous(widget);
}
struct widget_instance *ui_widget_load_by_name_or_tag(
char const *name,
long tag_index,
struct widget_instance *parent,
short local_player_index,
long invoking_widget_tag,
long focused_child_parent_widget_tag,
short focused_child_index)
{
struct ui_widget_definition *definition;
struct widget_instance *widget = NULL;
short widget_stack = (local_player_index == NONE) ? 0 : local_player_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
377,
widget_globals.initialized);
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
378,
(name != NULL) || (tag_index != NONE));
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
379,
(widget_stack>=0) && (widget_stack<MAXIMUM_GAMEPADS));
if (tag_index == NONE)
tag_index = tag_loaded(UI_WIDGET_DEFINITION_TAG, name);
if (tag_index != NONE)
{
definition = ui_widget_definition_get(tag_index);
widget = pool_new_pointer(
widget_memory_pool,
sizeof(struct widget_instance),
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
395);
if (widget)
{
if (!parent)
{
short previous_local_player_index;
if (widget_globals.active_widgets[widget_stack])
{
previous_local_player_index =
widget_globals.active_widgets[widget_stack]->local_player_index;
ui_widget_delete(widget_globals.active_widgets[widget_stack]);
}
else
{
previous_local_player_index = NONE;
}
widget_globals.active_widgets[widget_stack] = widget;
if (invoking_widget_tag != NONE &&
!TEST_FLAG(
ui_widget_definition_get(invoking_widget_tag)->flags,
_widget_dont_push_history_data_bit))
{
struct widget_stack_data data;
data.previous_widget_tag = invoking_widget_tag;
data.focused_child_parent_widget_tag = focused_child_parent_widget_tag;
data.focused_child_index = focused_child_index;
data.local_player_index = previous_local_player_index;
push_widget(&widget_globals.widget_stack[widget_stack], &data);
}
}
if (local_player_index == NONE)
{
switch (definition->controller_index)
{
case _widget_controller0:
local_player_index = 0;
break;
case _widget_controller1:
local_player_index = 1;
break;
case _widget_controller2:
local_player_index = 2;
break;
case _widget_controller3:
local_player_index = 3;
break;
case _widget_controller_any:
local_player_index = NONE;
break;
}
}
widget_instance_initialize(
widget,
parent,
definition,
tag_index,
local_player_index,
widget_stack);
}
else
{
error(_error_silent, "failed to create new widget; out of memory!");
}
}
else
{
error(_error_silent, "ui_widget_definition tag '%s'/%d not loaded", name, NONE);
}
return widget;
}
static void render_state_text(
rectangle2d *bounds,
rectangle2d *cursor_bounds,
wchar_t const *text)
{
rectangle2d text_bounds;
short initial_indent = cursor_bounds->x0 - bounds->x0;
if (initial_indent < 0)
error(_error_silent, "initial_indent<0 in render_state_text() and was about to explode");
initial_indent = MAX(0, initial_indent);
draw_string_set_indents(initial_indent, 0);
draw_unicode_string_compute_bounds(bounds, text, &text_bounds, cursor_bounds);
cursor_bounds->x0 -= 3;
text_bounds.x0 = bounds->x0;
rasterizer_draw_unicode_string(&text_bounds, NULL, NULL, 0, text);
bounds->y0 = cursor_bounds->y0;
return;
}
static void render_state_bitmap(
rectangle2d *bounds,
rectangle2d *cursor_bounds,
pixel32 color,
struct icon_hud_element_definition *icon)
{
struct game_globals *game_globals;
struct game_globals_interface_tag_references *interface_tag_references;
long bitmap_group_index;
long frame_index;
struct bitmap_data const *bitmap;
real_rectangle2d const *clip;
real scale;
point2d point;
global_scenario_get();
game_globals = scenario_get_game_globals();
interface_tag_references = game_globals->interface_tag_references.count
? TAG_BLOCK_GET_ELEMENT(
&game_globals->interface_tag_references,
0,
struct game_globals_interface_tag_references)
: NULL;
bitmap_group_index = interface_tag_references->interface_tag_references[_interface_bitmap_iface_map2].index;
frame_index = 0;
bitmap = NULL;
clip = NULL;
if (icon->frame_rate)
frame_index = system_milliseconds() * 30 / 1000 / icon->frame_rate;
hud_retrieve_bitmap_and_bounding_rect(
bitmap_group_index,
icon->sequence_index,
frame_index,
&bitmap,
&clip);
if (bitmap && _texture_cache_bitmap_get_hardware_format(
(struct bitmap_data *)bitmap, FALSE, TRUE))
{
scale = hud_globals_get_scale(local_player_count() > 1);
point.x = (short)(icon->offset.x * scale + cursor_bounds->x0 + 1.0f);
point.y = (short)(cursor_bounds->y1 - icon->offset.y * scale - 2.0f);
hud_draw_bitmap_direct(
bitmap,
_hud_anchor_bottom_left,
&point,
clip,
scale,
0.0f,
TEST_FLAG(icon->flags, _hud_icon_use_color_bit) ? icon->color : color,
FALSE);
if (TEST_FLAG(icon->flags, _hud_icon_absolute_width_bit))
cursor_bounds->x0 = icon->width_offset + point.x;
else if (clip)
cursor_bounds->x0 = (short)((clip->x1 - clip->x0) * bitmap->width + icon->width_offset + point.x);
else
cursor_bounds->x0 = bitmap->width + icon->width_offset + point.x;
}
return;
}
void draw_string_and_hack_in_icons(
rectangle2d *bounds,
rectangle2d *clip,
point2d *cursor_reference,
short height_adjust,
wchar_t const *instring,
boolean ignore_icon_color)
{
wchar_t *current = string_data;
rectangle2d cursor_bounds = *bounds;
wcscpy(string_data, instring);
while (current)
{
wchar_t *icon_spec = wcschr(current, L'%');
short icon_type;
if (!icon_spec)
break;
*icon_spec = 0;
icon_spec++;
render_state_text(bounds, &cursor_bounds, current);
current = icon_spec;
icon_type = get_icon_type(icon_spec);
if (icon_type == NONE)
{
render_state_text(bounds, &cursor_bounds, L"%");
}
else
{
short remapped_icon_type;
short icon_index;
current = icon_spec + wcslen(icon_names[icon_type]);
remapped_icon_type = remap_sticks_for_local_player(
icon_type,
local_player_index_for_draw_string_and_hack_in_icons);
icon_index = NONE;
if (remapped_icon_type > _icon_right_stick)
{
if (remapped_icon_type <= _icon_look)
{
if (remapped_icon_type <= _icon_accept)
{
struct game_input_preferences preferences;
input_abstraction_get_local_player_preferences(
local_player_index_for_draw_string_and_hack_in_icons,
&preferences);
icon_index = preferences.game_control_to_xbox_buttons[
button_mappings[remapped_icon_type - _icon_action]];
}
else
{
icon_index = button_mappings[remapped_icon_type - _icon_action];
switch (remapped_icon_type)
{
case _icon_accept:
icon_index = _icon_start_button;
break;
case _icon_back:
icon_index = _icon_back_button;
break;
case _icon_move:
icon_index = _icon_left_stick;
break;
case _icon_look:
icon_index = _icon_right_stick;
break;
}
}
}
else
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4341,
FALSE);
}
}
else
{
icon_index = remapped_icon_type;
}
{
struct icon_hud_element_definition *icon = TAG_BLOCK_GET_ELEMENT(
&hud_globals->messaging.button_icons,
icon_index,
struct icon_hud_element_definition);
byte saved_flags = icon->flags;
short saved_width_offset = icon->width_offset;
real_argb_color icon_color;
real_argb_color text_color;
long alpha;
pixel32_to_real_argb_color(icon->color, &icon_color);
icon->flags &= ~FLAG(_hud_icon_use_color_bit);
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4362,
icon_index>=0 && icon_index<NUM_ICONS);
if (icon_is_special[icon_index])
{
icon->flags &= ~FLAG(_hud_icon_absolute_width_bit);
icon->width_offset = -5;
}
draw_string_get_color(&text_color);
alpha = (long)(text_color.alpha * 255.0f) << 24;
if (icon->color == 0 || ignore_icon_color)
icon_color = text_color;
icon_color.red *= text_color.alpha;
icon_color.green *= text_color.alpha;
icon_color.blue *= text_color.alpha;
render_state_bitmap(
bounds,
&cursor_bounds,
(real_argb_color_to_pixel32(&icon_color) & 0x00ffffff) | alpha,
icon);
bounds->x0++;
icon->flags = saved_flags;
icon->width_offset = saved_width_offset;
}
}
}
if (current)
render_state_text(bounds, &cursor_bounds, current);
draw_string_set_indents(0, 0);
return;
}
void ui_start_main_menu_music(
void)
{
if (!widget_globals.main_menu_music_active && !main_menu_fade_active())
{
long sound_definition_index = tag_loaded(LOOPING_SOUND_DEFINITION_TAG, "sound\\music\\title1\\title1");
if (sound_definition_index != NONE)
{
error(_error_silent, "starting main menu music");
scripted_looping_sound_start(sound_definition_index, NONE, 1.0f);
widget_globals.main_menu_music_active = TRUE;
}
else
{
error(_error_silent, "title music tag not found");
}
}
return;
}
void ui_stop_main_menu_music(
void)
{
if (widget_globals.main_menu_music_active == TRUE)
{
long sound_definition_index = tag_loaded(LOOPING_SOUND_DEFINITION_TAG, "sound\\music\\title1\\title1");
if (sound_definition_index != NONE)
{
error(_error_silent, "stopping main menu music");
scripted_looping_sound_stop(sound_definition_index);
}
else
{
error(_error_silent, "title music tag not found");
}
widget_globals.main_menu_music_active = FALSE;
}
return;
}
void display_error_deferred(
short error_code,
short local_player_index,
boolean modal,
boolean pause_game_time)
{
long index;
if (local_player_index == NONE)
{
index = 0;
}
else
{
index = local_player_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2288,
(index>=0) && (index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS));
}
if (widget_globals.deferred_errors[index].error_code == NONE)
{
widget_globals.deferred_errors[index].error_code = error_code;
widget_globals.deferred_errors[index].local_player_index = local_player_index;
widget_globals.deferred_errors[index].modal = modal;
widget_globals.deferred_errors[index].pause_game_time = pause_game_time;
}
else
{
error(
_error_silent,
"there is already a deferred error message for local player %d; ignoring this one",
index);
}
return;
}
/* port: an error message of the port's own text (the maps have only the
Xbox's), in the dialog of an error whose text it takes the place of */
void display_error_text_deferred(
wchar_t const *text,
short local_player_index)
{
short index = local_player_index == NONE ? 0 : local_player_index;
if (!VALID_INDEX(index, MAXIMUM_NUMBER_OF_LOCAL_PLAYERS) || widget_globals.deferred_errors[index].error_code != NONE)
return;
ui_widget_port_error_pending_text = text;
display_error_deferred(_error_cannot_create_saved_game_file_with_empty_name, local_player_index, TRUE, FALSE);
return;
}
void display_errors_deferred_until_cinematic_stop(
void)
{
short local_player_index;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2367,
!cinematic_in_progress(),
"Noooooooooooooooooo!!!");
for (local_player_index = 0;
local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
local_player_index++)
{
struct ui_widget_deferred_cinematic_error *deferred_error =
&widget_globals.deferred_cinematic_errors[local_player_index];
if (deferred_error->error_code >= 0 && deferred_error->error_code < NUMBER_OF_ERROR_CODES)
{
display_error(
deferred_error->error_code,
local_player_index,
deferred_error->modal,
deferred_error->pause_game_time);
}
deferred_error->error_code = NONE;
}
return;
}
boolean ui_widgets_active_for_local_player(
short local_player_index)
{
boolean result = FALSE;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
1110,
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS,
"expected a valid local_player_index");
if (widget_globals.initialized)
{
long widget_index;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
if (widget_globals.active_widgets[widget_index] &&
widget_globals.active_widgets[widget_index]->local_player_index == local_player_index)
{
result = TRUE;
break;
}
}
}
return result;
}
void display_error(
short error_code,
short local_player_index,
boolean modal,
boolean pause_game_time)
{
if (cinematic_in_progress())
{
if (local_player_index == NONE)
local_player_index = 0;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2077,
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
if (widget_globals.deferred_cinematic_errors[local_player_index].error_code == NONE)
{
widget_globals.deferred_cinematic_errors[local_player_index].error_code = error_code;
widget_globals.deferred_cinematic_errors[local_player_index].modal = modal;
widget_globals.deferred_cinematic_errors[local_player_index].pause_game_time = pause_game_time;
}
else
{
error(_error_silent, "there is already a deferred-for-cinematic error queued for player #%d; ignoring this one", local_player_index);
}
}
else
{
char const *widget_name = NULL;
short error_local_player_index;
short local_player_count;
short widget_stack;
boolean first_local_player = TRUE;
struct widget_instance *top_widget;
long top_widget_tag_index;
struct widget_instance *widget;
/* (port: the port's own text for this error, taken whether or not its
dialog opens, so that it is never another's) */
wchar_t const *port_text = NULL;
if (error_code == _error_cannot_create_saved_game_file_with_empty_name)
{
port_text = ui_widget_port_error_pending_text;
ui_widget_port_error_pending_text = NULL;
}
if (local_player_index != NONE)
{
short index;
local_player_count = 0;
error_local_player_index = NONE;
for (index = local_player_get_next(NONE); index != NONE; index = local_player_get_next(index))
{
if (index == local_player_index)
{
error_local_player_index = local_player_index;
if (local_player_count > 0)
first_local_player = FALSE;
}
local_player_count++;
}
}
else
{
error_local_player_index = NONE;
local_player_count = 0;
}
if (error_local_player_index == NONE && !we_are_at_the_main_menu)
local_player_index = NONE;
switch (local_player_count)
{
case 0:
case 1:
widget_name = modal
? "ui\\shell\\error\\error_modal_fullscreen"
: "ui\\shell\\error\\error_nonmodal_fullscreen";
break;
case 2:
widget_name = modal
? "ui\\shell\\error\\error_modal_halfscreen"
: "ui\\shell\\error\\error_nonmodal_halfscreen";
break;
case 3:
if (first_local_player == TRUE)
{
widget_name = modal
? "ui\\shell\\error\\error_modal_halfscreen"
: "ui\\shell\\error\\error_nonmodal_halfscreen";
}
else
{
widget_name = modal
? "ui\\shell\\error\\error_modal_qtrscreen"
: "ui\\shell\\error\\error_nonmodal_qtrscreen";
}
break;
case 4:
widget_name = modal
? "ui\\shell\\error\\error_modal_qtrscreen"
: "ui\\shell\\error\\error_nonmodal_qtrscreen";
break;
default:
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2161,
FALSE,
"invalid local player count");
break;
}
if (widget_name)
{
widget_stack = local_player_index == NONE ? 0 : local_player_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2168,
(widget_stack>=0) && (widget_stack<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS));
if (transition_to_game_in_progress())
{
error(_error_silent, "aborting to the main menu root, for safety's sake");
main_screen_shell_load();
main_defer_map_map_change();
ui_widgets_set_fade_value(-1.0f);
}
top_widget = widget_globals.active_widgets[widget_stack];
top_widget_tag_index = top_widget ? top_widget->definition_tag_index : NONE;
if (top_widget && top_widget->widget_is_error_dialog == TRUE)
{
error(_error_silent, "there is already an error message displayed for this local player index");
widget = NULL;
}
else
{
widget = ui_widget_load_by_name_or_tag(
widget_name,
NONE,
NULL,
local_player_index,
top_widget_tag_index,
NONE,
NONE);
}
if (widget)
{
struct widget_instance *text_box;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2206,
widget->child && widget->child->child,
"error screen widget tag not layed out as expected");
text_box = widget->child->child;
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2208,
text_box->type == _ui_widget_type_text_box,
"expected a text box widget in the error widget");
text_box->parameters.text_box.string_list_index = PIN(error_code, 0, NUMBER_OF_ERROR_CODES - 1);
/* (port: the port's own text in place of the error's) */
if (port_text)
{
ui_widget_port_error_text_box = text_box;
ui_widget_port_error_text = port_text;
}
widget->widget_is_error_dialog = TRUE;
if (!widget->pause_game_time)
{
widget->pause_game_time = pause_game_time;
if (widget->pause_game_time == TRUE)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2217,
widget_globals.pause_game_time_count>=0,
"widget pause counter is out of whack");
widget_globals.pause_game_time_count++;
if (!game_time_get_paused())
game_time_set_paused(TRUE);
if (!widget_globals.sound_paused && !we_are_at_the_main_menu)
{
sound_pause(TRUE);
widget_globals.sound_paused = TRUE;
}
}
}
switch (error_code)
{
case _error_controller_unplugged_start_to_continue:
widget->milliseconds_to_auto_close = 0;
widget->auto_close_fade_time = 0;
break;
case _error_controller_unplugged:
widget->close_if_local_player_controller_present = TRUE;
widget->milliseconds_to_auto_close = 0;
widget->auto_close_fade_time = 0;
break;
default:
widget->close_if_local_player_controller_present = FALSE;
break;
}
}
else
{
error(_error_silent, "failed to display error message");
}
}
}
return;
}
void display_error_abort_to_dashboard(
short error_code,
boolean optional)
{
char const *widget_name;
struct widget_instance *widget;
if (optional == TRUE)
widget_name = "ui\\shell\\error\\error_abort_to_dashboard";
else
widget_name = "ui\\shell\\error\\error_abort_to_dashboard_you_have_no_choice";
if (!optional)
ui_widgets_close_all();
widget = ui_widget_load_by_name_or_tag(widget_name, NONE, NULL, NONE, NONE, NONE, NONE);
if (widget)
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2319,
widget->type == _ui_widget_type_text_box,
"expected a text box widget");
widget->parameters.text_box.string_list_index = error_code;
widget->widget_is_error_dialog = TRUE;
dashboard_abort_error = error_code;
}
else
{
error(_error_silent, "failed to load '%s' widget", widget_name);
}
return;
}
void display_error_damaged_media(
void)
{
display_error_abort_to_dashboard(_error_media_damaged, FALSE);
input_frame_end();
main_loop_of_death();
return;
}
/* port: menu_tags.c's */
char const *pc_menus_screen(char const *name);
void network_game_reset_to_pregame_ui(
void)
{
ui_widgets_close_all();
if (network_game_is_splitscreen_local())
{
if (network_game_is_quickstart_local())
{
if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\main_menu\\multiplayer_type_select\\split_screen\\pregame\\splitscreen_pregame_wrapper_normal",
NONE, NULL, NONE, NONE, NONE, NONE))
{
error(_error_silent, "failed to load pregame screen after quickstart match");
}
}
else
{
if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\main_menu\\multiplayer_type_select\\split_screen\\splitscreen_map_select_postgame_wrapper",
NONE, NULL, NONE, NONE, NONE, NONE))
{
error(_error_silent, "failed to load map select postgame screen");
}
}
}
else
{
if (global_network_game_server_get())
{
network_game_server_pause_countdown(global_network_game_server_get(), TRUE);
/* port: with the PC version's menus, theirs (port/linux/game/menu_tags.c) */
if (!ui_widget_load_by_name_or_tag(
pc_menus_screen("ui\\shell\\main_menu\\multiplayer_type_select\\connected\\connected_map_select_postgame_wrapper"),
NONE, NULL, NONE, NONE, NONE, NONE))
{
error(_error_silent, "failed to load map select postgame screen");
}
}
else
{
if (!ui_widget_load_by_name_or_tag(
pc_menus_screen("ui\\shell\\main_menu\\multiplayer_type_select\\connected\\pregame\\connected_pregame_screen"),
NONE, NULL, NONE, NONE, NONE, NONE))
{
error(_error_silent, "failed to load networked pregame status screen");
}
}
}
return;
}
void display_scenario_help(
short string_index)
{
char scenario_name[256];
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2407,
string_index>=0);
if (global_scenario_index != NONE)
{
char const *widget_name;
short local_player_index;
struct widget_instance *widget;
csstrncpy(scenario_name, tag_get_name(global_scenario_index), sizeof(scenario_name) - 1);
scenario_name[sizeof(scenario_name) - 1] = 0;
strlwr(scenario_name);
if (strstr(scenario_name, "a10"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_a10";
else if (strstr(scenario_name, "a30"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_a30";
else if (strstr(scenario_name, "a50"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_a50";
else if (strstr(scenario_name, "b30"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_b30";
else if (strstr(scenario_name, "b40"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_b40";
else if (strstr(scenario_name, "c10"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_c10";
else if (strstr(scenario_name, "c20"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_c20";
else if (strstr(scenario_name, "c40"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_c40";
else if (strstr(scenario_name, "d20"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_d20";
else if (strstr(scenario_name, "d40"))
widget_name = "ui\\shell\\solo_game\\player_help\\player_help_screen_d40";
else
{
error(_error_silent, "can't display scenario help; unknown scenario is active '%s'", scenario_name);
return;
}
local_player_index = player_ui_get_single_player_local_player_controller(0);
widget = ui_widget_load_by_name_or_tag(
widget_name,
NONE,
NULL,
local_player_index,
NONE,
NONE,
NONE);
if (widget)
{
struct widget_instance *text_box;
for (text_box = widget->child; text_box; text_box = text_box->next)
{
if (text_box->type == _ui_widget_type_text_box)
break;
}
match_vassert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
2438,
text_box && text_box->type == _ui_widget_type_text_box,
"expected text box widget in player help screen");
text_box->parameters.text_box.string_list_index = string_index;
}
else
{
error(_error_silent, "failed to load in-game help dialog");
}
}
else
{
error(_error_silent, "can't display scenario help because no scenario is loaded");
}
return;
}
static boolean transition_to_game_in_progress(
void)
{
if (
we_are_at_the_main_menu &&
widget_globals.fade_to_black <= 1.0f &&
widget_globals.fade_to_black >= 0.0f)
{
return TRUE;
}
return FALSE;
}
static short get_icon_type(
wchar_t const *string)
{
short icon_index;
for (icon_index = 0; icon_index < NUMBEROF(icon_names); icon_index++)
{
if (_wcsnicmp(string, icon_names[icon_index], wcslen(icon_names[icon_index])) == 0)
break;
}
if (icon_index == NUMBEROF(icon_names))
icon_index = NONE;
return icon_index;
}
static boolean should_flip_sticks_for_local_player(
short local_player_index)
{
struct game_input_preferences preferences;
if (local_player_index == NONE)
local_player_index = local_player_get_next(NONE);
memset(&preferences, 0, sizeof(preferences));
if (local_player_index != NONE)
input_abstraction_get_local_player_preferences(local_player_index, &preferences);
switch (preferences.joystick_controls)
{
case _joystick_preset_south_paw:
case _joystick_preset_legacy_south_paw:
return TRUE;
}
return FALSE;
}
short remap_sticks_for_local_player(
short icon,
short local_player_index)
{
switch (icon)
{
case _icon_left_stick:
case _icon_move:
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4243,
16 == get_icon_type(L"left-stick"));
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4244,
30 == get_icon_type(L"move"));
icon = should_flip_sticks_for_local_player(local_player_index) ? _icon_right_stick : _icon_left_stick;
break;
case _icon_right_stick:
case _icon_look:
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4250,
17 == get_icon_type(L"right-stick"));
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4251,
31 == get_icon_type(L"look"));
icon = should_flip_sticks_for_local_player(local_player_index) ? _icon_left_stick : _icon_right_stick;
break;
}
return icon;
}
static unsigned long __stdcall filesystem_initialization_thread_proc(
void *input)
{
widget_globals.filesystem_check_result = saved_game_perform_file_system_checks();
if (!widget_globals.filesystem_check_result)
{
word number_of_profiles = 1;
long profile_index;
playlist_profiles_enumerate_available_to_local_player_index(NONE, &number_of_profiles, &profile_index);
player_profiles_enumerate_available_to_local_player_index(NONE, &number_of_profiles, &profile_index, TRUE);
player_ui_get_player1_last_used_profile_index();
}
return 0;
}
static void perform_filesystem_initialization(
void)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
5439,
widget_globals.initialization_thread==NULL);
error(_error_silent, "begining filesystem checks & saved game file enumeration...");
ui_widgets_inhibit_processing(TRUE);
widget_globals.filesystem_check_result = 0;
if (!create_thread(0, filesystem_initialization_thread_proc, NULL, &widget_globals.initialization_thread))
{
error(_error_silent, "failed to spawn thread for filesystem checks - running synchronously!");
widget_globals.initialization_thread = NULL;
filesystem_initialization_thread_proc(NULL);
ui_widgets_inhibit_processing(FALSE);
}
return;
}
void main_screen_shell_load(
void)
{
boolean load_main_menu = TRUE;
ui_widgets_inhibit_processing(FALSE);
if (main_screen_shell_first_load == TRUE)
{
char const *command_line = shell_get_command_line();
if (command_line && _stricmp(command_line, "xdemo") == 0)
{
error(_error_silent, "xbox command line= '%s'", command_line);
}
else
{
bink_playback_start(
attract_mode_get_localized_movie_path(_bink_intro_movie),
FLAG(_bink_playback_button_click_stops_movie_bit) |
FLAG(_bink_playback_prevent_events_to_ui_bit) |
FLAG(_bink_playback_return_to_main_menu_when_finished_bit) |
FLAG(_bink_playback_dont_allow_skipping_if_filesystem_check_thread_is_active_bit) |
FLAG(_bink_playback_eat_up_memory_like_a_goddamn_beaver_bit));
load_main_menu = FALSE;
if (!bink_playback_active())
load_main_menu = TRUE;
}
perform_filesystem_initialization();
input_abstraction_reset_controller_detection_timer();
}
if (load_main_menu)
{
attract_mode_reset_timer();
ui_widgets_close_all();
/* port: the menus' main menu, when they are there (port/linux/game/menu_tags.c) */
{
extern char const *pc_menus_root_name(void);
if (!ui_widget_load_by_name_or_tag(pc_menus_root_name(), NONE, NULL, NONE, NONE, NONE, NONE))
error(_error_silent, "failed to load main screen shell window");
}
if (widget_globals.main_menu_deferred_error_code != NONE)
{
display_error(widget_globals.main_menu_deferred_error_code, NONE, TRUE, FALSE);
widget_globals.main_menu_deferred_error_code = NONE;
}
if (!widget_globals.main_menu_music_active)
ui_start_main_menu_music();
reset_last_player1_profile_index();
}
if (!virtual_keyboard_initialize())
error(_error_silent, "failed to initialize the virtual keyboard");
main_screen_shell_first_load = FALSE;
return;
}
static void widget_instance_reload_recursive(
struct widget_instance *widget)
{
return;
}
static void ui_widget_reload_by_tag(
long tag_index)
{
return;
}
real_rgb_color get_ui_rgb_white(
void)
{
real_rgb_color result;
result = *global_real_rgb_white;
result.red = global_ui_white_red;
result.green = global_ui_white_green;
result.blue = global_ui_white_blue;
return result;
}
real_argb_color get_ui_argb_white(
void)
{
real_argb_color result;
result = *global_real_argb_white;
result.red = global_ui_white_red;
result.green = global_ui_white_green;
result.blue = global_ui_white_blue;
return result;
}
static __inline real widget_instance_get_cumulative_alpha_modifier(
struct widget_instance *widget)
{
real alpha_modifier = widget->alpha_modifier;
widget = widget->parent;
while (widget)
{
alpha_modifier *= widget->alpha_modifier;
widget = widget->parent;
}
return alpha_modifier;
}
static boolean widget_instance_text_box_is_focused(
struct widget_instance *widget)
{
struct widget_instance *parent = widget->parent;
boolean focused = parent ? parent->focused_child == widget : TRUE;
if (!focused && parent)
{
struct widget_instance *ancestor;
do
{
ancestor = parent->parent;
if (ancestor)
{
if (ancestor->focused_child != parent)
break;
focused = (ancestor->type == _ui_widget_type_spinner_list ||
ancestor->type == _ui_widget_type_column_list) &&
ancestor->focused_child == parent;
}
parent = ancestor;
}
while (ancestor);
}
return focused;
}
static boolean string_has_icons_to_draw(
wchar_t *string)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4181,
string);
while (string)
{
wchar_t *icon_spec = wcschr(string, L'%');
if (!icon_spec)
break;
icon_spec++;
if (get_icon_type(icon_spec) != NONE)
return TRUE;
string = icon_spec;
}
return FALSE;
}
static long search_and_replace(
wchar_t *search,
wchar_t *replace,
wchar_t **string)
{
long replacements = 0;
if (string && *string)
{
wchar_t *buffer = *string;
long search_length = ustrlen(search);
long replace_length = ustrlen(replace);
long length = ustrlen(buffer) + 1;
long delta;
wchar_t *match;
if (replace_length <= search_length)
{
delta = search_length - replace_length;
match = ustrstr(buffer, search);
if (match)
{
do
{
replacements++;
csmemcpy(match, replace, 2 * replace_length);
if (delta > 0)
{
csmemmove(
&match[replace_length],
&match[search_length],
2 * (length - (match - buffer) - replace_length));
length -= delta;
}
match = ustrstr(buffer, search);
}
while (match);
}
}
else
{
delta = replace_length - search_length;
for (match = ustrstr(buffer, search);
match;
match = ustrstr(&match[search_length], search))
{
replacements++;
}
if (replacements > 0)
{
buffer = pool_resize_pointer(
widget_memory_pool,
buffer,
2 * (delta * replacements + length),
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4994);
if (!buffer)
{
replacements = NONE;
}
else
{
for (match = ustrstr(buffer, search);
match;
match = ustrstr(buffer, search))
{
csmemmove(
&match[replace_length],
&match[search_length],
2 * (length - (match - buffer) - search_length));
csmemcpy(match, replace, 2 * replace_length);
length += delta;
}
*string = buffer;
}
}
}
}
return replacements;
}
static void widget_instance_render_text_box(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus)
{
wchar_t **text;
long search_index;
long font_index;
short justification;
real alpha_modifier;
real color_alpha;
real_argb_color color;
rectangle2d bounds;
rectangle2d clip;
if (definition->text_label_string_list.index != NONE)
{
short string_list_index;
wchar_t *string;
unsigned long length;
if (widget->parameters.text_box.string_list_index == NONE)
string_list_index = definition->string_list_index;
else
string_list_index = widget->parameters.text_box.string_list_index;
string = widget == ui_widget_port_error_text_box && ui_widget_port_error_text ?
(wchar_t *)ui_widget_port_error_text :
unicode_string_list_get_string(definition->text_label_string_list.index, string_list_index);
/* port: the description of a spinner's extra item */
if (string_list_index >= SPINNER_EXTRA_DESCRIPTION_BASE &&
string_list_index < SPINNER_EXTRA_DESCRIPTION_BASE + (short)NUMBEROF(kills_to_win_extra_descriptions))
{
string = (wchar_t *)kills_to_win_extra_descriptions[string_list_index - SPINNER_EXTRA_DESCRIPTION_BASE];
}
length = ustrlen(string);
widget->parameters.text_box.text = pool_resize_pointer(
widget_memory_pool,
widget->parameters.text_box.text,
2 * length + 2,
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4421);
if (widget->parameters.text_box.text)
{
csmemcpy(widget->parameters.text_box.text, string, 2 * length);
widget->parameters.text_box.text[length] = 0;
}
else
{
widget->parameters.text_box.text = L"<out of memory>";
}
}
text = &widget->parameters.text_box.text;
if (!*text || !**text)
return;
for (search_index = 0;
search_index < definition->search_and_replace_functions.count;
search_index++)
{
struct ui_widget_search_and_replace_reference *reference =
(struct ui_widget_search_and_replace_reference *)
definition->search_and_replace_functions.address +
search_index;
if (reference && reference->search_string[0])
{
wchar_t search_string[32];
wchar_t *replace = ui_widget_search_and_replace_invoke(
widget,
reference->replace_function);
search_and_replace(
ascii_to_wide(
reference->search_string,
search_string,
sizeof(search_string)),
replace,
text);
}
}
font_index = definition->text_font.index;
if (font_index == NONE)
{
error(
_error_silent,
"failed to render text box widget because the font tag was invalid");
return;
}
justification = definition->justification;
if (justification < 0 || justification >= NUMBER_OF_TEXT_JUSTIFICATIONS)
{
error(
_error_silent,
"failed to render text box widget because the justification was invalid");
return;
}
if (!widget->visible)
return;
alpha_modifier = widget_instance_get_cumulative_alpha_modifier(widget);
bounds = definition->bounds;
clip = clip_rect ? *clip_rect : definition->bounds;
bounds.x1 += offset.x;
bounds.y1 += offset.y;
bounds.x0 += offset.x;
bounds.y0 += offset.y;
bounds.x0 += definition->horizontal_offset;
bounds.y0 += definition->vertical_offset;
if (focus)
{
color = get_ui_argb_white();
color_alpha = definition->text_color.alpha;
}
else
{
color = definition->text_color;
if (1.0f == color.red && 1.0f == color.green && 1.0f == color.blue)
{
color = get_ui_argb_white();
color_alpha = definition->text_color.alpha;
}
else
{
color_alpha = color.alpha;
}
}
color.alpha = alpha_modifier * color_alpha;
if (TEST_FLAG(definition->text_box_flags, _text_box_flashing_text_bit))
{
color.alpha = (((real)cos(
widget_globals.current_system_milliseconds *
SECONDS_PER_MILLISECOND * 3.0f) + 1.5f) * 0.4f) * color.alpha;
}
draw_string_set_draw_mode(font_index, NONE, justification, 0, &color);
if (string_has_icons_to_draw(*text))
draw_string_and_hack_in_icons(&bounds, &clip, NULL, 0, *text, FALSE);
else
rasterizer_draw_unicode_string(&bounds, &clip, NULL, 0, *text);
return;
}
static void widget_instance_render_spinner_list(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus)
{
long header_frame_index = 0;
long footer_frame_index = 0;
real alpha_modifier = widget_instance_get_cumulative_alpha_modifier(widget);
short last_list_tab_direction;
struct bitmap_data *bitmap;
rectangle2d bounds;
if (!widget->visible)
return;
last_list_tab_direction = widget->parameters.list.last_list_tab_direction;
if (last_list_tab_direction)
{
switch (SIGN(last_list_tab_direction))
{
case -1:
widget->parameters.list.last_list_tab_direction =
last_list_tab_direction + 1;
header_frame_index = 1;
break;
case 1:
widget->parameters.list.last_list_tab_direction =
last_list_tab_direction - 1;
footer_frame_index = 1;
break;
}
}
bitmap = bitmap_group_get_bitmap_from_sequence(
definition->list_header_bitmap.index,
0,
header_frame_index);
if (bitmap)
{
struct rasterizer_dynamic_screen_geometry_parameters parameters;
long alpha = fast_ftol(alpha_modifier * 255.0f);
csmemset(&parameters, 0, sizeof(parameters));
bounds = definition->list_header_bounds;
/* port: a spinner with extra items is wider, to the left, its arrow
with it */
if (spinner_string_list_extra_count(definition->text_label_string_list.index))
{
bounds.x0 -= SPINNER_EXTRA_WIDTH;
bounds.x1 -= SPINNER_EXTRA_WIDTH;
}
bounds.x0 += offset.x;
bounds.y0 += offset.y;
bounds.x1 += offset.x;
bounds.y1 += offset.y;
draw_bitmap_in_rect(
bitmap,
&bounds,
&bounds,
clip_rect,
(alpha << 24) | 0x00FFFFFF,
&parameters,
FALSE);
}
bitmap = bitmap_group_get_bitmap_from_sequence(
definition->list_footer_bitmap.index,
0,
footer_frame_index);
if (bitmap)
{
struct rasterizer_dynamic_screen_geometry_parameters parameters;
long alpha = fast_ftol(alpha_modifier * 255.0f);
csmemset(&parameters, 0, sizeof(parameters));
bounds = definition->list_footer_bounds;
bounds.x0 += offset.x;
bounds.y0 += offset.y;
bounds.x1 += offset.x;
bounds.y1 += offset.y;
draw_bitmap_in_rect(
bitmap,
&bounds,
&bounds,
clip_rect,
(alpha << 24) | 0x00FFFFFF,
&parameters,
FALSE);
}
if (definition->child_widgets.count == 0)
{
wchar_t *item_text;
if (definition->text_label_string_list.index != NONE)
{
short string_index = widget->parameters.list.selected_index;
wchar_t *string = spinner_string_list_get_string(
definition->text_label_string_list.index,
string_index);
unsigned long length = ustrlen(string);
item_text = pool_new_pointer(
widget_memory_pool,
2 * length + 2,
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
4610);
if (item_text)
{
long search_index;
csmemcpy(item_text, string, 2 * length);
item_text[length] = 0;
for (search_index = 0;
search_index < definition->search_and_replace_functions.count;
search_index++)
{
struct ui_widget_search_and_replace_reference *reference =
(struct ui_widget_search_and_replace_reference *)
definition->search_and_replace_functions.address +
search_index;
if (reference && reference->search_string[0])
{
wchar_t search_string[32];
wchar_t *replace = ui_widget_search_and_replace_invoke(
widget,
reference->replace_function);
search_and_replace(
ascii_to_wide(
reference->search_string,
search_string,
sizeof(search_string)),
replace,
&item_text);
}
}
}
}
else
{
item_text = widget->parameters.list.item_text;
}
if (item_text)
{
if (definition->text_font.index == NONE)
{
error(
_error_silent,
"failed to render spinner list item because the font tag was invalid");
}
else if (definition->justification < 0 ||
definition->justification >= NUMBER_OF_TEXT_JUSTIFICATIONS)
{
error(
_error_silent,
"failed to render spinner list item because the justification was invalid");
}
else
{
real text_alpha_modifier =
widget_instance_get_cumulative_alpha_modifier(widget);
real color_alpha;
real_argb_color color;
rectangle2d clip = clip_rect ? *clip_rect : definition->bounds;
bounds = definition->bounds;
bounds.x1 += offset.x;
bounds.y1 += offset.y;
bounds.x0 += offset.x;
bounds.y0 += offset.y;
/* port: a spinner with extra items (three digits) is wider, to the
left */
if (spinner_string_list_extra_count(definition->text_label_string_list.index))
{
bounds.x0 -= SPINNER_EXTRA_WIDTH;
clip.x0 -= SPINNER_EXTRA_WIDTH;
}
if (focus)
{
color.alpha = definition->text_color.alpha;
color.rgb = get_ui_rgb_white();
color_alpha = color.alpha;
}
else
{
color = definition->text_color;
if (1.0f == color.red &&
1.0f == color.green &&
1.0f == color.blue)
{
color_alpha = definition->text_color.alpha;
}
else
{
color_alpha = color.alpha;
}
}
color.alpha = text_alpha_modifier * color_alpha;
if (TEST_FLAG(
definition->text_box_flags,
_text_box_flashing_text_bit))
{
color.alpha = (((real)sin(
widget_globals.current_system_milliseconds *
SECONDS_PER_MILLISECOND * 3.0f) + 1.0f) * 0.5f) *
color.alpha;
}
draw_string_set_draw_mode(
definition->text_font.index,
NONE,
definition->justification,
0,
&color);
rasterizer_draw_unicode_string(&bounds, &clip, NULL, 0, item_text);
}
}
if (definition->text_label_string_list.index != NONE)
dispose_pointer(widget_memory_pool, item_text);
}
return;
}
/* ---------- the mouse (desktop builds)
The menus were made for a controller: the d-pad moves the focus through a
screen's items and A activates the focused one. With the mouse
(port/linux/include/halo_ui_pointer.h) the item under the pointer takes the
focus, a left click presses A on it (on a spinner list, left or right by the
half clicked), a right click presses B and the wheel the d-pad. The items and
where they are drawn are noted while the menus draw (ui_mouse_note_target),
for the next frame's processing (ui_widgets_process_mouse), which forgets
them: widgets can be deleted from then on. */
#define UI_MOUSE_MAXIMUM_TARGETS 96
enum ui_mouse_target_kind
{
/* an item the d-pad moves the focus to: a click presses A on it */
_ui_mouse_target_item,
/* a list showing one value at a time: a click on either half steps it
that way */
_ui_mouse_target_value,
/* one of the items a list shows side by side (profiles, levels): the
list steps to it, and a click then presses A */
_ui_mouse_target_list_slot,
/* a button's icon and label in a screen's key: a click presses it */
_ui_mouse_target_button
};
struct ui_mouse_target
{
struct widget_instance *widget;
rectangle2d bounds;
short kind;
short button_index;
/* (a button's: where its icon begins) */
short icon_x0;
};
static struct ui_mouse_target ui_mouse_targets[UI_MOUSE_MAXIMUM_TARGETS];
static long ui_mouse_target_count = 0;
static boolean ui_mouse_noting_targets = FALSE;
/* The presses the mouse makes, posted one a frame: the event queue keeps
only the latest event posted between two frames (queue_event). What they
lead to (the focus, a list's position) is only known once they have been
processed, so the pointer's next hover or click waits for them. */
#define UI_MOUSE_MAXIMUM_PRESSES 16
static short ui_mouse_presses[UI_MOUSE_MAXIMUM_PRESSES];
static long ui_mouse_press_count = 0;
static boolean ui_mouse_hover_pending = FALSE;
static boolean ui_mouse_click_pending = FALSE;
static short ui_mouse_hover_x, ui_mouse_hover_y;
static short ui_mouse_click_x, ui_mouse_click_y;
static void ui_mouse_press(
short button_index)
{
if (ui_mouse_press_count < UI_MOUSE_MAXIMUM_PRESSES)
ui_mouse_presses[ui_mouse_press_count++] = button_index;
return;
}
/* the widget's place among its parent's children, or NONE */
static long ui_mouse_child_index(
struct widget_instance *widget)
{
struct widget_instance *child;
long index = 0;
for (child = widget->parent ? widget->parent->child : NULL; child; child = child->next, index++)
{
if (child == widget)
return index;
}
return NONE;
}
/* a list that shows several of its items at once */
static boolean ui_mouse_list_shows_several(
struct widget_instance *widget)
{
return widget->type == _ui_widget_type_spinner_list &&
ui_widget_definition_get(widget->definition_tag_index)->child_widgets.count > 1;
}
/* the button a screen key's icon stands for ("a_butn" and so on), or NONE */
static short ui_mouse_key_button(
struct widget_instance *widget)
{
static struct
{
char const *name;
short button_index;
} const keys[] =
{
{ "a_butn", _gamepad_analog_button_a },
{ "b_butn", _gamepad_analog_button_b },
{ "x_butn", _gamepad_analog_button_x },
{ "y_butn", _gamepad_analog_button_y },
{ "black_butn", _gamepad_analog_button_black },
{ "white_butn", _gamepad_analog_button_white },
{ "start_butn", _gamepad_binary_button_start },
{ "back_butn", _gamepad_binary_button_back },
};
char const *name = tag_get_name(widget->definition_tag_index);
char const *leaf = name ? strrchr(name, '\\') : NULL;
long key_index;
leaf = leaf ? leaf + 1 : name;
for (key_index = 0; leaf && key_index < NUMBEROF(keys); key_index++)
{
long length = (long)strlen(keys[key_index].name);
/* and the smaller icons of the dialogs, "a_butn_sm" */
if (!strncmp(leaf, keys[key_index].name, length) &&
(leaf[length] == '\0' || leaf[length] == '_'))
{
return keys[key_index].button_index;
}
}
return NONE;
}
/* whether the d-pad moves the focus to a widget: an item of a column list,
or a child its parent tabs through */
static boolean ui_mouse_widget_is_item(
struct widget_instance *widget)
{
struct widget_instance *parent = widget->parent;
struct ui_widget_definition *definition;
long index;
if (!parent || parent->type == _ui_widget_type_spinner_list)
return FALSE;
index = ui_mouse_child_index(widget);
if (index == NONE || !widget_instance_can_receive_events(widget))
return FALSE;
if (parent->type == _ui_widget_type_column_list)
{
return !parent->parameters.list.list_items ||
index < parent->parameters.list.number_of_items;
}
definition = ui_widget_definition_get(parent->definition_tag_index);
if (!TEST_FLAG(definition->flags, _widget_dpad_updown_tabs_thru_children_bit) &&
!TEST_FLAG(definition->flags, _widget_dpad_leftright_tabs_thru_children_bit))
{
return FALSE;
}
definition = ui_widget_definition_get(widget->definition_tag_index);
return definition->event_handlers.count > 0 ||
TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list;
}
static void ui_mouse_note_target(
struct widget_instance *widget,
struct ui_widget_definition const *definition,
point2d offset)
{
struct widget_instance *parent = widget->parent;
struct ui_mouse_target *target;
rectangle2d bounds = definition->bounds;
short button_index;
short kind;
short icon_x0;
if (!ui_mouse_noting_targets ||
ui_mouse_target_count >= UI_MOUSE_MAXIMUM_TARGETS ||
widget->disabled)
{
return;
}
bounds.x0 += offset.x;
bounds.x1 += offset.x;
bounds.y0 += offset.y;
bounds.y1 += offset.y;
button_index = ui_mouse_key_button(widget);
icon_x0 = bounds.x0;
if (button_index != NONE)
{
/* the icon and its label: the text beginning just right of it */
struct widget_instance *sibling;
rectangle2d label_bounds;
short best_distance = 17;
#ifdef HALO_ANDROID
/* (VR: a controller points less steadily than a mouse: the label a
little further off is the button's too) */
short most_distance = halo_vr_active() ? 25 : 17;
#else
short most_distance = 17;
#endif
best_distance = most_distance;
kind = _ui_mouse_target_button;
for (sibling = parent ? parent->child : NULL; sibling; sibling = sibling->next)
{
struct ui_widget_definition *sibling_definition;
rectangle2d sibling_bounds;
short distance;
if (sibling->type != _ui_widget_type_text_box || !sibling->visible)
continue;
sibling_definition = ui_widget_definition_get(sibling->definition_tag_index);
sibling_bounds = sibling_definition->bounds;
sibling_bounds.x0 += offset.x - widget->horizontal_offset + sibling->horizontal_offset;
sibling_bounds.x1 += offset.x - widget->horizontal_offset + sibling->horizontal_offset;
sibling_bounds.y0 += offset.y - widget->vertical_offset + sibling->vertical_offset;
sibling_bounds.y1 += offset.y - widget->vertical_offset + sibling->vertical_offset;
distance = (short)ABS(sibling_bounds.x0 - bounds.x1);
if (distance < best_distance &&
sibling_bounds.y0 < bounds.y1 && sibling_bounds.y1 > bounds.y0)
{
best_distance = distance;
label_bounds = sibling_bounds;
}
}
if (best_distance < most_distance)
{
bounds.x1 = MAX(bounds.x1, label_bounds.x1);
bounds.y0 = MIN(bounds.y0, label_bounds.y0);
bounds.y1 = MAX(bounds.y1, label_bounds.y1);
}
#ifdef HALO_ANDROID
/* (and a margin about it: above and below, little to the sides,
where the next key is) */
if (halo_vr_active())
{
bounds.x0 -= 2;
bounds.x1 += 2;
bounds.y0 -= 4;
bounds.y1 += 4;
}
#endif
}
else if (parent && ui_mouse_list_shows_several(parent))
{
if (parent->disabled ||
ui_mouse_child_index(widget) == NONE ||
!widget_instance_can_receive_events(parent))
{
return;
}
kind = _ui_mouse_target_list_slot;
}
else if (widget->type == _ui_widget_type_spinner_list)
{
/* a list showing several items is picked through them */
if (!parent || ui_mouse_list_shows_several(widget) || !widget_instance_can_receive_events(widget))
return;
kind = _ui_mouse_target_value;
/* port: its arrows, drawn just outside it (the PC version's
spinners' header and footer bitmaps), its too */
bounds.x0 -= 12;
bounds.x1 += 12;
}
else if (ui_mouse_widget_is_item(widget))
{
kind = _ui_mouse_target_item;
}
else
{
return;
}
target = &ui_mouse_targets[ui_mouse_target_count++];
target->widget = widget;
target->bounds = bounds;
target->kind = kind;
target->button_index = button_index;
target->icon_x0 = icon_x0;
return;
}
/* the target drawn last (so on top, and the innermost) under a point */
static struct ui_mouse_target *ui_mouse_target_at(
short x,
short y)
{
long index;
struct ui_mouse_target *button = NULL;
for (index = ui_mouse_target_count - 1; index >= 0; index--)
{
struct ui_mouse_target *target = &ui_mouse_targets[index];
rectangle2d const *bounds = &target->bounds;
if (!(x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1))
continue;
/* port: a key's label's text box can be far wider than its text, over
the next key: of keys under the point, the one whose icon is nearest
on its left (each key from its icon to the next one's) */
if (target->kind == _ui_mouse_target_button)
{
if (!button ||
(target->icon_x0 <= x + 2 && (button->icon_x0 > x + 2 || target->icon_x0 > button->icon_x0)))
{
button = target;
}
continue;
}
if (!button)
return target;
break;
}
return button;
}
static boolean ui_mouse_widget_has_focus(
struct widget_instance *widget)
{
for (; widget->parent; widget = widget->parent)
{
if (widget->parent->focused_child != widget)
return FALSE;
}
return TRUE;
}
/* moves the focus to the widget as the d-pad would, with the selection of
the column lists on the way (widget_event_function_list_widget_goto_next_item) */
static void ui_mouse_give_focus(
struct widget_instance *widget)
{
struct widget_instance *ancestor;
if (ui_mouse_widget_has_focus(widget))
return;
widget_instance_give_focus_directly(widget_instance_get_topmost_parent(widget), widget);
for (ancestor = widget; ancestor->parent; ancestor = ancestor->parent)
{
if (ancestor->parent->type == _ui_widget_type_column_list)
ancestor->parent->parameters.list.selected_index = (short)ui_mouse_child_index(ancestor);
}
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
return;
}
/* the d-pad buttons that step a widget back and forward */
static void ui_mouse_list_directions(
struct widget_instance *widget,
short *back,
short *forward)
{
struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index);
if (TEST_FLAG(definition->flags, _widget_dpad_leftright_tabs_thru_list_items_bit) ||
TEST_FLAG(definition->flags, _widget_dpad_leftright_tabs_thru_children_bit))
{
*back = _widget_event_dpad_left;
*forward = _widget_event_dpad_right;
}
else
{
*back = _widget_event_dpad_up;
*forward = _widget_event_dpad_down;
}
return;
}
#ifdef HALO_ANDROID
/* VR: a click beside a list's slots or a value's box, along the way it
steps, where its arrows are drawn: a step back or on (TRUE: taken) */
static boolean ui_mouse_click_beside_list(
short x,
short y)
{
struct widget_instance *list = NULL;
rectangle2d bounds;
long index;
short back, forward;
boolean across;
for (index = 0; index < ui_mouse_target_count; index++)
{
struct ui_mouse_target const *target = &ui_mouse_targets[index];
struct widget_instance *owner = target->kind == _ui_mouse_target_list_slot ? target->widget->parent :
target->kind == _ui_mouse_target_value ? target->widget : NULL;
if (!owner)
continue;
if (!list)
{
list = owner;
bounds = target->bounds;
}
else if (owner == list)
{
bounds.x0 = MIN(bounds.x0, target->bounds.x0);
bounds.y0 = MIN(bounds.y0, target->bounds.y0);
bounds.x1 = MAX(bounds.x1, target->bounds.x1);
bounds.y1 = MAX(bounds.y1, target->bounds.y1);
}
}
if (!list)
return FALSE;
ui_mouse_list_directions(list, &back, &forward);
across = back == _widget_event_dpad_left;
if (across ? y < bounds.y0 - 24 || y >= bounds.y1 + 24 : x < bounds.x0 - 24 || x >= bounds.x1 + 24)
return FALSE;
ui_mouse_give_focus(list);
if (across ? x < bounds.x0 && x >= bounds.x0 - 80 : y < bounds.y0 && y >= bounds.y0 - 48)
ui_mouse_press(back);
else if (across ? x >= bounds.x1 && x < bounds.x1 + 80 : y >= bounds.y1 && y < bounds.y1 + 48)
ui_mouse_press(forward);
else
return FALSE;
return TRUE;
}
#endif
/* steps a list that shows several items to the one shown in a slot, as
pressing the d-pad that many times would */
static void ui_mouse_step_list_to_slot(
struct widget_instance *slot)
{
struct widget_instance *list = slot->parent;
long steps;
short back, forward;
ui_mouse_give_focus(list);
if (!list->focused_child)
return;
steps = ui_mouse_child_index(slot) - ui_mouse_child_index(list->focused_child);
ui_mouse_list_directions(list, &back, &forward);
for (; steps > 0; steps--)
ui_mouse_press(forward);
for (; steps < 0; steps++)
ui_mouse_press(back);
return;
}
/* the widget the wheel steps: the innermost on the focus's way that the
d-pad steps through */
static struct widget_instance *ui_mouse_wheel_widget(
struct widget_instance *root)
{
struct widget_instance *result = NULL;
struct widget_instance *widget;
for (widget = root; widget; widget = widget->focused_child)
{
struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index);
if (definition->flags & (FLAG(_widget_dpad_updown_tabs_thru_children_bit) |
FLAG(_widget_dpad_leftright_tabs_thru_children_bit) |
FLAG(_widget_dpad_updown_tabs_thru_list_items_bit) |
FLAG(_widget_dpad_leftright_tabs_thru_list_items_bit)))
{
result = widget;
}
}
return result;
}
/* the menu the mouse drives: the first player's, or everyone's */
static struct widget_instance *ui_mouse_menu(
void)
{
long widget_index;
for (widget_index = 0; widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; widget_index++)
{
struct widget_instance *widget = widget_globals.active_widgets[widget_index];
if (widget && (widget->local_player_index == NONE || widget->local_player_index == 0))
return widget;
}
return NULL;
}
static boolean ui_mouse_menus_active(
void)
{
if (widget_globals.initialization_thread || progress_bar_is_active())
return FALSE;
/* (and the scores after a game, which take A and B like the menus:
game_engine_update_non_deterministic) */
return virtual_keyboard_active() || ui_mouse_menu() != NULL || game_engine_showing_postgame();
}
#ifdef HALO_ANDROID
/* ---------- join codes (the VR port's)
Internet play's join codes (port/linux/src/p2p_code.c) and public games
(p2p_list.c): the host's lobby shows its game's code, and Y there lists the
game for anyone or not; on the screen of games to join, X opens a panel of
the public games and a way to type a code in, either of which then joins as
an invite link does (the game shows up in the list once its host is
reached). */
void platform_log(char const *format, ...);
int p2p_join_code(char *text, int size);
void halo_vr_pulse_pad_a(void);
int p2p_join_with_code(char const *typed);
int p2p_join_code_status(void);
void p2p_join_code_clear_status(void);
int p2p_join_state(void);
enum
{
/* p2p.h's */
_join_code_idle,
_join_code_looking,
_join_code_found,
_join_code_not_found,
_join_code_invalid,
_join_code_own,
_join_code_offline,
};
enum
{
/* p2p.h's */
_join_state_none,
_join_state_reaching,
_join_state_connected,
_join_state_no_answer,
_join_state_blocked,
_join_state_lost,
};
struct ui_public_game
{
/* p2p.h's struct p2p_public_game */
char name[16];
char map[32];
char mode[24];
char code[12];
int players;
int maximum_players;
/* (a game of the server browser's: no code, its invite) */
char invite[77];
};
void p2p_set_public_game(int public_game);
int p2p_join_invite(char const *text);
int p2p_public_game(void);
int p2p_public_games(struct ui_public_game *games, int maximum);
long config_integer(char const *name);
int config_write_integer(char const *name, long value);
enum
{
MAXIMUM_SHOWN_PUBLIC_GAMES = 32,
/* the online games panel's rows: "enter a code" and the games */
ONLINE_PANEL_ROWS = 11,
/* (the rows before the games: "enter a code") */
ONLINE_PANEL_FIXED_ROWS = 1,
};
static struct
{
boolean keyboard_launched;
wchar_t typed[12];
/* the panel of online games, over the screen of games to join */
boolean panel_open;
short selection;
short scroll;
short game_count;
struct ui_public_game games[MAXIMUM_SHOWN_PUBLIC_GAMES];
/* the host options panel, over a hosted game's lobby */
boolean host_panel_open;
short host_selection;
} ui_join_code_globals;
enum
{
/* the host options: public, bots, their skill */
HOST_PANEL_ROWS = 3,
MAXIMUM_BOTS_SHOWN = 15,
};
/* the multiplayer maps' names, as the menus show them */
static char const *ui_join_code_map_name(
char const *map)
{
static char const *const names[][2] = {
{ "beavercreek", "Battle Creek" }, { "bloodgulch", "Blood Gulch" }, { "boardingaction", "Boarding Action" },
{ "carousel", "Derelict" }, { "chillout", "Chill Out" }, { "damnation", "Damnation" },
{ "dangercanyon", "Danger Canyon" }, { "deathisland", "Death Island" }, { "gephyrophobia", "Gephyrophobia" },
{ "hangemhigh", "Hang 'Em High" }, { "icefields", "Ice Fields" }, { "infinity", "Infinity" },
{ "longest", "Longest" }, { "prisoner", "Prisoner" }, { "putput", "Chiron TL-34" },
{ "ratrace", "Rat Race" }, { "sidewinder", "Sidewinder" }, { "timberland", "Timberland" },
{ "wizard", "Wizard" },
};
short index;
for (index = 0; index < (short)NUMBEROF(names); index++)
{
if (!strcmp(map, names[index][0]))
return names[index][1];
}
return map;
}
static boolean ui_join_code_screen_active(
char const *name)
{
return widget_globals.active_widgets[0] && widget_globals.active_widgets[0]->name &&
strcmp(widget_globals.active_widgets[0]->name, name) == 0;
}
/* the screens the panels go over: the Xbox's, or the PC version's
(vr.menus, port/linux/game/menu_tags.c) */
static boolean ui_join_code_pc_screens(
void)
{
return ui_join_code_screen_active("join_game_screen") || ui_join_code_screen_active("lobby_screen");
}
static boolean ui_join_code_join_screen(
void)
{
return ui_join_code_screen_active("server_list_screen") || ui_join_code_screen_active("join_game_screen");
}
static boolean ui_join_code_lobby_screen(
void)
{
return ui_join_code_screen_active("connected_pregame_screen") || ui_join_code_screen_active("lobby_screen");
}
static void ui_join_code_launch_keyboard(
void)
{
ui_join_code_globals.typed[0] = 0;
virtual_keyboard_set_caption(L"Enter the join code of an online game");
if (!virtual_keyboard_launch(ui_join_code_globals.typed, sizeof(ui_join_code_globals.typed), 8))
{
virtual_keyboard_set_caption(NULL);
return;
}
p2p_join_code_clear_status();
ui_join_code_globals.keyboard_launched = TRUE;
}
/* the panel's input: the stick or the pad's arrows choose a row, A takes
it, B closes it */
static void ui_join_code_panel_event(
struct event_record const *event)
{
short rows = (short)(ui_join_code_globals.game_count + 1);
short move = 0;
if (event->type == _event_type_left_stick)
{
if (event->data.stick.y == SHORT_MAX)
move = -1;
else if (event->data.stick.y == SHORT_MIN)
move = 1;
}
else if (event->type == _event_type_button && event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _gamepad_binary_button_dpad_up: move = -1; break;
case _gamepad_binary_button_dpad_down: move = 1; break;
case _gamepad_analog_button_b:
case _gamepad_binary_button_back:
ui_join_code_globals.panel_open = FALSE;
break;
case _gamepad_analog_button_x:
ui_join_code_globals.panel_open = FALSE;
ui_join_code_launch_keyboard();
break;
case _gamepad_analog_button_a:
case _gamepad_binary_button_start:
ui_join_code_globals.panel_open = FALSE;
if (ui_join_code_globals.selection == 0 ||
ui_join_code_globals.selection > ui_join_code_globals.game_count)
{
ui_join_code_launch_keyboard();
}
else
{
struct ui_public_game const *game =
&ui_join_code_globals.games[ui_join_code_globals.selection - ONLINE_PANEL_FIXED_ROWS];
p2p_join_code_clear_status();
/* (a game of the server browser's: by its invite) */
if (game->code[0])
p2p_join_with_code(game->code);
else
p2p_join_invite(game->invite);
}
break;
}
}
if (move)
{
ui_join_code_globals.selection = (short)PIN(ui_join_code_globals.selection + move, 0, rows - 1);
if (ui_join_code_globals.selection < ui_join_code_globals.scroll)
ui_join_code_globals.scroll = ui_join_code_globals.selection;
else if (ui_join_code_globals.selection >= ui_join_code_globals.scroll + ONLINE_PANEL_ROWS)
ui_join_code_globals.scroll = (short)(ui_join_code_globals.selection - ONLINE_PANEL_ROWS + 1);
}
}
long network_game_server_port_bot_count(void);
/* the host options panel's input: the stick or the pad's arrows up and
down choose a row, left and right (or A) change it, B or Y closes it */
static void ui_join_code_host_panel_event(
struct event_record const *event)
{
short move = 0;
short change = 0;
if (event->type == _event_type_left_stick)
{
if (event->data.stick.y == SHORT_MAX)
move = -1;
else if (event->data.stick.y == SHORT_MIN)
move = 1;
else if (event->data.stick.x == SHORT_MAX)
change = 1;
else if (event->data.stick.x == SHORT_MIN)
change = -1;
}
else if (event->type == _event_type_button && event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _gamepad_binary_button_dpad_up: move = -1; break;
case _gamepad_binary_button_dpad_down: move = 1; break;
case _gamepad_binary_button_dpad_right:
case _gamepad_analog_button_a:
change = 1;
break;
case _gamepad_binary_button_dpad_left: change = -1; break;
case _gamepad_analog_button_b:
case _gamepad_analog_button_y:
case _gamepad_binary_button_back:
ui_join_code_globals.host_panel_open = FALSE;
break;
}
}
if (move)
ui_join_code_globals.host_selection = (short)PIN(ui_join_code_globals.host_selection + move, 0, HOST_PANEL_ROWS - 1);
if (!change)
return;
switch (ui_join_code_globals.host_selection)
{
case 0:
p2p_set_public_game(!p2p_public_game());
break;
case 1:
{
/* (from the bots there are, which the game's room may hold below
bots.count; A goes round: past the most there is room for, none) */
long before = network_game_server_port_bot_count();
long count = PIN(before + change, 0, MAXIMUM_BOTS_SHOWN);
config_write_integer("bots.count", count);
if (change > 0 && network_game_server_port_bot_count() <= before)
{
count = event->type == _event_type_button && event->data.button.index == _gamepad_analog_button_a ? 0 :
before;
config_write_integer("bots.count", count);
}
break;
}
case 2:
{
long skill = config_integer("bots.skill") + change;
if (skill > 2)
skill = event->type == _event_type_button && event->data.button.index == _gamepad_analog_button_a ? 0 : 2;
config_write_integer("bots.skill", PIN(skill, 0, 2));
break;
}
}
}
/* X on the screen of games to join: the panel of online games (and in it,
everything); Y in a hosted game's lobby: the host options (public, bots).
TRUE: the event is taken. */
static boolean ui_join_code_take_event(
struct widget_instance *widget,
struct event_record const *event)
{
if (widget != widget_globals.active_widgets[0])
return FALSE;
if (ui_join_code_globals.host_panel_open)
{
if (!ui_join_code_lobby_screen() || !global_network_game_server_get())
{
ui_join_code_globals.host_panel_open = FALSE;
return FALSE;
}
ui_join_code_host_panel_event(event);
return TRUE;
}
if (ui_join_code_globals.panel_open)
{
if (!ui_join_code_join_screen())
{
ui_join_code_globals.panel_open = FALSE;
return FALSE;
}
ui_join_code_panel_event(event);
return TRUE;
}
if (event->type != _event_type_button || event->data.button.value != 1)
return FALSE;
if (event->data.button.index == _gamepad_analog_button_x && ui_join_code_join_screen())
{
ui_join_code_globals.panel_open = TRUE;
ui_join_code_globals.selection = 0;
ui_join_code_globals.scroll = 0;
return TRUE;
}
if (event->data.button.index == _gamepad_analog_button_y &&
ui_join_code_lobby_screen() && global_network_game_server_get())
{
ui_join_code_globals.host_panel_open = TRUE;
ui_join_code_globals.host_selection = 0;
return TRUE;
}
return FALSE;
}
/* the pointer over the VR port's panels: the row under it chosen, and a
click on it as A (TRUE: a panel is open and took it) */
static boolean ui_join_code_take_pointer(
struct halo_ui_pointer const *pointer)
{
struct event_record press;
short row;
/* (a click where it was clicked, which may be a moment before) */
short x = pointer->left_clicks ? pointer->click_x : pointer->x;
short y = pointer->left_clicks ? pointer->click_y : pointer->y;
csmemset(&press, 0, sizeof(press));
press.type = _event_type_button;
press.data.button.index = _gamepad_analog_button_a;
press.data.button.value = 1;
/* (the screen where controllers join by their A: a click is the
pad's A, there, not a menu's) */
if (pointer->left_clicks && y < 400 && ui_join_code_screen_active("4way_start2join_screen"))
{
halo_vr_pulse_pad_a();
return TRUE;
}
if (ui_join_code_globals.panel_open)
{
/* (a click beside the panel, or on its key's B, closes it; on its
key's X, a code to enter, on its A, as A) */
if (pointer->left_clicks && (x < 40 || x >= 600 || y < 72 || y >= 410 ||
(y >= 384 && x >= 210 && x < 280)))
{
ui_join_code_globals.panel_open = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
return TRUE;
}
if (pointer->left_clicks && y >= 384 && x >= 104 && x < 210)
{
ui_join_code_launch_keyboard();
return TRUE;
}
if (x >= 48 && x < 592 && y >= 128 && y < 128 + ONLINE_PANEL_ROWS * 22)
{
row = (short)(ui_join_code_globals.scroll + (y - 128) / 22);
if (row < ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS)
{
ui_join_code_globals.selection = row;
if (pointer->left_clicks)
ui_join_code_panel_event(&press);
}
}
return TRUE;
}
if (ui_join_code_globals.host_panel_open)
{
/* (a click beside the panel, or on its key's B, closes it) */
if (pointer->left_clicks && (x < 140 || x >= 500 || y < 150 || y >= 330 || (y >= 296 && x >= 250)))
{
ui_join_code_globals.host_panel_open = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
return TRUE;
}
if (x >= 148 && x < 492 && y >= 187 && y < 187 + HOST_PANEL_ROWS * 30)
{
ui_join_code_globals.host_selection = (short)((y - 187) / 30);
if (pointer->left_clicks)
ui_join_code_host_panel_event(&press);
}
return TRUE;
}
/* (on the PC version's screens, their hints top right clicked: X's and
Y's) */
if (pointer->left_clicks && x >= 420 && y < 52 && ui_join_code_pc_screens())
{
press.data.button.index = ui_join_code_join_screen() ? _gamepad_analog_button_x : _gamepad_analog_button_y;
return ui_join_code_take_event(widget_globals.active_widgets[0], &press);
}
return FALSE;
}
/* the keyboard closed: the code typed, looked up */
static void ui_join_code_update(
void)
{
{
/* (each screen's name in the log, as it comes up) */
static char const *logged;
char const *name = widget_globals.active_widgets[0] ? widget_globals.active_widgets[0]->name : NULL;
if (name != logged)
{
logged = name;
if (name)
platform_log("menus: screen %s", name);
}
}
char text[sizeof(ui_join_code_globals.typed) / sizeof(wchar_t)];
short index;
if (!ui_join_code_globals.keyboard_launched || virtual_keyboard_active())
return;
ui_join_code_globals.keyboard_launched = FALSE;
virtual_keyboard_set_caption(NULL);
if (!virtual_keyboard_last_exit_saved_text())
return;
for (index = 0; index < (short)(NUMBEROF(text) - 1) && ui_join_code_globals.typed[index]; index++)
{
wchar_t character = ui_join_code_globals.typed[index];
text[index] = character < 128 ? (char)character : '?';
}
text[index] = 0;
p2p_join_with_code(text);
}
/* text in bounds, left, right or centred (0, 1, 2) */
static void ui_join_code_draw_text(
short x0,
short x1,
short y0,
char const *text,
boolean icons,
short justification,
real_argb_color const *color)
{
rectangle2d bounds;
wchar_t wide[96];
short index;
long font_index = tag_loaded(FONT_GROUP_TAG, "ui\\small_ui");
if (font_index == NONE)
return;
for (index = 0; index < (short)(NUMBEROF(wide) - 1) && text[index]; index++)
wide[index] = (wchar_t)(unsigned char)text[index];
wide[index] = 0;
bounds.y0 = y0;
bounds.y1 = (short)(y0 + 20);
bounds.x0 = x0;
bounds.x1 = x1;
draw_string_set_draw_mode(font_index, NONE, justification, 0, color);
if (icons)
draw_string_and_hack_in_icons(&bounds, &bounds, NULL, 0, wide, FALSE);
else
rasterizer_draw_unicode_string(&bounds, &bounds, NULL, 0, wide);
}
static void ui_join_code_draw_line(
short y0,
char const *text,
boolean icons,
boolean centered)
{
real_argb_color color = { 1.0f, 0.85f, 0.85f, 0.85f };
ui_join_code_draw_text((short)(centered ? 0 : 48), (short)(centered ? 640 : 600), y0, text, icons,
(short)(centered ? 2 : 0), &color);
}
static void ui_join_code_draw_quad(
short x0,
short y0,
short x1,
short y1,
unsigned long color)
{
rectangle2d bounds;
bounds.x0 = x0;
bounds.y0 = y0;
bounds.x1 = x1;
bounds.y1 = y1;
draw_quad(&bounds, color);
}
/* the panel of online games: "enter a code", then the public games */
static void ui_join_code_render_panel(
void)
{
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color text_color = { 1.0f, 0.85f, 0.85f, 0.85f };
real_argb_color dim_color = { 1.0f, 0.6f, 0.6f, 0.6f };
char line[64];
short row;
ui_join_code_globals.game_count =
(short)p2p_public_games(ui_join_code_globals.games, MAXIMUM_SHOWN_PUBLIC_GAMES);
ui_join_code_globals.selection =
(short)PIN(ui_join_code_globals.selection, 0, ui_join_code_globals.game_count);
ui_join_code_draw_quad(40, 72, 600, 410, 0xF0050A18);
ui_join_code_draw_quad(40, 72, 600, 74, 0xFF3A6EA5);
ui_join_code_draw_quad(40, 408, 600, 410, 0xFF3A6EA5);
snprintf(line, sizeof(line), "ONLINE GAMES (%d public)", (int)ui_join_code_globals.game_count);
ui_join_code_draw_text(56, 584, 80, line, FALSE, 0, &title_color);
ui_join_code_draw_text(56, 216, 104, "Name", FALSE, 0, &dim_color);
ui_join_code_draw_text(216, 376, 104, "Map", FALSE, 0, &dim_color);
ui_join_code_draw_text(376, 516, 104, "Game", FALSE, 0, &dim_color);
ui_join_code_draw_text(516, 584, 104, "Players", FALSE, 1, &dim_color);
for (row = 0; row < ONLINE_PANEL_ROWS; row++)
{
short index = (short)(ui_join_code_globals.scroll + row);
short y = (short)(128 + row * 22);
if (index > ui_join_code_globals.game_count)
break;
if (index == ui_join_code_globals.selection)
ui_join_code_draw_quad(48, y, 592, (short)(y + 21), 0x803A6EA5);
if (index == 0)
{
ui_join_code_draw_text(56, 584, (short)(y + 1), "Enter a join code...", FALSE, 0, &text_color);
}
else
{
struct ui_public_game const *game = &ui_join_code_globals.games[index - 1];
ui_join_code_draw_text(56, 212, (short)(y + 1), game->name, FALSE, 0, &text_color);
ui_join_code_draw_text(216, 372, (short)(y + 1), ui_join_code_map_name(game->map), FALSE, 0,
&text_color);
ui_join_code_draw_text(376, 512, (short)(y + 1), game->mode, FALSE, 0, &text_color);
snprintf(line, sizeof(line), "%d/%d", game->players, game->maximum_players);
ui_join_code_draw_text(516, 584, (short)(y + 1), line, FALSE, 1, &text_color);
}
}
if (!ui_join_code_globals.game_count)
ui_join_code_draw_text(56, 584, 152, "No public games right now", FALSE, 0, &dim_color);
ui_join_code_draw_text(56, 584, 384, "%a-button Join %x-button Enter a code %b-button Back", TRUE, 0,
&text_color);
}
/* over the screens, as they are drawn */
static void ui_join_code_render(
void)
{
char code[16];
if (ui_join_code_join_screen())
{
char const *status = NULL;
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
if (ui_join_code_globals.panel_open)
{
ui_join_code_render_panel();
return;
}
/* (in the key bar's free left, and under it: clear of the map's
picture; on the PC version's, top right, beside its title) */
if (ui_join_code_pc_screens())
ui_join_code_draw_text(500, 630, 12, "%x-button Join code", TRUE, 0, &hint_color);
else
ui_join_code_draw_text(8, 200, 420, "%x-button Online / code", TRUE, 0, &hint_color);
switch (p2p_join_code_status())
{
case _join_code_looking: status = "Looking for the game of that code..."; break;
case _join_code_found: status = "Found it: the game shows up in the list once its host is reached"; break;
case _join_code_not_found: status = "No game answered that code"; break;
case _join_code_invalid: status = "That is not a join code (8 letters and digits)"; break;
case _join_code_own: status = "That is this game's own code"; break;
case _join_code_offline: status = "Online play is off (network.online in config.toml)"; break;
}
/* (the code found, or a link: how reaching its host goes) */
if (!status || p2p_join_code_status() == _join_code_found)
{
switch (p2p_join_state())
{
case _join_state_reaching: status = "Reaching the online game's host..."; break;
case _join_state_connected: status = "Connected: the online game is in the list above"; break;
case _join_state_no_answer: status = "The host did not answer: it may have stopped hosting"; break;
case _join_state_blocked: status = "Blocked by the routers: allow UPnP on yours or the host's"; break;
case _join_state_lost: status = "Lost the connection to the online game's host"; break;
}
}
/* (on the PC version's, under its list, which says itself when
online play is off) */
if (status && ui_join_code_pc_screens())
{
if (p2p_join_code_status() != _join_code_offline)
ui_join_code_draw_text(20, 620, 358, status, FALSE, 2, &hint_color);
}
else if (status)
ui_join_code_draw_line(452, status, FALSE, FALSE);
}
else if (ui_join_code_lobby_screen() && global_network_game_server_get())
{
char line[128];
long bots = network_game_server_port_bot_count();
char bots_text[24];
if (bots > 0)
snprintf(bots_text, sizeof(bots_text), "%ld bot%s", bots, bots == 1 ? "" : "s");
else
snprintf(bots_text, sizeof(bots_text), "no bots");
if (p2p_join_code(code, sizeof(code)))
{
snprintf(line, sizeof(line), "Online join code: %s %%y-button Host options (%s, %s)", code,
p2p_public_game() ? "public" : "private", bots_text);
}
else
{
snprintf(line, sizeof(line), "%%y-button Host options (%s)", bots_text);
}
/* (under the buttons' key; on the PC version's, top right, beside
its title, a line each) */
if (ui_join_code_pc_screens())
{
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
if (p2p_join_code(code, sizeof(code)))
{
snprintf(line, sizeof(line), "Join code: %s", code);
ui_join_code_draw_text(420, 630, 12, line, FALSE, 0, &hint_color);
}
snprintf(line, sizeof(line), "%%y-button Host options (%s)", bots_text);
ui_join_code_draw_text(420, 630, 32, line, TRUE, 0, &hint_color);
}
else
{
ui_join_code_draw_line(448, line, TRUE, TRUE);
}
if (ui_join_code_globals.host_panel_open)
{
static char const *const skills[] = { "Easy", "Normal", "Hard" };
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color text_color = { 1.0f, 0.85f, 0.85f, 0.85f };
short row;
ui_join_code_draw_quad(140, 150, 500, 330, 0xF0050A18);
ui_join_code_draw_quad(140, 150, 500, 152, 0xFF3A6EA5);
ui_join_code_draw_quad(140, 328, 500, 330, 0xFF3A6EA5);
ui_join_code_draw_text(156, 484, 158, "HOST OPTIONS", FALSE, 0, &title_color);
for (row = 0; row < HOST_PANEL_ROWS; row++)
{
short y = (short)(190 + row * 30);
if (row == ui_join_code_globals.host_selection)
ui_join_code_draw_quad(148, (short)(y - 3), 492, (short)(y + 21), 0x803A6EA5);
switch (row)
{
case 0:
snprintf(line, sizeof(line), "Public game: %s", p2p_public_game() ? "Yes (listed online)" : "No");
break;
case 1:
snprintf(line, sizeof(line), "Bots: %s", bots > 0 ? bots_text : "None");
break;
default:
{
long skill = PIN(config_integer("bots.skill"), 0, 2);
snprintf(line, sizeof(line), "Bot skill: %s", skills[skill]);
break;
}
}
ui_join_code_draw_text(160, 484, y, line, FALSE, 0, &text_color);
}
ui_join_code_draw_text(156, 484, 300, "%a-button Change %b-button Done", TRUE, 0, &text_color);
}
}
}
/* ---------- VR options (the VR port's)
In VR, Y on the main menu or the pause menu opens a panel of the VR
settings one may want to change while playing (vr.c reads them again as
they change), and from it a page of what the controllers' buttons do. */
int config_boolean(char const *name);
double config_real(char const *name);
char const *config_string(char const *name);
int config_write(char const *name, char const *value);
int config_write_boolean(char const *name, int value);
enum
{
_vr_option_turning,
_vr_option_snap_angle,
_vr_option_smooth_speed,
_vr_option_vignette,
_vr_option_wheel,
_vr_option_handedness,
_vr_option_mirror_controls,
_vr_option_laser,
_vr_option_crosshair,
_vr_option_motion_tracker,
_vr_option_body,
_vr_option_fingers,
_vr_option_graphics,
_vr_option_refresh_rate,
_vr_option_resolution,
_vr_option_controls,
_vr_option_quit,
NUMBER_OF_VR_OPTIONS,
/* (twelve rows a page between the title and the footer, in the box) */
VR_OPTIONS_TOP = 94,
VR_OPTIONS_ROW_HEIGHT = 22,
VR_OPTIONS_FOOTER = 362,
VR_OPTIONS_PER_PAGE = 12,
NUMBER_OF_VR_OPTION_PAGES = 2,
_vr_controls_on_foot = 0,
_vr_controls_vehicles,
_vr_controls_menus,
NUMBER_OF_VR_CONTROLS_PAGES,
};
static struct
{
boolean open;
boolean controls;
short selection;
short page;
/* "Quit Halo" chosen once: a second time quits */
boolean quit_armed;
} ui_vr_options_globals;
/* the panel's pages (its tabs), their rows in order (NONE past the last) */
static char const *const vr_option_page_names[NUMBER_OF_VR_OPTION_PAGES] = { "PLAY", "MORE" };
static short const vr_option_pages[NUMBER_OF_VR_OPTION_PAGES][VR_OPTIONS_PER_PAGE] =
{
{ _vr_option_turning, _vr_option_snap_angle, _vr_option_smooth_speed, _vr_option_vignette, _vr_option_wheel,
_vr_option_handedness, _vr_option_mirror_controls, _vr_option_laser, _vr_option_crosshair,
_vr_option_motion_tracker, _vr_option_body, _vr_option_fingers },
{ _vr_option_graphics, _vr_option_refresh_rate, _vr_option_resolution, _vr_option_controls, _vr_option_quit,
NONE, NONE, NONE, NONE, NONE, NONE, NONE },
};
/* the page a row is on, and its place there */
static short ui_vr_options_page_of(
short option,
short *index)
{
short page, row;
for (page = 0; page < NUMBER_OF_VR_OPTION_PAGES; page++)
{
for (row = 0; row < VR_OPTIONS_PER_PAGE; row++)
{
if (vr_option_pages[page][row] == option)
{
if (index)
*index = row;
return page;
}
}
}
if (index)
*index = 0;
return 0;
}
static short ui_vr_options_page_rows(
short page)
{
short rows = 0;
while (rows < VR_OPTIONS_PER_PAGE && vr_option_pages[page][rows] != NONE)
rows++;
return rows;
}
/* the settings' hands: the weapon in the left one, and the sticks and
buttons mirrored with it (vr.c) */
static boolean ui_vr_left_handed(
void)
{
char const *handedness = config_string("vr.handedness");
return handedness && strcmp(handedness, "left") == 0;
}
static boolean ui_vr_controls_mirrored(
void)
{
return ui_vr_left_handed() && config_boolean("vr.mirror_controls");
}
void platform_request_quit(void);
static real const vr_snap_angles[] = { 30.0f, 45.0f, 60.0f, 90.0f };
static real const vr_smooth_speeds[] = { 60.0f, 90.0f, 120.0f, 150.0f, 180.0f };
static real const vr_vignettes[] = { 0.0f, 0.35f, 0.7f, 1.0f };
static char const *const vr_vignette_names[] = { "Off", "Light", "Medium", "Strong" };
/* vr.body's values, and as the panel shows them */
static char const *const vr_bodies[] = { "arms", "hands", "off" };
static char const *const vr_body_names[] = { "Arms and hands", "Hands only", "Weapon only" };
/* vr.graphics's (port/linux/game/vr_graphics.c), and vr.resolution_scale's
(read as the app starts) */
static char const *const vr_graphics_presets[] = { "low", "medium", "high", "max" };
static char const *const vr_graphics_names[] = { "Low", "Medium", "High", "Max" };
static real const vr_resolution_scales[] = { 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f };
static short ui_vr_options_graphics(
void)
{
char const *graphics = config_string("vr.graphics");
short index;
for (index = 0; index < (short)NUMBEROF(vr_graphics_presets); index++)
{
if (graphics && strcmp(graphics, vr_graphics_presets[index]) == 0)
return index;
}
return 2;
}
/* the refresh rates the headset offers, in order, and which of them
vr.refresh_rate chooses (the highest at or below it; 0 asks for the
highest); 0 rates where it lets none be chosen (SteamVR: SteamOS's) */
static short ui_vr_options_refresh_rates(
real rates[16],
short *chosen,
real *current)
{
float offered[16];
float now = 0.0f;
short count = (short)halo_vr_refresh_rates(offered, 16, &now);
real wanted = (real)config_real("vr.refresh_rate");
short index;
*current = now;
*chosen = 0;
for (index = 0; index < count; index++)
{
short place = index;
/* (in order, lowest first) */
while (place > 0 && rates[place - 1] > offered[index])
{
rates[place] = rates[place - 1];
place--;
}
rates[place] = offered[index];
}
for (index = 0; index < count; index++)
{
if (wanted <= 0.0f || rates[index] <= wanted + 0.5f)
*chosen = index;
}
return count;
}
static short ui_vr_options_body(
void)
{
char const *body = config_string("vr.body");
short index;
for (index = 0; index < (short)NUMBEROF(vr_bodies); index++)
{
if (body && strcmp(body, vr_bodies[index]) == 0)
return index;
}
return 0;
}
static boolean ui_vr_options_screen(
void)
{
struct widget_instance *screen = widget_globals.active_widgets[0];
/* (the Xbox's main menu, or the PC version's: vr.menus) */
return halo_vr_active() && screen && screen->name &&
(main_menu_screen_is_active() || strstr(screen->name, "pause_game") != NULL ||
strcmp(screen->name, "main_menu") == 0);
}
/* which of the choices a setting is nearest */
static short ui_vr_options_nearest(
real value,
real const *choices,
short count)
{
short index;
short nearest = 0;
for (index = 1; index < count; index++)
{
if (fabs(choices[index] - value) < fabs(choices[nearest] - value))
nearest = index;
}
return nearest;
}
static void ui_vr_options_step(
char const *name,
real const *choices,
short count,
short change)
{
char text[16];
short index = ui_vr_options_nearest((real)config_real(name), choices, count);
index = (short)((index + change + count) % count);
snprintf(text, sizeof(text), "%g", choices[index]);
config_write(name, text);
}
/* a row's setting changed: forward or back, going round */
static void ui_vr_options_change(
short change)
{
switch (ui_vr_options_globals.selection)
{
case _vr_option_turning: config_write_boolean("vr.smooth_turn", !config_boolean("vr.smooth_turn")); break;
case _vr_option_snap_angle:
ui_vr_options_step("vr.snap_turn_degrees", vr_snap_angles, NUMBEROF(vr_snap_angles), change);
break;
case _vr_option_smooth_speed:
ui_vr_options_step("vr.smooth_turn_speed", vr_smooth_speeds, NUMBEROF(vr_smooth_speeds), change);
break;
case _vr_option_vignette:
ui_vr_options_step("vr.comfort_vignette", vr_vignettes, NUMBEROF(vr_vignettes), change);
break;
case _vr_option_wheel: config_write_boolean("vr.vehicle_wheel", !config_boolean("vr.vehicle_wheel")); break;
case _vr_option_handedness: config_write("vr.handedness", ui_vr_left_handed() ? "right" : "left"); break;
case _vr_option_mirror_controls:
config_write_boolean("vr.mirror_controls", !config_boolean("vr.mirror_controls"));
break;
case _vr_option_laser: config_write_boolean("vr.laser", !config_boolean("vr.laser")); break;
case _vr_option_crosshair: config_write_boolean("vr.crosshair", !config_boolean("vr.crosshair")); break;
case _vr_option_motion_tracker:
config_write_boolean("vr.motion_tracker_on_wrist", !config_boolean("vr.motion_tracker_on_wrist"));
break;
case _vr_option_body:
config_write("vr.body", vr_bodies[(ui_vr_options_body() + change + (short)NUMBEROF(vr_bodies)) %
(short)NUMBEROF(vr_bodies)]);
break;
case _vr_option_fingers: config_write_boolean("vr.fingers", !config_boolean("vr.fingers")); break;
case _vr_option_graphics:
config_write("vr.graphics", vr_graphics_presets[(ui_vr_options_graphics() + change +
(short)NUMBEROF(vr_graphics_presets)) % (short)NUMBEROF(vr_graphics_presets)]);
break;
case _vr_option_refresh_rate:
{
real rates[16], current;
short chosen;
short count = ui_vr_options_refresh_rates(rates, &chosen, &current);
if (count > 0)
{
char text[16];
snprintf(text, sizeof(text), "%g", rates[(chosen + change + count) % count]);
config_write("vr.refresh_rate", text);
}
break;
}
case _vr_option_resolution:
ui_vr_options_step("vr.resolution_scale", vr_resolution_scales, NUMBEROF(vr_resolution_scales), change);
break;
case _vr_option_controls:
ui_vr_options_globals.controls = TRUE;
ui_vr_options_globals.page = _vr_controls_on_foot;
break;
case _vr_option_quit:
/* (the Steam Frame's Steam cannot end the game, which runs in
Lepton's container: it ends itself; asked twice) */
if (ui_vr_options_globals.quit_armed)
platform_request_quit();
ui_vr_options_globals.quit_armed = TRUE;
break;
}
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
}
static void ui_vr_options_close(
void)
{
ui_vr_options_globals.quit_armed = FALSE;
ui_vr_options_globals.open = FALSE;
ui_vr_options_globals.controls = FALSE;
}
/* the panel's input: up and down choose a row, left and right (or A)
change it, B or Y closes it (the controls page: left and right turn its
pages, B goes back) */
static boolean ui_vr_options_take_event(
struct widget_instance *widget,
struct event_record const *event)
{
short move = 0;
short change = 0;
boolean accept = FALSE;
boolean back = FALSE;
if (widget != widget_globals.active_widgets[0])
return FALSE;
if (ui_vr_options_globals.open && !ui_vr_options_screen())
ui_vr_options_close();
if (!ui_vr_options_globals.open)
{
if (event->type == _event_type_button && event->data.button.value == 1 &&
event->data.button.index == _gamepad_analog_button_y && ui_vr_options_screen())
{
ui_vr_options_globals.open = TRUE;
ui_vr_options_globals.controls = FALSE;
ui_vr_options_globals.selection = vr_option_pages[0][0];
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
return TRUE;
}
return FALSE;
}
if (event->type == _event_type_left_stick)
{
if (event->data.stick.y == SHORT_MAX)
move = -1;
else if (event->data.stick.y == SHORT_MIN)
move = 1;
else if (event->data.stick.x == SHORT_MAX)
change = 1;
else if (event->data.stick.x == SHORT_MIN)
change = -1;
}
else if (event->type == _event_type_button && event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _gamepad_binary_button_dpad_up: move = -1; break;
case _gamepad_binary_button_dpad_down: move = 1; break;
case _gamepad_binary_button_dpad_right: change = 1; break;
case _gamepad_binary_button_dpad_left: change = -1; break;
case _gamepad_analog_button_a:
case _gamepad_binary_button_start:
accept = TRUE;
break;
case _gamepad_analog_button_b:
case _gamepad_analog_button_y:
case _gamepad_binary_button_back:
back = TRUE;
break;
}
}
if (ui_vr_options_globals.controls)
{
if (back)
{
ui_vr_options_globals.controls = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
}
else if (change || accept)
{
ui_vr_options_globals.page = (short)((ui_vr_options_globals.page + (change ? change : 1) +
NUMBER_OF_VR_CONTROLS_PAGES) % NUMBER_OF_VR_CONTROLS_PAGES);
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
}
return TRUE;
}
if (back)
{
ui_vr_options_close();
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
}
else if (move)
{
/* (past a page's last row or first, onto the next page or back) */
short row;
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, &row);
row = (short)(row + move);
if (row < 0 && page > 0)
{
page--;
row = (short)(ui_vr_options_page_rows(page) - 1);
}
else if (row >= ui_vr_options_page_rows(page) && page < NUMBER_OF_VR_OPTION_PAGES - 1)
{
page++;
row = 0;
}
row = (short)PIN(row, 0, ui_vr_options_page_rows(page) - 1);
ui_vr_options_globals.selection = vr_option_pages[page][row];
ui_vr_options_globals.quit_armed = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
}
else if (accept)
{
ui_vr_options_change(1);
}
else if (change && ui_vr_options_globals.selection != _vr_option_controls)
{
ui_vr_options_change(change);
}
return TRUE;
}
/* the pointer over the panel: the row under it chosen, a click on it as A;
on the controls page the tabs, and the footer's Back (TRUE: the panel is
open and took it) */
static boolean ui_vr_options_take_pointer(
struct halo_ui_pointer const *pointer)
{
short x = pointer->left_clicks ? pointer->click_x : pointer->x;
short y = pointer->left_clicks ? pointer->click_y : pointer->y;
if (!ui_vr_options_globals.open)
{
/* (its hint clicked: open) */
boolean pc_main_menu = widget_globals.active_widgets[0] && widget_globals.active_widgets[0]->name &&
strcmp(widget_globals.active_widgets[0]->name, "main_menu") == 0;
if (pointer->left_clicks && ui_vr_options_screen() &&
(pc_main_menu ? x >= 400 && y >= 446 : x >= 256 && x < 400 && y >= 434 && y < 464))
{
ui_vr_options_globals.open = TRUE;
ui_vr_options_globals.controls = FALSE;
ui_vr_options_globals.selection = vr_option_pages[0][0];
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
return TRUE;
}
return FALSE;
}
if (!ui_vr_options_screen())
{
ui_vr_options_close();
return FALSE;
}
if (ui_vr_options_globals.controls)
{
if (!pointer->left_clicks)
return TRUE;
if (y >= 50 && y < 74 && x >= 40 && x < 600)
{
ui_vr_options_globals.page = (short)PIN((x - 40) / 186, 0, NUMBER_OF_VR_CONTROLS_PAGES - 1);
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
}
else if (y >= 410 && y < 434)
{
ui_vr_options_globals.controls = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
}
return TRUE;
}
{
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, NULL);
if (pointer->left_clicks && y >= 68 && y < 88 && x >= 330 && x < 514)
{
/* (a tab: its page) */
short tab = (short)PIN((x - 330) / 92, 0, NUMBER_OF_VR_OPTION_PAGES - 1);
if (tab != page)
{
ui_vr_options_globals.selection = vr_option_pages[tab][0];
ui_vr_options_globals.quit_armed = FALSE;
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
}
}
else if (x >= 118 && x < 522 && y >= VR_OPTIONS_TOP - 4 &&
y < VR_OPTIONS_TOP - 4 + ui_vr_options_page_rows(page) * VR_OPTIONS_ROW_HEIGHT)
{
short option = vr_option_pages[page][(y - (VR_OPTIONS_TOP - 4)) / VR_OPTIONS_ROW_HEIGHT];
if (option != ui_vr_options_globals.selection)
ui_vr_options_globals.quit_armed = FALSE;
ui_vr_options_globals.selection = option;
if (pointer->left_clicks)
ui_vr_options_change(1);
}
else if (pointer->left_clicks && x >= 118 && x < 522 && y >= VR_OPTIONS_FOOTER + 4 &&
y < VR_OPTIONS_FOOTER + 30)
{
ui_vr_options_close();
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
}
}
return TRUE;
}
/* a line of the controls page: what is pressed, dim, and what it does */
static void ui_vr_controls_line(
short x,
short y,
char const *control,
char const *action)
{
real_argb_color control_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color action_color = { 1.0f, 0.88f, 0.88f, 0.88f };
ui_join_code_draw_text(x, (short)(x + 92), y, control, FALSE, 0, &control_color);
ui_join_code_draw_text((short)(x + 92), (short)(x + 280), y, action, FALSE, 0, &action_color);
}
static void ui_vr_controls_note(
short y,
char const *text)
{
real_argb_color color = { 1.0f, 0.88f, 0.88f, 0.88f };
ui_join_code_draw_text(44, 596, y, text, FALSE, 0, &color);
}
/* a column of the controls page: its lines, what is pressed and what it
does ("" for a line that goes on the one above) */
enum
{
VR_CONTROLS_COLUMN_LINES = 12,
};
struct ui_vr_controls_column
{
short count;
char const *controls[VR_CONTROLS_COLUMN_LINES];
char const *actions[VR_CONTROLS_COLUMN_LINES];
};
static void ui_vr_controls_add(
struct ui_vr_controls_column *column,
char const *control,
char const *action)
{
if (column->count < VR_CONTROLS_COLUMN_LINES)
{
column->controls[column->count] = control;
column->actions[column->count] = action;
column->count++;
}
}
static void ui_vr_controls_column_render(
short x,
short y,
struct ui_vr_controls_column const *column)
{
short line;
for (line = 0; line < column->count; line++)
{
if (line > 0)
y = (short)(y + (column->controls[line][0] ? 22 : 18));
ui_vr_controls_line(x, y, column->controls[line], column->actions[line]);
}
}
/* The page of what the controllers' buttons do, as the hands are set
(vr.handedness, vr.mirror_controls: vr.c's halo_vr_controllers): the
weapon hand's trigger fires and its grip strikes; the free hand's throws
grenades and grips the foregrip; the move and turn sticks, and the face
buttons (on the Quest's right controller A and B jump and light, on its
left X and Y act and switch weapons; on the Steam Frame all four on the
right, its left D-pad taking them mirrored) change sides with the mirrored
controls. The menus keep their buttons. */
static void ui_vr_controls_render(
void)
{
static char const *const tabs[] = { "ON FOOT", "VEHICLES", "MENUS" };
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color tab_color = { 1.0f, 0.6f, 0.6f, 0.6f };
real_argb_color selected_color = { 1.0f, 1.0f, 1.0f, 1.0f };
struct ui_vr_controls_column columns[2];
boolean frame = halo_vr_controller_layout() == 1;
boolean left_handed = ui_vr_left_handed();
boolean mirrored = ui_vr_controls_mirrored();
/* the columns (0 the left controller, 1 the right) of the weapon hand,
the free hand, the turning stick and the jump button */
short gun = left_handed ? 0 : 1;
short free_column = (short)(1 - gun);
short turn = mirrored ? 0 : 1;
short move = (short)(1 - turn);
char const *wrist_note = left_handed ?
"Motion tracker: turn the back of your right wrist towards you." :
"Motion tracker: turn the back of your left wrist towards you.";
short tab;
short left = 44;
short right = 328;
short y = 112;
csmemset(columns, 0, sizeof(columns));
ui_join_code_draw_quad(24, 40, 616, 440, 0xFF050A18);
ui_join_code_draw_quad(24, 40, 616, 42, 0xFF3A6EA5);
ui_join_code_draw_quad(24, 438, 616, 440, 0xFF3A6EA5);
for (tab = 0; tab < NUMBER_OF_VR_CONTROLS_PAGES; tab++)
{
short x0 = (short)(40 + tab * 186);
if (tab == ui_vr_options_globals.page)
ui_join_code_draw_quad(x0, 50, (short)(x0 + 180), 74, 0x803A6EA5);
ui_join_code_draw_text(x0, (short)(x0 + 180), 52, tabs[tab], FALSE, 2,
tab == ui_vr_options_globals.page ? &selected_color : &tab_color);
}
ui_join_code_draw_text(left, (short)(left + 270), 86, "LEFT CONTROLLER", FALSE, 0, &title_color);
ui_join_code_draw_text(right, (short)(right + 270), 86, "RIGHT CONTROLLER", FALSE, 0, &title_color);
ui_join_code_draw_quad(320, 86, 321, 300, 0xFF3A6EA5);
if (frame)
{
/* the Steam Frame's controllers: A, B, X, Y and a menu button on the
right one, a D-pad and a View button on the left one; mirrored, the
D-pad does what A, B, X and Y do (down, left, right, up) */
char const *jump = mirrored ? "D-pad down" : "A";
char const *light = mirrored ? "D-pad left" : "B";
char const *act = mirrored ? "D-pad right" : "X";
char const *weapon = mirrored ? "D-pad up" : "Y";
short buttons = mirrored ? 0 : 1;
switch (ui_vr_options_globals.page)
{
case _vr_controls_on_foot:
ui_vr_controls_add(&columns[move], "Stick", "Move; click: crouch");
ui_vr_controls_add(&columns[turn], "Stick", "Turn; click: zoom");
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
ui_vr_controls_add(&columns[free_column], "Trigger", "Grenade: hold, throw");
ui_vr_controls_add(&columns[gun], "Grip", "Melee (or punch)");
ui_vr_controls_add(&columns[free_column], "Grip", "Weapon in both hands");
ui_vr_controls_add(&columns[buttons], jump, "Jump");
ui_vr_controls_add(&columns[buttons], light, "Light; held: grenades");
ui_vr_controls_add(&columns[buttons], act, "Action, reload");
ui_vr_controls_add(&columns[buttons], weapon, "Switch weapon");
ui_vr_controls_add(&columns[mirrored ? 0 : 1], "Bumper", "Flashlight");
ui_vr_controls_add(&columns[mirrored ? 1 : 0], "Bumper", "Switch grenades");
ui_vr_controls_add(&columns[0], "View", "Scores");
if (mirrored)
ui_vr_controls_add(&columns[1], "A, B, X, Y", "As the D-pad");
else
ui_vr_controls_add(&columns[0], "D-pad", "In the menus");
ui_vr_controls_add(&columns[1], "Menu", "Pause");
ui_vr_controls_note(320, wrist_note);
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
break;
case _vr_controls_vehicles:
ui_vr_controls_add(&columns[move], "Stick", "Drive");
ui_vr_controls_add(&columns[turn], "Stick", "Steer; up and down:");
ui_vr_controls_add(&columns[turn], "", "climb, dive");
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
ui_vr_controls_add(&columns[free_column], "Trigger", "Second weapon");
ui_vr_controls_add(&columns[free_column], "", "(Banshee, tank)");
ui_vr_controls_add(&columns[buttons], act, "Get in, get out");
ui_vr_controls_add(&columns[buttons], light, "Flashlight");
break;
default:
ui_vr_controls_add(&columns[0], "Stick", "Choose");
ui_vr_controls_add(&columns[0], "D-pad", "Choose");
ui_vr_controls_add(&columns[0], "Trigger", "Click where it points");
ui_vr_controls_add(&columns[1], "Stick", "Choose, scroll");
ui_vr_controls_add(&columns[1], "A", "Accept");
ui_vr_controls_add(&columns[1], "B", "Back");
ui_vr_controls_add(&columns[1], "X, Y", "As the screen shows");
ui_vr_controls_add(&columns[1], "Trigger", "Click where it points");
break;
}
}
else
{
/* the Quest's: A and B on the right controller, X, Y and the menu
button on the left; mirrored, X and Y do what A and B do, and the
other way round */
short jumps = mirrored ? 0 : 1;
short acts = (short)(1 - jumps);
switch (ui_vr_options_globals.page)
{
case _vr_controls_on_foot:
ui_vr_controls_add(&columns[move], "Stick", "Move");
ui_vr_controls_add(&columns[move], "Stick click", "Crouch");
ui_vr_controls_add(&columns[turn], "Stick", "Turn");
ui_vr_controls_add(&columns[turn], "Stick click", "Zoom");
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
ui_vr_controls_add(&columns[gun], "Grip", "Melee (or punch");
ui_vr_controls_add(&columns[gun], "", "forward)");
ui_vr_controls_add(&columns[free_column], "Trigger", "Grenade: hold it,");
ui_vr_controls_add(&columns[free_column], "", "let go as you throw");
ui_vr_controls_add(&columns[free_column], "Grip", "Weapon in both hands");
ui_vr_controls_add(&columns[jumps], jumps ? "A" : "X", "Jump");
ui_vr_controls_add(&columns[jumps], jumps ? "B" : "Y", "Flashlight");
ui_vr_controls_add(&columns[jumps], jumps ? "B held" : "Y held", "Switch grenades");
ui_vr_controls_add(&columns[acts], acts ? "A" : "X", "Action, reload");
ui_vr_controls_add(&columns[acts], acts ? "B" : "Y", "Switch weapon");
ui_vr_controls_add(&columns[0], "Menu", "Pause");
ui_vr_controls_note(320, wrist_note);
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
ui_vr_controls_note(360, "Hold the Meta button to recentre the view.");
break;
case _vr_controls_vehicles:
ui_vr_controls_add(&columns[move], "Stick", "Drive");
ui_vr_controls_add(&columns[turn], "Stick", "Steer; up and down:");
ui_vr_controls_add(&columns[turn], "", "climb, dive");
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
ui_vr_controls_add(&columns[free_column], "Trigger", "Second weapon");
ui_vr_controls_add(&columns[free_column], "", "(Banshee, tank)");
ui_vr_controls_add(&columns[acts], acts ? "A" : "X", "Get in, get out");
ui_vr_controls_add(&columns[jumps], jumps ? "B" : "Y", "Flashlight");
break;
default:
ui_vr_controls_add(&columns[0], "Stick", "Choose");
ui_vr_controls_add(&columns[0], "X, Y", "As the screen shows");
ui_vr_controls_add(&columns[0], "Trigger", "Click where it points");
ui_vr_controls_add(&columns[1], "Stick", "Choose, scroll");
ui_vr_controls_add(&columns[1], "A", "Accept");
ui_vr_controls_add(&columns[1], "B", "Back");
ui_vr_controls_add(&columns[1], "Trigger", "Click where it points");
break;
}
}
ui_vr_controls_column_render(left, y, &columns[0]);
ui_vr_controls_column_render(right, y, &columns[1]);
switch (ui_vr_options_globals.page)
{
case _vr_controls_vehicles:
ui_vr_controls_note(300, "Warthog: grip the wheel with one hand or both and turn it.");
ui_vr_controls_note(320, left_handed ? "Holding it, the left trigger accelerates, the right one brakes" :
"Holding it, the right trigger accelerates, the left one brakes");
ui_vr_controls_note(338, mirrored ? "and reverses. Let go, and the left stick steers." :
"and reverses. Let go, and the right stick steers.");
ui_vr_controls_note(360, left_handed ? "At a gun or as a passenger: aim with the left controller." :
"At a gun or as a passenger: aim with the right controller.");
break;
default:
ui_vr_controls_note(300, "Point at the menus with either controller; its trigger clicks.");
ui_vr_controls_note(320, "On the keyboard, BACK and ENTER under it can be clicked too.");
ui_vr_controls_note(340, "Y on the main menu or the pause menu: these VR options.");
if (left_handed)
ui_vr_controls_note(360, "Left-handed, the menus keep their buttons.");
break;
}
ui_join_code_draw_text(44, 596, 412, "%b-button Back Left, right: the other pages", TRUE, 0, &tab_color);
}
static void ui_vr_options_render(
void)
{
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
real_argb_color text_color = { 1.0f, 0.88f, 0.88f, 0.88f };
real_argb_color dim_color = { 1.0f, 0.5f, 0.5f, 0.5f };
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
char value[48];
short row;
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, NULL);
short tab;
boolean smooth = config_boolean("vr.smooth_turn") != 0;
if (!ui_vr_options_screen())
{
ui_vr_options_close();
return;
}
if (!ui_vr_options_globals.open)
{
/* (under the menu; centred by hand, as icons are drawn from the
left) */
if (strcmp(widget_globals.active_widgets[0]->name, "main_menu") == 0)
ui_join_code_draw_text(490, 630, 452, "%y-button VR options", TRUE, 0, &hint_color);
else
ui_join_code_draw_text(266, 400, 440, "%y-button VR options", TRUE, 0, &hint_color);
return;
}
if (ui_vr_options_globals.controls)
{
ui_vr_controls_render();
return;
}
ui_join_code_draw_quad(110, 62, 530, 396, 0xFF050A18);
ui_join_code_draw_quad(110, 62, 530, 64, 0xFF3A6EA5);
ui_join_code_draw_quad(110, 394, 530, 396, 0xFF3A6EA5);
ui_join_code_draw_text(126, 330, 72, "VR OPTIONS", FALSE, 0, &title_color);
/* (the pages' tabs, beside the title) */
for (tab = 0; tab < NUMBER_OF_VR_OPTION_PAGES; tab++)
{
short x0 = (short)(330 + tab * 92);
if (tab == page)
ui_join_code_draw_quad(x0, 68, (short)(x0 + 88), 88, 0x803A6EA5);
ui_join_code_draw_text(x0, (short)(x0 + 88), 72, vr_option_page_names[tab], FALSE, 2,
tab == page ? &text_color : &dim_color);
}
for (row = 0; row < ui_vr_options_page_rows(page); row++)
{
short y = (short)(VR_OPTIONS_TOP + row * VR_OPTIONS_ROW_HEIGHT);
short option = vr_option_pages[page][row];
char const *label = "";
boolean unused = FALSE;
value[0] = 0;
switch (option)
{
case _vr_option_turning:
label = "Turning";
snprintf(value, sizeof(value), "%s", smooth ? "Smooth" : "Snap");
break;
case _vr_option_snap_angle:
label = "Snap turn";
snprintf(value, sizeof(value), "%.0f degrees", vr_snap_angles[ui_vr_options_nearest(
(real)config_real("vr.snap_turn_degrees"), vr_snap_angles, NUMBEROF(vr_snap_angles))]);
unused = smooth;
break;
case _vr_option_smooth_speed:
label = "Smooth turn speed";
snprintf(value, sizeof(value), "%.0f degrees/s", vr_smooth_speeds[ui_vr_options_nearest(
(real)config_real("vr.smooth_turn_speed"), vr_smooth_speeds, NUMBEROF(vr_smooth_speeds))]);
unused = !smooth;
break;
case _vr_option_vignette:
label = "Vehicle comfort vignette";
snprintf(value, sizeof(value), "%s", vr_vignette_names[ui_vr_options_nearest(
(real)config_real("vr.comfort_vignette"), vr_vignettes, NUMBEROF(vr_vignettes))]);
break;
case _vr_option_wheel:
label = "Warthog steering wheel";
snprintf(value, sizeof(value), "%s", config_boolean("vr.vehicle_wheel") ? "On" : "Off (stick)");
break;
case _vr_option_handedness:
label = "Weapon hand";
snprintf(value, sizeof(value), "%s", ui_vr_left_handed() ? "Left" : "Right");
break;
case _vr_option_mirror_controls:
label = "Mirrored controls";
snprintf(value, sizeof(value), "%s", config_boolean("vr.mirror_controls") ? "Sticks, buttons" : "Off");
unused = !ui_vr_left_handed();
break;
case _vr_option_laser:
label = "Weapon laser";
snprintf(value, sizeof(value), "%s", config_boolean("vr.laser") ? "On" : "Off");
break;
case _vr_option_crosshair:
label = "Crosshair";
snprintf(value, sizeof(value), "%s", config_boolean("vr.crosshair") ? "On" : "Off");
break;
case _vr_option_motion_tracker:
label = "Motion tracker";
snprintf(value, sizeof(value), "%s", config_boolean("vr.motion_tracker_on_wrist") ? "On the wrist" : "In the HUD");
break;
case _vr_option_body:
label = "Body";
snprintf(value, sizeof(value), "%s", vr_body_names[ui_vr_options_body()]);
break;
case _vr_option_fingers:
label = "Fingers";
snprintf(value, sizeof(value), "%s", config_boolean("vr.fingers") ? "Tracked" : "Off");
unused = ui_vr_options_body() == 2;
break;
case _vr_option_graphics:
label = "Graphics";
snprintf(value, sizeof(value), "%s", vr_graphics_names[ui_vr_options_graphics()]);
break;
case _vr_option_refresh_rate:
{
real rates[16], current;
short chosen;
short count = ui_vr_options_refresh_rates(rates, &chosen, &current);
label = "Refresh rate";
if (count > 0)
snprintf(value, sizeof(value), "%.0f Hz", rates[chosen]);
else if (current > 1.0f)
snprintf(value, sizeof(value), "%.0f Hz (system)", current);
else
snprintf(value, sizeof(value), "System's");
unused = count <= 1;
break;
}
case _vr_option_resolution:
{
real scale = vr_resolution_scales[ui_vr_options_nearest((real)config_real("vr.resolution_scale"),
vr_resolution_scales, NUMBEROF(vr_resolution_scales))];
label = "Resolution";
if (fabs(scale - halo_vr_started_resolution_scale()) > 0.01f)
snprintf(value, sizeof(value), "%.0f%% (restart)", scale * 100.0f);
else
snprintf(value, sizeof(value), "%.0f%%", scale * 100.0f);
break;
}
case _vr_option_controls:
label = "Controller buttons...";
break;
case _vr_option_quit:
label = ui_vr_options_globals.quit_armed && ui_vr_options_globals.selection == _vr_option_quit ?
"Quit Halo? Once more to quit" : "Quit Halo";
break;
}
if (option == ui_vr_options_globals.selection)
ui_join_code_draw_quad(118, (short)(y - 4), 522, (short)(y + VR_OPTIONS_ROW_HEIGHT - 6), 0x803A6EA5);
ui_join_code_draw_text(128, 330, y, label, FALSE, 0, unused ? &dim_color : &text_color);
if (value[0])
{
char shown[56];
snprintf(shown, sizeof(shown), "< %s >", value);
ui_join_code_draw_text(330, 512, y, shown, FALSE, 1, unused ? &dim_color : &text_color);
}
}
if (page == 1)
{
/* (what the graphics rows do) */
ui_join_code_draw_text(128, 512, 230, "Graphics: Medium and Low leave effects out (shadows,", FALSE, 0,
&dim_color);
ui_join_code_draw_text(128, 512, 248, "reflections, lens flares; grass, weather...) for a steadier", FALSE,
0, &dim_color);
ui_join_code_draw_text(128, 512, 266, "frame; Max lights models for each pixel. The refresh rate", FALSE, 0,
&dim_color);
ui_join_code_draw_text(128, 512, 284, "changes at once where the headset lets it (on the Steam", FALSE, 0,
&dim_color);
ui_join_code_draw_text(128, 512, 302, "Frame, in SteamOS); the resolution after a restart.", FALSE, 0,
&dim_color);
}
ui_join_code_draw_text(126, 514, VR_OPTIONS_FOOTER + 8, "%a-button Change %b-button Done", TRUE, 0, &text_color);
}
/* whether a menu is up (VR shows it on a screen in the room: render.c) */
boolean ui_widgets_menus_active(
void)
{
return ui_mouse_menus_active();
}
#endif
/* the pointer's motion, clicks and wheel since the last frame, as the first
player's controller events */
/* port: a row of a list to choose from (the PC version's menus' lists of
gametypes, maps, profiles, levels and games: port/assets/menus) */
static boolean ui_mouse_selection_row(
struct widget_instance *widget)
{
return widget && widget->parent && pc_menu_tag(widget->parent->definition_tag_index) &&
(!strncmp(widget->name, "list_item_", 10) || !strncmp(widget->name, "server_item_", 12));
}
/* port: a press the menus post from their updates (menu_functions.c: the
server browser's join, once its game is reached), posted where the mouse's
are: one posted while the widgets update or draw would be overwritten by the
next frame's events (queue_event keeps the latest) */
static short ui_widget_port_press_controller = NONE;
static short ui_widget_port_press_button;
void ui_widget_port_post_button(
short controller_index,
short button_index)
{
ui_widget_port_press_controller = controller_index;
ui_widget_port_press_button = button_index;
return;
}
static void ui_widgets_process_mouse(
void)
{
struct halo_ui_pointer pointer;
struct ui_mouse_target *target;
short controller_index = 0;
/* (the keyboard takes the pointer itself: virtual_keyboard.c) */
if (virtual_keyboard_active() ||
!halo_ui_pointer_update(ui_mouse_menus_active(), &pointer))
{
ui_mouse_press_count = 0;
ui_mouse_hover_pending = FALSE;
ui_mouse_click_pending = FALSE;
}
else
{
#ifdef HALO_ANDROID
/* (the VR port's panels over the menus take the pointer themselves) */
if (ui_vr_options_take_pointer(&pointer) || ui_join_code_take_pointer(&pointer))
return;
#endif
if (pointer.moved)
{
ui_mouse_hover_pending = TRUE;
ui_mouse_hover_x = pointer.x;
ui_mouse_hover_y = pointer.y;
}
if (pointer.left_clicks)
{
ui_mouse_click_pending = TRUE;
ui_mouse_click_x = pointer.click_x;
ui_mouse_click_y = pointer.click_y;
}
if (pointer.right_clicks)
ui_mouse_press(_widget_event_b_button);
if (pointer.wheel_steps && ui_mouse_menu())
{
struct widget_instance *widget = ui_mouse_wheel_widget(ui_mouse_menu());
short back = _widget_event_dpad_up, forward = _widget_event_dpad_down;
long step;
if (widget)
ui_mouse_list_directions(widget, &back, &forward);
for (step = 0; step < ABS(pointer.wheel_steps) && step < 4; step++)
ui_mouse_press(pointer.wheel_steps > 0 ? back : forward);
}
if (!ui_mouse_press_count && ui_mouse_hover_pending)
{
ui_mouse_hover_pending = FALSE;
target = ui_mouse_target_at(ui_mouse_hover_x, ui_mouse_hover_y);
if (target)
{
switch (target->kind)
{
case _ui_mouse_target_item:
case _ui_mouse_target_value:
/* (a selection list's row is chosen by a click, not
by passing over it on the way to its buttons) */
if (!ui_mouse_selection_row(target->widget))
ui_mouse_give_focus(target->widget);
break;
case _ui_mouse_target_list_slot:
ui_mouse_step_list_to_slot(target->widget);
break;
}
}
}
if (!ui_mouse_press_count && ui_mouse_click_pending)
{
ui_mouse_click_pending = FALSE;
target = ui_mouse_target_at(ui_mouse_click_x, ui_mouse_click_y);
if (target)
{
switch (target->kind)
{
case _ui_mouse_target_item:
/* (a selection list's row: chosen by a click, used by
a click on it chosen) */
if (ui_mouse_selection_row(target->widget) && target->widget->parent &&
target->widget->parent->focused_child != target->widget)
{
ui_mouse_give_focus(target->widget);
break;
}
ui_mouse_give_focus(target->widget);
ui_mouse_press(_gamepad_analog_button_a);
break;
case _ui_mouse_target_value:
{
short back, forward;
boolean first_half;
ui_mouse_give_focus(target->widget);
ui_mouse_list_directions(target->widget, &back, &forward);
first_half = back == _widget_event_dpad_left ?
ui_mouse_click_x < (target->bounds.x0 + target->bounds.x1) / 2 :
ui_mouse_click_y < (target->bounds.y0 + target->bounds.y1) / 2;
ui_mouse_press(first_half ? back : forward);
break;
}
case _ui_mouse_target_list_slot:
ui_mouse_step_list_to_slot(target->widget);
ui_mouse_press(_gamepad_analog_button_a);
break;
case _ui_mouse_target_button:
ui_mouse_press(target->button_index);
break;
}
}
else
{
long index;
#ifdef HALO_ANDROID
/* (VR: beside a list, on its arrows) */
if (halo_vr_active() && ui_mouse_click_beside_list(ui_mouse_click_x, ui_mouse_click_y))
index = 0;
else
#endif
/* a screen with nothing to pick (a message to dismiss): the
click is its A */
for (index = 0; index < ui_mouse_target_count; index++)
{
if (ui_mouse_targets[index].kind != _ui_mouse_target_button)
break;
}
if (index == ui_mouse_target_count)
ui_mouse_press(_gamepad_analog_button_a);
}
}
if (ui_mouse_press_count)
{
event_manager_post_button(controller_index, ui_mouse_presses[0]);
csmemmove(ui_mouse_presses, ui_mouse_presses + 1, --ui_mouse_press_count * sizeof(ui_mouse_presses[0]));
}
}
ui_mouse_target_count = 0;
return;
}
static void widget_instance_render_recursive(
struct widget_instance *widget,
rectangle2d *clip_rect,
point2d offset,
boolean focus,
boolean use_nifty_plasma_fx)
{
struct ui_widget_definition *definition =
ui_widget_definition_get(widget->definition_tag_index);
real alpha_modifier = widget_instance_get_cumulative_alpha_modifier(widget);
boolean render_children = TRUE;
long input_index;
struct widget_instance *child;
struct bitmap_data *bitmap;
if (!use_nifty_plasma_fx &&
TEST_FLAG(definition->flags, _widget_always_render_with_nifty_fx_bit))
{
use_nifty_plasma_fx = TRUE;
}
offset.x += widget->horizontal_offset;
offset.y += widget->vertical_offset;
for (input_index = 0;
input_index < definition->game_data_inputs.count;
input_index++)
{
struct ui_widget_game_data_input_reference *input =
(struct ui_widget_game_data_input_reference *)
definition->game_data_inputs.address +
input_index;
ui_widget_game_data_function_invoke(widget, input->function);
}
if (!widget->visible)
return;
ui_mouse_note_target(widget, definition, offset);
bitmap = bitmap_group_get_bitmap_from_sequence(
definition->background_bitmap.index,
0,
widget->animation.current_frame_index);
if (bitmap)
{
real alpha = alpha_modifier;
rectangle2d bounds = definition->bounds;
rectangle2d *clip = clip_rect;
rectangle2d local_clip;
pixel32 color;
struct rasterizer_dynamic_screen_geometry_parameters multitexture_params;
struct bitmap_group *bitmap_group =
bitmap_group_get(definition->background_bitmap.index);
struct bitmap_group_sequence *sequence = TAG_BLOCK_GET_ELEMENT(
&bitmap_group->sequences,
0,
struct bitmap_group_sequence);
/* port: a widget whose bounds cover the whole 640x480 design space (the
* pause menu's dim, for example) should cover the whole screen too,
* not just the centered 640 columns -- same as the fade_to_black
* quad in render_ui_widgets(). Both the bounds and the clip are
* widened symmetrically below, before the centering offset is
* added. Only flat fills (the dims, and the menus' vertical
* gradient, at most 16 texels wide): a picture is drawn texel for
* texel, so wider bounds would shift it (the loading screen). */
boolean widen_to_screen =
bounds.x0 <= 0 && bounds.y0 <= 0 &&
bounds.x1 >= 640 && bounds.y1 >= 480 &&
bitmap->width <= 16 &&
halo_screen_width() > 640;
if (use_nifty_plasma_fx)
{
ui_plasma_effect_color.alpha = 0.0f;
ui_plasma_effect_color.red = 0.05f;
ui_plasma_effect_color.green = 0.05f;
ui_plasma_effect_color.blue = 0.05f;
}
if (widen_to_screen)
{
long extra = (halo_screen_width() - 640) / 2;
bounds.x0 -= (short)extra;
bounds.x1 = (short)(640 + extra);
}
bounds.x0 += offset.x;
bounds.x1 += offset.x;
bounds.y0 += offset.y;
bounds.y1 += offset.y;
if (clip)
{
local_clip = *clip;
clip = &local_clip;
clip->x0 += offset.x;
clip->x1 += offset.x;
clip->y0 += offset.y;
clip->y1 += offset.y;
}
if (widen_to_screen && clip)
{
/* widen the clip by the same amount (it is offset-shifted but
* still 640-wide at the edges) so the dim is not clipped back
* to the centered columns */
long extra = (halo_screen_width() - 640) / 2;
if (clip->x0 <= 0)
clip->x0 -= (short)extra;
if (clip->x1 >= 640)
clip->x1 = (short)(clip->x1 + extra);
}
if (TEST_FLAG(definition->flags, _widget_flash_background_bitmap_bit))
{
alpha = (((real)cos(
widget_globals.current_system_milliseconds *
SECONDS_PER_MILLISECOND * 3.0f) + 1.0f) * 0.5f) *
alpha_modifier;
}
color = modulate_pixel32_by_real_alpha(0xFFFFFFFF, alpha);
draw_bitmap_in_rect(
bitmap,
&bounds,
&bounds,
clip,
color,
&multitexture_params,
FALSE);
if (use_nifty_plasma_fx)
{
ui_plasma_effect_color.alpha = 0.0f;
ui_plasma_effect_color.red = 0.0f;
ui_plasma_effect_color.green = 0.0f;
ui_plasma_effect_color.blue = 0.0f;
}
}
switch (widget->type)
{
case _ui_widget_type_text_box:
widget_instance_render_text_box(
widget,
definition,
clip_rect,
offset,
widget_instance_text_box_is_focused(widget));
break;
case _ui_widget_type_spinner_list:
widget_instance_render_spinner_list(
widget,
definition,
clip_rect,
offset,
focus);
if (TEST_FLAG(
definition->list_flags,
_list_items_generated_from_string_list_tag) &&
definition->child_widgets.count == 0)
{
render_children = FALSE;
}
break;
case _ui_widget_type_column_list:
widget_instance_render_column_list(
widget,
definition,
clip_rect,
offset,
focus);
render_children = !TEST_FLAG(
definition->list_flags,
_list_items_generated_in_code);
break;
}
if (render_children)
{
for (child = widget->child; child; child = child->next)
{
focus = child == widget->focused_child;
use_nifty_plasma_fx = focus &&
(widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list);
widget_instance_render_recursive(
child,
clip_rect,
offset,
focus,
use_nifty_plasma_fx);
}
}
return;
}
void render_ui_widgets_postgame(
short local_player_index,
rectangle2d *window_bounds)
{
point2d const offsets[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS][MAXIMUM_NUMBER_OF_LOCAL_PLAYERS] =
{
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, 0 }, { 0, 240 }, { 0, 0 }, { 0, 0 } },
{ { 0, 0 }, { 0, 240 }, { 320, 240 }, { 0, 0 } },
{ { 0, 0 }, { 320, 0 }, { 0, 240 }, { 320, 240 } }
};
rectangle2d bounds;
long widget_index;
if (virtual_keyboard_active())
return;
local_player_index = PIN(
local_player_index,
0,
MAXIMUM_NUMBER_OF_LOCAL_PLAYERS - 1);
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
struct widget_instance *widget = widget_globals.active_widgets[widget_index];
boolean should_render = FALSE;
if (widget)
{
if (widget->render_regardless_of_controller_index == TRUE)
{
should_render = TRUE;
}
else if (widget->widget_is_error_dialog == TRUE)
{
if (widget->local_player_index == local_player_index ||
widget->local_player_index == NONE ||
local_player_index == NONE ||
we_are_at_the_main_menu)
{
should_render = TRUE;
}
}
else if (widget->local_player_index == NONE && widget_index == 0)
{
should_render = TRUE;
}
else if (widget->local_player_index == local_player_index)
{
should_render = TRUE;
}
}
if (should_render)
{
bounds.x0 = 0;
bounds.y0 = 0;
bounds.x1 = window_bounds->x1 - window_bounds->x0;
bounds.y1 = window_bounds->y1 - window_bounds->y0;
widget_instance_render_recursive(
widget_globals.active_widgets[widget_index],
&bounds,
offsets[local_player_count() - 1][local_player_index],
TRUE,
FALSE);
}
}
return;
}
void render_ui_widgets(
short local_player_index,
rectangle2d const *window_bounds)
{
rectangle2d bounds;
long widget_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
911,
window_bounds != NULL);
local_player_index_for_draw_string_and_hack_in_icons =
local_player_index == NONE ? 0 : local_player_index;
if (bink_playback_ui_rendering_inhibited())
return;
if (!virtual_keyboard_active())
{
local_player_index = PIN(
local_player_index,
0,
MAXIMUM_NUMBER_OF_LOCAL_PLAYERS - 1);
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
struct widget_instance *widget = widget_globals.active_widgets[widget_index];
boolean should_render = FALSE;
if (widget)
{
if (widget->render_regardless_of_controller_index == TRUE)
{
should_render = TRUE;
}
else if (widget->widget_is_error_dialog == TRUE)
{
if (widget->local_player_index == local_player_index ||
widget->local_player_index == NONE ||
local_player_index == NONE ||
we_are_at_the_main_menu)
{
should_render = TRUE;
}
}
else if (widget->local_player_index == NONE && widget_index == 0)
{
should_render = TRUE;
}
else if (widget->local_player_index == local_player_index)
{
should_render = TRUE;
}
}
if (should_render)
{
point2d offset;
bounds.x0 = 0;
bounds.y0 = 0;
bounds.x1 = window_bounds->x1 - window_bounds->x0;
bounds.y1 = window_bounds->y1 - window_bounds->y0;
offset.x = 0;
offset.y = 0;
/* the mouse drives the first player's menus */
ui_mouse_noting_targets = widget->local_player_index == NONE ||
widget->local_player_index == 0;
widget_instance_render_recursive(
widget_globals.active_widgets[widget_index],
&bounds,
offset,
TRUE,
FALSE);
ui_mouse_noting_targets = FALSE;
if (widget_globals.debug_show_path)
{
real_argb_color color = { 1.0f, 1.0f, 1.0f, 1.0f };
bounds.x0 += 32;
bounds.x1 += 32;
bounds.y0 += 32;
bounds.y1 += 32;
draw_string_set_draw_mode(
tag_loaded(FONT_GROUP_TAG, "ui\\small_ui"),
NONE,
0,
0,
&color);
rasterizer_draw_string(
&bounds,
NULL,
NULL,
0,
tag_get_name(
widget_globals.active_widgets[widget_index]->definition_tag_index));
}
}
}
#ifdef HALO_ANDROID
ui_join_code_render();
ui_vr_options_render();
#endif
if (widget_globals.fade_to_black >= 0.0f &&
widget_globals.fade_to_black <= 1.0f)
{
real alpha;
/* the whole screen, around the centered 640 columns */
bounds.x0 = (short)(-(halo_screen_width() - 640) / 2);
bounds.x1 = (short)(640 + (halo_screen_width() - 640) / 2);
bounds.y0 = 0;
bounds.y1 = 480;
if (widget_globals.fade_to_black >= 0.95f)
widget_globals.fade_to_black = 1.0f;
alpha = widget_globals.fade_to_black * 255.0f;
draw_quad(&bounds, fast_ftol(alpha) << 24);
}
}
else
{
virtual_keyboard_render();
}
return;
}
/* ---------- private code */
static long spinner_string_list_extra_count(
long string_list_index)
{
if (string_list_index != NONE && !csstrcmp(tag_get_name(string_list_index),
"ui\\shell\\main_menu\\settings_select\\multiplayer_setup\\playlist_edit\\slayer_edit\\var_kills_to_win"))
{
return NUMBEROF(kills_to_win_extra_strings);
}
return 0;
}
/* a spinner's items of its string list's own (the descriptions of a list of
spinners count those: ui_widget_game_data_input_functions.c) */
short ui_widget_spinner_own_item_count(
struct widget_instance *spinner)
{
struct ui_widget_definition *definition = ui_widget_definition_get(spinner->definition_tag_index);
return (short)(spinner->parameters.list.number_of_items -
spinner_string_list_extra_count(definition->text_label_string_list.index));
}
/* the string list index of the description of a spinner's extra item, or
NONE for an item of its own */
short ui_widget_spinner_extra_description(
struct widget_instance *spinner,
short item_index)
{
short own = ui_widget_spinner_own_item_count(spinner);
if (item_index < own)
return NONE;
return (short)(SPINNER_EXTRA_DESCRIPTION_BASE + item_index - own);
}
/* a string of a string list, or of its extra strings past the tag's own */
static wchar_t *spinner_string_list_get_string(
long string_list_index,
short string_index)
{
struct string_list *string_list = unicode_string_list_definition_get(string_list_index);
if (string_list && string_index >= string_list->strings.count &&
string_index - string_list->strings.count < spinner_string_list_extra_count(string_list_index))
{
return (wchar_t *)kills_to_win_extra_strings[string_index - string_list->strings.count];
}
return unicode_string_list_get_string(string_list_index, string_index);
}
static void widget_instance_render_column_list(
struct widget_instance *widget,
struct ui_widget_definition *definition,
rectangle2d *clip_rect,
point2d offset,
boolean focus)
{
if (widget->parameters.list.extended_description)
{
widget->parameters.list.extended_description->alpha_modifier =
widget_instance_get_cumulative_alpha_modifier(widget);
widget_instance_render_recursive(
widget->parameters.list.extended_description,
clip_rect,
offset,
FALSE,
TRUE);
}
if (TEST_FLAG(definition->list_flags, _list_items_generated_in_code))
{
struct widget_instance *child;
long item_index = 0;
for (child = widget->child; child; child = child->next)
{
if (item_index >= widget->parameters.list.number_of_items)
break;
widget_instance_render_recursive(
child,
clip_rect,
offset,
focus,
item_index == widget->parameters.list.selected_index);
item_index++;
}
}
widget->parameters.list.last_list_tab_direction = 0;
return;
}
static __inline void widget_instance_update_animation_parameters(
struct widget_instance *widget)
{
widget->animation.first_frame_index =
FLOOR(widget->animation.first_frame_index, 0);
widget->animation.last_frame_index =
FLOOR(widget->animation.last_frame_index, 0);
return;
}
static __inline void spinner_list_update(
struct widget_instance *widget)
{
struct widget_instance *child;
for (child = widget->child; child; child = child->next)
{
child->animation.current_frame_index = 0;
if (child == widget->focused_child &&
child->animation.number_of_sprite_frames == 2)
{
child->animation.current_frame_index = 1;
}
}
return;
}
/* the definition is passed by January's only call site but is not read; the
spinner list and the column list keep separate updaters rather than sharing one
parameterised helper */
static void column_list_update(
struct widget_instance *widget,
struct ui_widget_definition *definition)
{
struct widget_instance *child;
for (child = widget->child; child; child = child->next)
{
if (child == widget->focused_child)
{
if (child->animation.number_of_sprite_frames == 2)
child->animation.current_frame_index = 1;
}
else if (child->animation.number_of_sprite_frames == 2)
{
child->animation.current_frame_index = 0;
}
}
return;
}
static void widget_instance_tab_to_next_valid_widget(
struct widget_instance *widget)
{
struct widget_instance *child;
if (widget->focused_child && widget->focused_child->next)
child = widget->focused_child->next;
else
child = widget->child;
while (child && child != widget->focused_child)
{
struct ui_widget_definition *definition =
ui_widget_definition_get(child->definition_tag_index);
if ((definition->event_handlers.count > 0 ||
TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list) &&
/* port: over the PC version's labels and hidden rows */
!(widget->type == _ui_widget_type_column_list && pc_menu_tag(widget->definition_tag_index) &&
widget_instance_port_is_label(child)))
{
widget->focused_child = child;
break;
}
child = child->next;
if (!child)
child = widget->child;
}
return;
}
static void widget_instance_tab_to_previous_valid_widget(
struct widget_instance *widget)
{
struct widget_instance *child;
if (widget->focused_child)
{
if (widget->focused_child->previous)
child = widget->focused_child->previous;
else
child = widget_instance_get_tail_child_widget(widget);
}
else
{
child = widget->child->previous;
if (!child)
child = widget->child;
}
while (child && child != widget->focused_child)
{
struct ui_widget_definition *definition =
ui_widget_definition_get(child->definition_tag_index);
if ((definition->event_handlers.count > 0 ||
TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list) &&
/* port: over the PC version's labels and hidden rows */
!(widget->type == _ui_widget_type_column_list && pc_menu_tag(widget->definition_tag_index) &&
widget_instance_port_is_label(child)))
{
widget->focused_child = child;
break;
}
if (child->previous)
child = child->previous;
else
child = widget_instance_get_tail_child_widget(widget);
}
return;
}
/* port: whether a widget of the local player (NONE: any) takes the
controller's events. In co-op's menus (Multiplayer's CO-OP CAMPAIGN,
port/linux/game/menu_functions.c) the screens it shares with one player's
campaign, New Game's levels and the difficulty, are player 1's (their rows
the first controller's), and either player's controller uses them: player 1's
is the one that chose co-op, player 2's the one that chose their profile */
static boolean widget_takes_events_of_controller(
struct widget_instance const *widget,
short controller_index)
{
short player;
if (widget->local_player_index == NONE || widget->local_player_index == controller_index)
return TRUE;
if (widget->local_player_index != 0 || !we_are_at_the_main_menu || player_spawn_count < 2 ||
controller_index < 0 || controller_index >= MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
{
return FALSE;
}
for (player = 0; player < 2; player++)
{
if (player_ui_get_single_player_local_player_controller(player) == controller_index)
return TRUE;
}
return FALSE;
}
static void widget_instance_process_one_event_recursive(
struct widget_instance *widget,
struct ui_widget_definition *definition,
struct event_record *event,
boolean *return_widget_deleted)
{
boolean event_handled = FALSE;
boolean widget_deleted = FALSE;
boolean event_for_this_widget = widget_takes_events_of_controller(widget, event->controller_index);
long audio_feedback = _ui_audio_feedback_none;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
3067,
widget && definition && event && return_widget_deleted);
if (event->type == _event_type_button &&
event->data.button.value > 1 &&
event->controller_index >= 0 &&
event->controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS &&
event->data.button.index >= _widget_event_dpad_up &&
event->data.button.index <= _widget_event_dpad_right &&
widget_globals.current_system_milliseconds -
dpad_event_times[event->controller_index]
[event->data.button.index - _widget_event_dpad_up] >=
DPAD_EVENT_REPEAT_MILLISECONDS)
{
event->data.button.value = 1;
}
if (widget->close_if_local_player_controller_present == TRUE)
{
if (widget->local_player_index >= 0 &&
widget->local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
{
if (input_has_gamepad(widget->local_player_index))
{
ui_widget_delete(widget_instance_get_topmost_parent(widget));
widget_deleted = TRUE;
}
}
else
{
short controller_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
3107,
widget->local_player_index==NONE);
for (controller_index = 0;
controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
controller_index++)
{
if (input_has_gamepad(controller_index))
{
ui_widget_delete(widget_instance_get_topmost_parent(widget));
widget_deleted = TRUE;
break;
}
}
}
}
if (event_for_this_widget && !widget_deleted)
{
if (event->type == _event_type_button &&
event->data.button.value == 1)
{
boolean handled_by_event_handler = FALSE;
long handler_index;
if (event->data.button.index == _widget_event_back_button)
{
for (handler_index = 0;
handler_index < definition->event_handlers.count;
handler_index++)
{
struct ui_widget_event_handler_reference *handler =
(struct ui_widget_event_handler_reference *)
definition->event_handlers.address + handler_index;
if (handler->event_type == _widget_event_back_button)
{
handled_by_event_handler = TRUE;
break;
}
}
}
else if (event->data.button.index == _widget_event_b_button)
{
for (handler_index = 0;
handler_index < definition->event_handlers.count;
handler_index++)
{
struct ui_widget_event_handler_reference *handler =
(struct ui_widget_event_handler_reference *)
definition->event_handlers.address + handler_index;
if (handler->event_type == _widget_event_b_button)
{
handled_by_event_handler = TRUE;
break;
}
}
}
else
{
handled_by_event_handler = TRUE;
}
if (!handled_by_event_handler)
{
widget_instance_go_back_to_previous(widget);
audio_feedback = _ui_audio_feedback_back;
widget_deleted = TRUE;
event_handled = TRUE;
}
}
}
if (!widget_deleted)
{
if (widget->milliseconds_to_auto_close > 0)
{
if (widget_globals.current_system_milliseconds - widget->creation_time >=
widget->auto_close_fade_time + widget->milliseconds_to_auto_close)
{
ui_widget_delete(widget_instance_get_topmost_parent(widget));
widget_deleted = TRUE;
}
else if (widget->auto_close_fade_time > 0)
{
long faded_milliseconds =
widget_globals.current_system_milliseconds -
widget->creation_time -
widget->milliseconds_to_auto_close;
if (faded_milliseconds > 0)
{
widget->alpha_modifier = 1.0f - (real)faded_milliseconds /
(real)widget->auto_close_fade_time;
}
}
}
}
if (!widget_deleted)
{
widget_instance_update_animation_parameters(widget);
if (widget->type == _ui_widget_type_spinner_list)
spinner_list_update(widget);
else if (widget->type == _ui_widget_type_column_list)
column_list_update(widget, definition);
if (event_for_this_widget)
{
if (!event_handled &&
TEST_FLAG(definition->flags, _widget_dpad_updown_tabs_thru_children_bit) &&
widget->focused_child &&
!widget_deleted)
{
if (event->type == _event_type_button &&
event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _widget_event_dpad_up:
widget_instance_tab_to_previous_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case _widget_event_dpad_down:
widget_instance_tab_to_next_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
else if (event->type == _event_type_left_stick)
{
switch (event->data.stick.y)
{
case SHORT_MIN:
widget_instance_tab_to_next_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case SHORT_MAX:
widget_instance_tab_to_previous_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
}
if (!event_handled &&
TEST_FLAG(definition->flags, _widget_dpad_leftright_tabs_thru_children_bit) &&
widget->focused_child &&
!widget_deleted)
{
if (event->type == _event_type_button &&
event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _widget_event_dpad_left:
widget_instance_tab_to_previous_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case _widget_event_dpad_right:
widget_instance_tab_to_next_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
else if (event->type == _event_type_left_stick)
{
switch (event->data.stick.x)
{
case SHORT_MIN:
widget_instance_tab_to_previous_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case SHORT_MAX:
widget_instance_tab_to_next_valid_widget(widget);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
}
if (TEST_FLAG(definition->flags, _widget_dpad_updown_tabs_thru_list_items_bit) &&
(widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list) &&
!event_handled &&
!widget_deleted)
{
if (event->type == _event_type_button &&
event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _widget_event_dpad_up:
widget_event_function_list_widget_goto_previous_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case _widget_event_dpad_down:
widget_event_function_list_widget_goto_next_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
else if (event->type == _event_type_left_stick)
{
switch (event->data.stick.y)
{
case SHORT_MIN:
widget_event_function_list_widget_goto_next_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case SHORT_MAX:
widget_event_function_list_widget_goto_previous_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
}
if (TEST_FLAG(definition->flags, _widget_dpad_leftright_tabs_thru_list_items_bit) &&
(widget->type == _ui_widget_type_spinner_list ||
widget->type == _ui_widget_type_column_list) &&
!event_handled &&
!widget_deleted)
{
if (event->type == _event_type_button &&
event->data.button.value == 1)
{
switch (event->data.button.index)
{
case _widget_event_dpad_left:
widget_event_function_list_widget_goto_previous_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case _widget_event_dpad_right:
widget_event_function_list_widget_goto_next_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
else if (event->type == _event_type_left_stick)
{
switch (event->data.stick.x)
{
case SHORT_MIN:
widget_event_function_list_widget_goto_previous_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
case SHORT_MAX:
widget_event_function_list_widget_goto_next_item(
widget,
event,
&widget_deleted);
if (audio_feedback == _ui_audio_feedback_none)
audio_feedback = _ui_audio_feedback_cursor;
event_handled = TRUE;
break;
}
}
}
}
}
if (event_for_this_widget)
{
long handler_index;
for (handler_index = 0;
handler_index < definition->event_handlers.count;
handler_index++)
{
struct ui_widget_event_handler_reference *handler;
boolean handler_matches = FALSE;
if (widget_deleted)
break;
handler = (struct ui_widget_event_handler_reference *)
definition->event_handlers.address + handler_index;
switch (event->type)
{
case _event_type_left_stick:
switch (handler->event_type)
{
case _widget_event_left_stick_up:
handler_matches = event->data.stick.y == SHORT_MAX;
break;
case _widget_event_left_stick_down:
handler_matches = event->data.stick.y == SHORT_MIN;
break;
case _widget_event_left_stick_left:
handler_matches = event->data.stick.x == SHORT_MIN;
break;
case _widget_event_left_stick_right:
handler_matches = event->data.stick.x == SHORT_MAX;
break;
}
break;
case _event_type_right_stick:
switch (handler->event_type)
{
case _widget_event_right_stick_up:
handler_matches = event->data.stick.y == SHORT_MAX;
break;
case _widget_event_right_stick_down:
handler_matches = event->data.stick.y == SHORT_MIN;
break;
case _widget_event_right_stick_left:
handler_matches = event->data.stick.x == SHORT_MIN;
break;
case _widget_event_right_stick_right:
handler_matches = event->data.stick.x == SHORT_MAX;
break;
}
break;
case _event_type_button:
handler_matches = handler->event_type == event->data.button.index &&
event->data.button.value == 1;
break;
}
if (handler_matches)
{
event_handled = TRUE;
event_handler_dispatch(
widget,
definition,
event,
handler,
&widget_deleted);
}
}
}
match_vwarn(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
3477,
!TEST_FLAG(definition->flags, _widget_pass_handled_events_to_all_children_bit) ||
TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit),
"if the _widget_pass_handled_events_to_all_children_bit flag is checked, _widget_pass_unhandled_events_to_children_bit must also be checked for it to work");
if ((TEST_FLAG(definition->flags, _widget_pass_handled_events_to_all_children_bit) ||
!event_handled) &&
(TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_all_children_bit)) &&
!widget_deleted)
{
if (TEST_FLAG(definition->flags, _widget_pass_unhandled_events_to_all_children_bit))
{
struct widget_instance *child;
for (child = widget->child; child; child = child->next)
{
if (widget_takes_events_of_controller(child, event->controller_index))
{
widget_instance_process_one_event_recursive(
child,
ui_widget_definition_get(child->definition_tag_index),
event,
&widget_deleted);
if (widget_deleted == TRUE)
break;
}
}
}
else if (widget->focused_child)
{
if (widget_takes_events_of_controller(widget->focused_child, event->controller_index))
{
widget_instance_process_one_event_recursive(
widget->focused_child,
ui_widget_definition_get(
widget->focused_child->definition_tag_index),
event,
&widget_deleted);
}
}
}
if (widget_deleted == TRUE &&
TEST_FLAG(definition->flags, _widget_return_to_main_menu_if_no_history_bit))
{
long widget_index;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
if (widget_globals.active_widgets[widget_index])
break;
}
if (widget_index == MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)
main_goto_main_menu();
}
if (event->type == _event_type_button &&
event->data.button.value == 1 &&
event->controller_index >= 0 &&
event->controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS &&
event->data.button.index >= _widget_event_dpad_up &&
event->data.button.index <= _widget_event_dpad_right)
{
dpad_event_times[event->controller_index]
[event->data.button.index - _widget_event_dpad_up] =
widget_globals.current_system_milliseconds;
}
ui_play_audio_feedback_sound(audio_feedback);
*return_widget_deleted = widget_deleted;
return;
}
static boolean ui_check_for_pause_game(
void)
{
boolean pause_pressed = FALSE;
boolean network_game = network_game_is_active();
short controller_index = NONE;
if (game_in_progress() &&
!cinematic_in_progress() &&
game_connection() != _game_connection_film_playback &&
!we_are_at_the_main_menu &&
widget_globals.pause_disabled_ticks == 0)
{
long gamepad_index;
for (gamepad_index = 0;
gamepad_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
gamepad_index++)
{
if (input_has_gamepad(gamepad_index) &&
local_player_exists(gamepad_index) &&
input_get_gamepad_state(gamepad_index)->
buttons[_gamepad_binary_button_start] == 1)
{
pause_pressed = TRUE;
controller_index = gamepad_index;
break;
}
}
}
if (pause_pressed)
{
boolean pressed_by_first_local_player = TRUE;
short local_player_count = 0;
short pressing_local_player_index = NONE;
short local_player_index;
for (local_player_index = local_player_get_next(NONE);
local_player_index != NONE;
local_player_index = local_player_get_next(local_player_index))
{
if (local_player_index == controller_index)
{
pressing_local_player_index = controller_index;
if (local_player_count >= 1)
pressed_by_first_local_player = FALSE;
}
local_player_count++;
}
if (network_game)
{
if (game_engine_allow_pause() &&
pressing_local_player_index == controller_index)
{
if (!widget_globals.active_widgets[controller_index])
{
struct network_game_client *client = global_network_game_client_get();
struct network_game *network_game_data =
network_game_client_get_game(client);
short machine_index =
network_game_client_get_machine_index(client);
char const *widget_name;
/* port: a campaign map has only the campaign's pause screen */
if (network_coop_active())
widget_name = "ui\\shell\\solo_game\\pause_game\\pause_game";
else switch (local_player_count)
{
case 1:
widget_name =
"ui\\shell\\multiplayer_game\\pause_game\\1p_pause_game";
break;
case 2:
widget_name =
"ui\\shell\\multiplayer_game\\pause_game\\2p_pause_game";
break;
case 3:
widget_name = pressed_by_first_local_player == TRUE
? "ui\\shell\\multiplayer_game\\pause_game\\2p_pause_game"
: "ui\\shell\\multiplayer_game\\pause_game\\4p_pause_game";
break;
case 4:
widget_name =
"ui\\shell\\multiplayer_game\\pause_game\\4p_pause_game";
break;
default:
error(
_error_silent,
"invalid local player count for multiplayer game");
widget_name = NULL;
break;
}
if (widget_name &&
!ui_widget_load_by_name_or_tag(
widget_name,
NONE,
NULL,
controller_index,
NONE,
NONE,
NONE))
{
error(
_error_silent,
"failed to load multiplayer pause game window");
}
}
else
{
ui_widget_delete(widget_globals.active_widgets[controller_index]);
}
}
}
else
{
switch (local_player_count)
{
case 0:
case 1:
if (widget_globals.active_widgets[controller_index])
{
if (game_time_get_paused() == TRUE)
ui_widgets_close_all();
/* port: (and a multiplayer map's own, below, which pauses
nothing, closes as in a multiplayer game) */
else if (tag_loaded(UI_WIDGET_DEFINITION_TAG, "ui\\shell\\solo_game\\pause_game\\pause_game") == NONE)
ui_widget_delete(widget_globals.active_widgets[controller_index]);
}
/* port: a multiplayer map played alone (New Game's MULTIPLAYER
maps) has no campaign pause screen, but its own (LEAVE GAME
goes to the main menu: network_game_remove_local_player) */
else if (tag_loaded(UI_WIDGET_DEFINITION_TAG, "ui\\shell\\solo_game\\pause_game\\pause_game") == NONE &&
tag_loaded(UI_WIDGET_DEFINITION_TAG, "ui\\shell\\multiplayer_game\\pause_game\\1p_pause_game") != NONE)
{
if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\multiplayer_game\\pause_game\\1p_pause_game",
NONE,
NULL,
controller_index,
NONE,
NONE,
NONE))
{
error(
_error_silent,
"failed to load multiplayer pause game window");
}
}
else if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\solo_game\\pause_game\\pause_game",
NONE,
NULL,
controller_index,
NONE,
NONE,
NONE))
{
error(
_error_silent,
"failed to load full screen pause game window");
}
break;
case 2:
if (widget_globals.active_widgets[controller_index])
{
if (game_time_get_paused() == TRUE)
ui_widgets_close_all();
}
else if (!game_time_get_paused())
{
if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\solo_game\\pause_game\\pause_game_split_screen",
NONE,
NULL,
controller_index,
NONE,
NONE,
NONE))
{
error(
_error_silent,
"failed to load split screen pause game window");
}
}
break;
default:
error(
_error_silent,
"the ui seems to be confused... assuming you are playing full-screen single player?");
if (!ui_widgets_active())
{
if (!ui_widget_load_by_name_or_tag(
"ui\\shell\\solo_game\\pause_game\\pause_game",
NONE,
NULL,
controller_index,
NONE,
NONE,
NONE))
{
error(
_error_silent,
"failed to load full screen pause game window");
}
}
else
{
ui_widgets_close_all();
}
break;
}
}
}
/* This runs once a frame, several frames per tick on the native builds
(port/linux/game/render_interpolation.c): count the lock down in 30 Hz
ticks of real time, not in frames. */
{
static real leftover_ticks = 0.f;
long ticks;
leftover_ticks += main_get_seconds_elapsed() * TICKS_PER_SECOND;
ticks = (long)leftover_ticks;
leftover_ticks -= (real)ticks;
widget_globals.pause_disabled_ticks =
FLOOR(widget_globals.pause_disabled_ticks - ticks, 0);
}
return pause_pressed;
}
void process_ui_widgets(
void)
{
boolean widgets_processed = FALSE;
boolean widget_deleted;
boolean any_modal_widget_active;
boolean pause_pressed;
boolean modal_widget_active[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
long widget_index;
match_assert(
"c:\\halo\\SOURCE\\interface\\ui_widget.c",
644,
widget_globals.initialized);
widget_globals.current_system_milliseconds = system_milliseconds();
ui_widgets_process_mouse();
if (ui_widget_port_press_controller != NONE)
{
event_manager_post_button(ui_widget_port_press_controller, ui_widget_port_press_button);
ui_widget_port_press_controller = NONE;
}
if (widget_globals.initialization_thread)
{
if (!thread_has_exited(widget_globals.initialization_thread))
return;
dispose_thread(widget_globals.initialization_thread);
widget_globals.initialization_thread = NULL;
ui_widgets_inhibit_processing(FALSE);
switch (widget_globals.filesystem_check_result)
{
case _file_system_check_result_not_enough_free_space:
if (bink_playback_in_progress())
bink_playback_stop();
display_error_abort_to_dashboard(
_error_hard_drive_not_enough_free_space,
TRUE);
return;
case _file_system_check_result_too_many_saved_games:
if (bink_playback_in_progress())
bink_playback_stop();
display_error_abort_to_dashboard(
_error_hard_drive_maximum_saved_game_files,
TRUE);
return;
}
return;
}
if (progress_bar_is_active())
return;
if (virtual_keyboard_active())
{
virtual_keyboard_process();
event_manager_flush();
return;
}
#ifdef HALO_ANDROID
ui_join_code_update();
#endif
if (attract_mode_should_start())
{
attract_mode_start();
return;
}
if (widget_globals.deferred_dashboard_error_code != NONE)
{
display_error_abort_to_dashboard(
widget_globals.deferred_dashboard_error_code,
widget_globals.deferred_dashboard_optional);
widget_globals.deferred_dashboard_error_code = NONE;
return;
}
if (widget_globals.deferred_errors[0].error_code != NONE ||
widget_globals.deferred_errors[1].error_code != NONE ||
widget_globals.deferred_errors[2].error_code != NONE ||
widget_globals.deferred_errors[3].error_code != NONE)
{
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
struct ui_widget_deferred_error *deferred_error =
&widget_globals.deferred_errors[widget_index];
if (deferred_error->error_code != NONE)
{
if (!we_are_at_the_main_menu &&
!network_game_is_active() &&
game_time_get() < DEFERRED_ERROR_DELAY_TICKS)
{
error(
_error_silent,
"waiting for %d ticks before displaying deferred errors",
DEFERRED_ERROR_DELAY_TICKS);
}
else
{
display_error(
deferred_error->error_code,
deferred_error->local_player_index,
deferred_error->modal,
deferred_error->pause_game_time);
deferred_error->error_code = NONE;
}
}
}
return;
}
pause_pressed = ui_check_for_pause_game();
any_modal_widget_active = FALSE;
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
modal_widget_active[widget_index] =
widget_globals.active_widgets[widget_index] &&
widget_globals.active_widgets[widget_index]->widget_is_error_dialog == TRUE;
any_modal_widget_active |= modal_widget_active[widget_index];
}
for (widget_index = 0;
widget_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS;
widget_index++)
{
struct widget_instance *widget = widget_globals.active_widgets[widget_index];
boolean process_widget;
if (modal_widget_active[widget_index] == TRUE)
process_widget = widget && widget->widget_is_error_dialog == TRUE;
else if (we_are_at_the_main_menu)
process_widget = widget && !any_modal_widget_active;
else
process_widget = widget != NULL;
if (process_widget == TRUE)
{
struct ui_widget_definition *definition =
ui_widget_definition_get(widget->definition_tag_index);
struct event_record event = {0};
if (widget_globals.processing_inhibited ||
!get_next_event(&event, widget->local_player_index))
{
/* the widget still gets one empty event so that its animation,
auto-close timer and fade keep running */
if (!pause_pressed)
{
event.controller_index = widget->local_player_index;
widget_instance_process_one_event_recursive(
widget,
definition,
&event,
&widget_deleted);
}
}
else
{
do
{
#ifdef HALO_ANDROID
if (ui_vr_options_take_event(widget, &event) || ui_join_code_take_event(widget, &event))
continue;
#endif
if (!pause_pressed)
{
widget_instance_process_one_event_recursive(
widget,
definition,
&event,
&widget_deleted);
if (widget_deleted == TRUE)
break;
}
if (widget != widget_globals.active_widgets[widget_index])
break;
}
while (get_next_event(&event, widget->local_player_index));
}
widgets_processed = TRUE;
if (!widget_globals.active_widgets[widget_index] &&
widget_globals.widget_stack[widget_index])
{
struct widget_stack_data data;
pop_widget(&widget_globals.widget_stack[widget_index], &data);
if (data.previous_widget_tag != NONE)
{
struct widget_instance *new_widget = ui_widget_load_by_name_or_tag(
NULL,
data.previous_widget_tag,
NULL,
data.local_player_index,
NONE,
NONE,
NONE);
if (new_widget)
{
widget_instance_set_focused_child_by_index(
data.focused_child_parent_widget_tag,
new_widget,
data.focused_child_index);
}
}
}
}
}
if (widgets_processed)
event_manager_flush();
return;
}