mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 12:00:26 +02:00
Push to talk becomes an action of the BUTTONS page (vr.button_talk), on an eighth button there, the free hand's grip (its default): it can trade places with any other action (the move stick's click, say, so that a player holding a weapon in both hands can talk). The free hand's grip as a button is pressed once held 0.15 s, at the threshold halo_vr_two_hand takes the foregrip at, and not at all while it holds the weapon's foregrip or the Warthog's wheel: taken in that time, the press is theirs until the grip opens. Before, the runtime's grip button (which closes before the foregrip's threshold) could open the microphone for a frame or two as the hand took the weapon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10766 lines
315 KiB
C
10766 lines
315 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 "custom_edition_maps.h" /* port: port/linux/game/custom_edition_maps.c */
|
|
#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);
|
|
/* port: whether this machine has, or is adding, a second player: co-op,
|
|
split screen, the lobby's (port/linux/game/menu_functions.c) */
|
|
unsigned char pc_menu_split_players(
|
|
void);
|
|
/* port: local_player_count, 0 before the players' globals are made
|
|
(source/game/players.c) */
|
|
short players_port_local_player_count(
|
|
void);
|
|
/* port: where in its widget, and how large, the menus draw a frame of
|
|
ui.map's that they scale (port/linux/game/menu_tags.c) */
|
|
boolean pc_menu_frame_placement(
|
|
struct bitmap_data const *bitmap,
|
|
short *x,
|
|
short *y,
|
|
short *width,
|
|
short *height);
|
|
/* port: the placeholder bitmap the menus draw a sprite of a high-res
|
|
texture from, or the sheet if it has none (port/linux/game/hud_hires_tags.c) */
|
|
struct bitmap_data const *hud_hires_sprite_bitmap(
|
|
struct bitmap_data const *sheet,
|
|
short sequence_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), above the Custom
|
|
Edition maps' display indices (custom_edition_maps.c keeps them below 0x7000) */
|
|
#define SPINNER_EXTRA_DESCRIPTION_BASE 0x7000
|
|
/* ... 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(¶meters, 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;
|
|
/* port: (rasterizer.h) */
|
|
parameters.alpha_weighted = FALSE;
|
|
parameters.framebuffer_blend_function = 0;
|
|
rasterizer_psuedo_dynamic_screen_quad_draw(¶meters, 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;
|
|
}
|
|
|
|
/* port: an error of the port's own text (as display_error_text_deferred's)
|
|
shown when the main menu is next loaded, as display_error_when_main_menu_loaded
|
|
shows one of the game's: a network game left for a reason the game has no
|
|
error for (a map missing: cache_files.c). The text is copied, when no error
|
|
is queued already. */
|
|
void display_error_text_when_main_menu_loaded(
|
|
wchar_t const *text)
|
|
{
|
|
static wchar_t queued_text[512];
|
|
|
|
if (widget_globals.main_menu_deferred_error_code != NONE)
|
|
{
|
|
error(_error_silent, "there is already an error message queued for display at the main menu; ignoring this one");
|
|
return;
|
|
}
|
|
ustrncpy(queued_text, text, NUMBEROF(queued_text) - 1);
|
|
queued_text[NUMBEROF(queued_text) - 1] = 0;
|
|
ui_widget_port_error_pending_text = queued_text;
|
|
widget_globals.main_menu_deferred_error_code = _error_cannot_create_saved_game_file_with_empty_name;
|
|
|
|
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))
|
|
{
|
|
/* port: an event of nothing, not none: a map's widget may name a
|
|
function for this handler that reads its event */
|
|
static struct event_record no_event;
|
|
boolean widget_deleted = FALSE;
|
|
boolean handled;
|
|
|
|
csmemset(&no_event, 0, sizeof(no_event));
|
|
no_event.controller_index = NONE;
|
|
handled = ui_widget_event_handler_function_invoke(
|
|
widget,
|
|
&no_event,
|
|
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))
|
|
{
|
|
/* port: the sprite from a high-res texture, if it has one */
|
|
bitmap = hud_hires_sprite_bitmap(bitmap, icon->sequence_index);
|
|
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;
|
|
unsigned long length;
|
|
|
|
/* port: no more than the buffer holds (the text is the map's: a string
|
|
list's or a hud message's; retail strings are up to 398 characters of
|
|
1024). A longer one is cut, said once. */
|
|
for (length = 0; length < NUMBEROF(string_data) - 1 && instring[length]; length++)
|
|
string_data[length] = instring[length];
|
|
string_data[length] = 0;
|
|
if (instring[length])
|
|
{
|
|
static boolean long_string_reported = FALSE;
|
|
|
|
if (!long_string_reported)
|
|
{
|
|
long_string_reported = TRUE;
|
|
error(
|
|
_error_silent,
|
|
"string of more than %d characters cut",
|
|
(long)NUMBEROF(string_data) - 1);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* port: the port's own error text (display_error_text_deferred), which may be
|
|
longer than a line of its dialog: broken at spaces (the strings' line break,
|
|
'\r') where a line would be wider than `width` in the font drawn with.
|
|
FALSE when a line is still wider (a word longer than a line). */
|
|
static boolean ui_widget_port_text_wrap(
|
|
wchar_t *text,
|
|
rectangle2d const *bounds,
|
|
short width)
|
|
{
|
|
long line_start = 0;
|
|
long last_space = NONE;
|
|
long index;
|
|
boolean fits = TRUE;
|
|
|
|
for (index = 0; ; index++)
|
|
{
|
|
wchar_t character = text[index];
|
|
|
|
if (character == L'\r' || character == L'\n')
|
|
{
|
|
line_start = index + 1;
|
|
last_space = NONE;
|
|
continue;
|
|
}
|
|
if (character == L' ' || character == 0)
|
|
{
|
|
wchar_t line[256];
|
|
long length = MIN(index - line_start, (long)NUMBEROF(line) - 1);
|
|
rectangle2d text_bounds;
|
|
rectangle2d cursor_bounds;
|
|
|
|
csmemcpy(line, text + line_start, length * sizeof(wchar_t));
|
|
line[length] = 0;
|
|
draw_unicode_string_compute_bounds(bounds, line, &text_bounds, &cursor_bounds);
|
|
if (cursor_bounds.x0 - bounds->x0 > width && last_space != NONE)
|
|
{
|
|
text[last_space] = L'\r';
|
|
line_start = last_space + 1;
|
|
/* (the line begun, the word just measured: wider alone?) */
|
|
length = MIN(index - line_start, (long)NUMBEROF(line) - 1);
|
|
csmemcpy(line, text + line_start, length * sizeof(wchar_t));
|
|
line[length] = 0;
|
|
draw_unicode_string_compute_bounds(bounds, line, &text_bounds, &cursor_bounds);
|
|
}
|
|
if (cursor_bounds.x0 - bounds->x0 > width)
|
|
fits = FALSE;
|
|
if (character == 0)
|
|
break;
|
|
last_space = index;
|
|
}
|
|
}
|
|
|
|
return fits;
|
|
}
|
|
|
|
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);
|
|
/* port: the port's own error text, wrapped to its dialog: the box its
|
|
background draws (narrower than the text box; its picture is padded to a
|
|
power of two, so a margin of the text's offset on each side and as much
|
|
again), and as tall as the text box less its offset above and as much
|
|
below (the dialog's footer). Text that would be taller, or wider (a word
|
|
longer than a line), is drawn in the menus' smaller font. */
|
|
if (widget == ui_widget_port_error_text_box && ui_widget_port_error_text)
|
|
{
|
|
struct bitmap_data *background = definition->background_bitmap.index != NONE ?
|
|
bitmap_group_get_bitmap_from_sequence(definition->background_bitmap.index, 0, 0) : NULL;
|
|
short width = (short)(definition->bounds.x1 - definition->bounds.x0);
|
|
short height = (short)(definition->bounds.y1 - definition->bounds.y0 - 2 * definition->vertical_offset);
|
|
wchar_t wrapped[512];
|
|
rectangle2d text_bounds;
|
|
rectangle2d cursor_bounds;
|
|
|
|
if (background && background->width > 0 && background->width < width)
|
|
width = background->width;
|
|
width = (short)(width - 4 * definition->horizontal_offset);
|
|
ustrncpy(wrapped, *text, NUMBEROF(wrapped) - 1);
|
|
wrapped[NUMBEROF(wrapped) - 1] = 0;
|
|
if (!ui_widget_port_text_wrap(wrapped, &bounds, width) ||
|
|
(draw_unicode_string_compute_bounds(&bounds, wrapped, &text_bounds, &cursor_bounds),
|
|
cursor_bounds.y1 - bounds.y0 > height))
|
|
{
|
|
long small_font_index = tag_loaded('font', "ui\\small_ui");
|
|
|
|
if (small_font_index != NONE)
|
|
{
|
|
font_index = small_font_index;
|
|
draw_string_set_draw_mode(font_index, NONE, justification, 0, &color);
|
|
}
|
|
}
|
|
ui_widget_port_text_wrap(*text, &bounds, width);
|
|
}
|
|
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(¶meters, 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,
|
|
¶meters,
|
|
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(¶meters, 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,
|
|
¶meters,
|
|
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;
|
|
}
|
|
|
|
/* port/linux/game/menu_functions.c's: a text box drawn, at the bounds its
|
|
text was drawn in (the lobby's speaker icons) */
|
|
void menu_functions_text_box_drawn(struct widget_instance *widget, rectangle2d const *bounds);
|
|
|
|
/* ---------- 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,
|
|
/* the band of a list's slot rows left of its first slot (a list showing
|
|
several items): a click steps the list back by one */
|
|
_ui_mouse_target_list_back,
|
|
/* the band right of its last slot: a click steps it forward by one */
|
|
_ui_mouse_target_list_forward
|
|
};
|
|
|
|
struct ui_mouse_target
|
|
{
|
|
struct widget_instance *widget;
|
|
rectangle2d bounds;
|
|
short kind;
|
|
short button_index;
|
|
/* where a value splits into previous / next: the middle of its box
|
|
before ui_mouse_widen_values widens it, so across the middle of the value
|
|
as drawn, where its arrows are, and up and down the middle of its row once
|
|
ui_mouse_merge_setting_rows has given it the row's height. A widened box
|
|
that a neighbour or the screen's edge holds to one side keeps it */
|
|
short split_x;
|
|
short split_y;
|
|
/* a button's parts, kept so its area can be settled once the whole frame's
|
|
buttons are known (ui_mouse_fit_button_targets) */
|
|
rectangle2d icon_bounds;
|
|
struct widget_instance *label;
|
|
rectangle2d label_bounds;
|
|
};
|
|
|
|
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 frame's targets are noted and settled (ui_mouse_fit_button_targets,
|
|
ui_mouse_merge_setting_rows, ui_mouse_widen_values) by one render only:
|
|
render_ui_widgets runs once per player's viewport, and once more for a
|
|
mirror (render.c), and a second render would note the same widgets again
|
|
and widen the values once more, which moves their split off the arrows.
|
|
Cleared with the targets, by ui_widgets_process_mouse */
|
|
static boolean ui_mouse_targets_settled = 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;
|
|
/* whether the pointer moved the focus last (the d-pad clears it): a list
|
|
then doesn't scroll on at its end, which the pointer, moving, would make it
|
|
do every frame (menu_functions.c) */
|
|
static boolean ui_mouse_focused_last = FALSE;
|
|
static short ui_mouse_hover_x, ui_mouse_hover_y;
|
|
static short ui_mouse_click_x, ui_mouse_click_y;
|
|
/* whether the latest pointer read was the touchscreen: the taller legend
|
|
areas, the merged setting rows and the widened values are for a finger only
|
|
(render_ui_widgets); the desktop mouse keeps their original areas */
|
|
static boolean ui_mouse_pointer_is_touch = FALSE;
|
|
|
|
/* a pointer coarser than a mouse: a finger, or (port) a VR controller's
|
|
ray, which points less steadily; the legend's buttons get the larger areas */
|
|
static boolean ui_mouse_pointer_is_coarse(
|
|
void)
|
|
{
|
|
#ifdef HALO_ANDROID
|
|
if (halo_vr_active())
|
|
return TRUE;
|
|
#endif
|
|
return ui_mouse_pointer_is_touch;
|
|
}
|
|
|
|
/* ---------- the debug view of the targets (debug.touch_targets)
|
|
|
|
Drawn by render_ui_widgets in the pass that noted the targets: the list is
|
|
only valid from the render that noted them to the next
|
|
ui_widgets_process_mouse, which clears it. */
|
|
|
|
#define UI_DEBUG_MARK_MILLISECONDS 3000
|
|
#define UI_DEBUG_KEYBOARD_RECTANGLES 96
|
|
|
|
struct ui_debug_mark
|
|
{
|
|
boolean shown;
|
|
short x, y;
|
|
unsigned long time;
|
|
};
|
|
|
|
static struct ui_debug_mark ui_debug_down_mark, ui_debug_tap_mark;
|
|
/* the frames ui_widgets_process_mouse has run, and the one the latest tap arrived in */
|
|
static long ui_debug_frame, ui_debug_click_frame;
|
|
|
|
void platform_log(char const *format, ...);
|
|
int config_boolean(char const *name);
|
|
|
|
/* whether debug.touch_targets is on; read once: settings do not change
|
|
while the game runs */
|
|
static boolean ui_debug_targets_enabled(
|
|
void)
|
|
{
|
|
static long enabled = NONE;
|
|
|
|
if (enabled == NONE)
|
|
enabled = config_boolean("debug.touch_targets") != 0;
|
|
|
|
return enabled != 0;
|
|
}
|
|
|
|
/* outlines a rectangle, 2 menu pixels thick, in menu coordinates; the
|
|
caller has the menus' centering offset on (render.c sets
|
|
halo_screen_ui_offset(TRUE) around it; adding the offset inside would
|
|
double it) */
|
|
static void ui_debug_draw_outline(
|
|
rectangle2d const *bounds,
|
|
pixel32 color)
|
|
{
|
|
rectangle2d edge;
|
|
|
|
edge = *bounds;
|
|
edge.y1 = edge.y0 + 2;
|
|
draw_quad(&edge, color);
|
|
edge = *bounds;
|
|
edge.y0 = edge.y1 - 2;
|
|
draw_quad(&edge, color);
|
|
edge = *bounds;
|
|
edge.x1 = edge.x0 + 2;
|
|
draw_quad(&edge, color);
|
|
edge = *bounds;
|
|
edge.x0 = edge.x1 - 2;
|
|
draw_quad(&edge, color);
|
|
|
|
return;
|
|
}
|
|
|
|
/* draws a cross of about 8 menu pixels at a mark that is still within its 3 seconds */
|
|
static void ui_debug_draw_mark(
|
|
struct ui_debug_mark const *mark,
|
|
pixel32 color)
|
|
{
|
|
rectangle2d arm;
|
|
|
|
if (!mark->shown ||
|
|
system_milliseconds() - mark->time > UI_DEBUG_MARK_MILLISECONDS)
|
|
{
|
|
return;
|
|
}
|
|
arm.x0 = mark->x - 4;
|
|
arm.x1 = mark->x + 4;
|
|
arm.y0 = mark->y - 1;
|
|
arm.y1 = mark->y + 1;
|
|
draw_quad(&arm, color);
|
|
arm.x0 = mark->x - 1;
|
|
arm.x1 = mark->x + 1;
|
|
arm.y0 = mark->y - 4;
|
|
arm.y1 = mark->y + 4;
|
|
draw_quad(&arm, color);
|
|
|
|
return;
|
|
}
|
|
|
|
/* outlines the targets the pointer code collected this frame and the last
|
|
finger-down and tap points; only for the render that notes the targets
|
|
(the first player's): a split-screen viewport that is not hit-tested shows
|
|
nothing or it would show outlines that no tap uses */
|
|
static void ui_debug_draw_targets(
|
|
boolean first_players_render)
|
|
{
|
|
long index;
|
|
|
|
if (!first_players_render || !ui_debug_targets_enabled())
|
|
return;
|
|
if (virtual_keyboard_active())
|
|
{
|
|
rectangle2d rectangles[UI_DEBUG_KEYBOARD_RECTANGLES];
|
|
long count = virtual_keyboard_target_rectangles(rectangles, UI_DEBUG_KEYBOARD_RECTANGLES);
|
|
|
|
for (index = 0; index < count; index++)
|
|
ui_debug_draw_outline(&rectangles[index], 0xc0ffffff);
|
|
}
|
|
else
|
|
{
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
/* in the order of enum ui_mouse_target_kind; the band's two sides
|
|
share orange, which no other kind uses */
|
|
static pixel32 const colors[] = { 0xc000ff00, 0xc04080ff, 0xc0ffff00, 0xc0ff0000, 0xc0ff8000, 0xc0ff8000 };
|
|
short kind = ui_mouse_targets[index].kind;
|
|
|
|
ui_debug_draw_outline(&ui_mouse_targets[index].bounds, colors[PIN(kind, 0, 5)]);
|
|
}
|
|
}
|
|
ui_debug_draw_mark(&ui_debug_down_mark, 0xffff00ff);
|
|
ui_debug_draw_mark(&ui_debug_tap_mark, 0xff00ffff);
|
|
|
|
return;
|
|
}
|
|
|
|
/* remembers a finger-down or tap point for the debug view, to show it
|
|
for 3 seconds */
|
|
static void ui_debug_set_mark(
|
|
struct ui_debug_mark *mark,
|
|
short x,
|
|
short y)
|
|
{
|
|
mark->shown = TRUE;
|
|
mark->x = x;
|
|
mark->y = y;
|
|
mark->time = system_milliseconds();
|
|
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void ui_mouse_list_directions(
|
|
struct widget_instance *widget,
|
|
short *back,
|
|
short *forward);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* notes a target that steps a list by one, unless it is empty or the
|
|
targets are full */
|
|
static void ui_mouse_note_list_step(
|
|
struct widget_instance *widget,
|
|
rectangle2d const *bounds,
|
|
short kind)
|
|
{
|
|
struct ui_mouse_target *target;
|
|
|
|
if (ui_mouse_target_count >= UI_MOUSE_MAXIMUM_TARGETS ||
|
|
bounds->x0 >= bounds->x1 ||
|
|
bounds->y0 >= bounds->y1)
|
|
{
|
|
return;
|
|
}
|
|
target = &ui_mouse_targets[ui_mouse_target_count++];
|
|
target->widget = widget;
|
|
target->bounds = *bounds;
|
|
target->kind = kind;
|
|
target->button_index = NONE;
|
|
/* as ui_mouse_note_target sets them for any target that is not a
|
|
button: only a value's split and a button's parts are read, and no stale
|
|
field of an earlier frame's target stays in the slot */
|
|
target->split_x = (bounds->x0 + bounds->x1) / 2;
|
|
target->split_y = (bounds->y0 + bounds->y1) / 2;
|
|
target->icon_bounds = *bounds;
|
|
target->label = NULL;
|
|
target->label_bounds = *bounds;
|
|
|
|
return;
|
|
}
|
|
|
|
/* notes the band that steps a list showing several items side by side:
|
|
left of the first slot (steps back), right of the last (steps forward). A
|
|
stock several-items list covers the whole 640x480 screen, so a tap beside
|
|
its slots cannot mean "step"; the band is limited to the slots' rows, and a
|
|
tap on the title, between slots or under them does nothing. The caller notes
|
|
nothing for the list itself, and widget_instance_render_recursive notes the
|
|
slots after this; ui_mouse_target_at takes the last target noted under a
|
|
point, so a slot would win over the band. A list the d-pad steps up and down
|
|
has no side to tap and gets no band */
|
|
static void ui_mouse_note_slot_band(
|
|
struct widget_instance *widget,
|
|
struct ui_widget_definition const *definition,
|
|
point2d offset)
|
|
{
|
|
struct widget_instance *child;
|
|
rectangle2d bounds;
|
|
rectangle2d slots;
|
|
short back, forward;
|
|
boolean found = FALSE;
|
|
|
|
ui_mouse_list_directions(widget, &back, &forward);
|
|
if (back != _widget_event_dpad_left)
|
|
return;
|
|
for (child = widget->child; child; child = child->next)
|
|
{
|
|
rectangle2d slot;
|
|
|
|
if (!child->visible)
|
|
continue;
|
|
/* where widget_instance_render_recursive draws the child: the
|
|
list's offset plus the child's own */
|
|
slot = ui_widget_definition_get(child->definition_tag_index)->bounds;
|
|
slot.x0 += offset.x + child->horizontal_offset;
|
|
slot.x1 += offset.x + child->horizontal_offset;
|
|
slot.y0 += offset.y + child->vertical_offset;
|
|
slot.y1 += offset.y + child->vertical_offset;
|
|
if (!found)
|
|
{
|
|
slots = slot;
|
|
found = TRUE;
|
|
}
|
|
else
|
|
{
|
|
slots.x0 = MIN(slots.x0, slot.x0);
|
|
slots.x1 = MAX(slots.x1, slot.x1);
|
|
slots.y0 = MIN(slots.y0, slot.y0);
|
|
slots.y1 = MAX(slots.y1, slot.y1);
|
|
}
|
|
}
|
|
if (!found)
|
|
return;
|
|
bounds.y0 = slots.y0;
|
|
bounds.y1 = slots.y1;
|
|
bounds.x0 = definition->bounds.x0 + offset.x;
|
|
bounds.x1 = slots.x0;
|
|
ui_mouse_note_list_step(widget, &bounds, _ui_mouse_target_list_back);
|
|
bounds.x0 = slots.x1;
|
|
bounds.x1 = definition->bounds.x1 + offset.x;
|
|
ui_mouse_note_list_step(widget, &bounds, _ui_mouse_target_list_forward);
|
|
|
|
return;
|
|
}
|
|
|
|
/* notes a widget as a tap target of this frame, if it is one: an item,
|
|
a value, a list slot, or a legend button (kept in parts that
|
|
ui_mouse_fit_button_targets settles into the tap area); a list showing
|
|
several items notes the band beside its slots */
|
|
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;
|
|
rectangle2d icon_bounds = bounds;
|
|
rectangle2d target_label_bounds = bounds;
|
|
struct widget_instance *target_label = NULL;
|
|
short button_index;
|
|
short kind;
|
|
|
|
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);
|
|
if (button_index != NONE)
|
|
{
|
|
/* the icon and its label: the text beginning just right of it */
|
|
struct widget_instance *sibling;
|
|
rectangle2d label_bounds = bounds;
|
|
struct widget_instance *label = NULL;
|
|
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;
|
|
label = sibling;
|
|
}
|
|
}
|
|
icon_bounds = bounds;
|
|
target_label = best_distance < most_distance ? label : NULL;
|
|
target_label_bounds = label_bounds;
|
|
}
|
|
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)
|
|
{
|
|
if (!widget_instance_can_receive_events(widget))
|
|
return;
|
|
/* a list showing several items is picked through them, and stepped
|
|
by the band beside them */
|
|
if (ui_mouse_list_shows_several(widget))
|
|
{
|
|
ui_mouse_note_slot_band(widget, definition, offset);
|
|
return;
|
|
}
|
|
if (!parent)
|
|
return;
|
|
kind = _ui_mouse_target_value;
|
|
}
|
|
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->split_x = (bounds.x0 + bounds.x1) / 2;
|
|
target->split_y = (bounds.y0 + bounds.y1) / 2;
|
|
target->icon_bounds = icon_bounds;
|
|
target->label = target_label;
|
|
target->label_bounds = target_label_bounds;
|
|
|
|
return;
|
|
}
|
|
|
|
/* makes a setting row its value: an item whose only value child is a value
|
|
stops being a target, and the value takes the row's height. A tap anywhere
|
|
else on the row would press A, which on a setting screen is ACCEPT. The
|
|
value keeps its width and left/right half rule. Only the value's parent is
|
|
the row; an item holding several values (a panel, a list of spinners) is not
|
|
a row and keeps its own area. Only exactly one value merges because growing
|
|
several values to the item's height would pile up boxes of which only the
|
|
last noted could be tapped. Runs after ui_mouse_fit_button_targets so the
|
|
legends are fitted against the whole row: a legend growing up into a row's
|
|
label area would press ACCEPT there */
|
|
static void ui_mouse_merge_setting_rows(
|
|
void)
|
|
{
|
|
long index;
|
|
long other;
|
|
long kept = 0;
|
|
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
struct ui_mouse_target *row = &ui_mouse_targets[index];
|
|
struct ui_mouse_target *only_value = NULL;
|
|
long value_count = 0;
|
|
|
|
if (row->kind != _ui_mouse_target_item)
|
|
continue;
|
|
for (other = 0; other < ui_mouse_target_count; other++)
|
|
{
|
|
struct ui_mouse_target *value = &ui_mouse_targets[other];
|
|
|
|
if (value->kind == _ui_mouse_target_value && value->widget->parent == row->widget)
|
|
{
|
|
only_value = value;
|
|
value_count++;
|
|
}
|
|
}
|
|
if (value_count == 1)
|
|
{
|
|
only_value->bounds.y0 = row->bounds.y0;
|
|
only_value->bounds.y1 = row->bounds.y1;
|
|
row->kind = NONE;
|
|
}
|
|
}
|
|
/* in place, in order: the last target noted wins an overlap */
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
if (ui_mouse_targets[index].kind != NONE)
|
|
ui_mouse_targets[kept++] = ui_mouse_targets[index];
|
|
}
|
|
ui_mouse_target_count = kept;
|
|
|
|
return;
|
|
}
|
|
|
|
/* doubles the width of each value (a setting) around its centre: its arrows
|
|
are small for a finger. Each half grows outward by half the box's width.
|
|
The noted[] snapshot holds the boxes as noted; every measure is against them,
|
|
not as already widened, so two neighbours never take each other's room
|
|
whatever the order they were noted in. A box is not held to its row (the
|
|
last arrow of a row sits near the row's end) but stops at the edge of a
|
|
non-button target beside it that it overlaps vertically, at the midpoint of
|
|
the gap to another value facing it, and at the menu's drawable area. A box
|
|
never shrinks. Where a limit holds one side back, the split between previous
|
|
and next stays at the original centre where the arrows are. Runs after
|
|
ui_mouse_merge_setting_rows, once the rows have gone, and once a frame */
|
|
static void ui_mouse_widen_values(
|
|
void)
|
|
{
|
|
rectangle2d noted[UI_MOUSE_MAXIMUM_TARGETS];
|
|
long index;
|
|
long other;
|
|
short screen_width = (short)halo_screen_width();
|
|
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
noted[index] = ui_mouse_targets[index].bounds;
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
struct ui_mouse_target *value = &ui_mouse_targets[index];
|
|
rectangle2d const *box = ¬ed[index];
|
|
short half = (short)((box->x1 - box->x0) / 2);
|
|
short left = (short)(box->x0 - half);
|
|
short right = (short)(box->x1 + half);
|
|
|
|
if (value->kind != _ui_mouse_target_value)
|
|
continue;
|
|
value->split_x = (box->x0 + box->x1) / 2;
|
|
value->split_y = (box->y0 + box->y1) / 2;
|
|
left = MAX(left, (short)(-(screen_width - 640) / 2));
|
|
right = MIN(right, (short)(640 + (screen_width - 640) / 2));
|
|
for (other = 0; other < ui_mouse_target_count; other++)
|
|
{
|
|
struct ui_mouse_target const *beside = &ui_mouse_targets[other];
|
|
rectangle2d const *beside_box = ¬ed[other];
|
|
boolean is_value = beside->kind == _ui_mouse_target_value;
|
|
|
|
if (other == index || beside->kind == _ui_mouse_target_button ||
|
|
beside_box->y0 >= box->y1 || beside_box->y1 <= box->y0)
|
|
{
|
|
continue;
|
|
}
|
|
if (beside_box->x1 <= box->x0)
|
|
left = MAX(left, is_value ? (short)((beside_box->x1 + box->x0) / 2) : beside_box->x1);
|
|
else if (beside_box->x0 >= box->x1)
|
|
right = MIN(right, is_value ? (short)((box->x1 + beside_box->x0) / 2) : beside_box->x0);
|
|
}
|
|
value->bounds.x0 = MIN(left, box->x0);
|
|
value->bounds.x1 = MAX(right, box->x1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* the right edge of a legend label's text as the game draws it, or NONE
|
|
when it cannot be measured (no text, no font, or icons in the string, which
|
|
the measuring does not account for) */
|
|
static short ui_mouse_label_text_right(
|
|
struct widget_instance *label,
|
|
rectangle2d const *label_bounds)
|
|
{
|
|
struct ui_widget_definition *definition = ui_widget_definition_get(label->definition_tag_index);
|
|
wchar_t const *text = label->parameters.text_box.text;
|
|
real_argb_color color = { 1.0f, 1.0f, 1.0f, 1.0f };
|
|
rectangle2d bounds = *label_bounds;
|
|
rectangle2d text_bounds;
|
|
rectangle2d cursor_bounds;
|
|
|
|
if (!text || !*text || definition->text_font.index == NONE ||
|
|
definition->justification < 0 || definition->justification >= NUMBER_OF_TEXT_JUSTIFICATIONS ||
|
|
string_has_icons_to_draw(text))
|
|
{
|
|
return NONE;
|
|
}
|
|
/* as widget_instance_render_text_box places the text */
|
|
bounds.x0 += definition->horizontal_offset;
|
|
bounds.y0 += definition->vertical_offset;
|
|
/* this leaves the label's font, justification and a white colour as the
|
|
global draw mode. That is harmless: it runs at the end of the widget
|
|
render, where the menu's own text has already left a menu font there, and
|
|
the menu, HUD, cinematic and terminal draws set their font before they
|
|
draw; the debug texts that set only a colour or format find a menu font
|
|
either way */
|
|
draw_string_set_draw_mode(definition->text_font.index, NONE, definition->justification, 0, &color);
|
|
draw_unicode_string_compute_bounds(&bounds, text, &text_bounds, &cursor_bounds);
|
|
|
|
return text_bounds.x1 == SHORT_MIN ? NONE : text_bounds.x1;
|
|
}
|
|
|
|
/* settles the areas of the frame's legend buttons: each reaches its label's
|
|
drawn text (the text box can be far wider), stops before the next legend's
|
|
icon on its row, and for a touchscreen only grows up to 8 units upward (not
|
|
past an item, value or list slot above it) and in the screen's bottom strip
|
|
runs down to the screen's edge (not past an item, value or list slot below
|
|
it); recomputed from the parts kept at noting, so running again changes nothing */
|
|
static void ui_mouse_fit_button_targets(
|
|
void)
|
|
{
|
|
long index;
|
|
long other;
|
|
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
struct ui_mouse_target *target = &ui_mouse_targets[index];
|
|
short text_right;
|
|
|
|
if (target->kind != _ui_mouse_target_button)
|
|
continue;
|
|
target->bounds = target->icon_bounds;
|
|
if (target->label)
|
|
{
|
|
target->bounds.x1 = MAX(target->bounds.x1, target->label_bounds.x1);
|
|
target->bounds.y0 = MIN(target->bounds.y0, target->label_bounds.y0);
|
|
target->bounds.y1 = MAX(target->bounds.y1, target->label_bounds.y1);
|
|
text_right = ui_mouse_label_text_right(target->label, &target->label_bounds);
|
|
if (text_right != NONE)
|
|
{
|
|
target->bounds.x1 = MAX(target->icon_bounds.x1, MIN(target->label_bounds.x1, text_right + 2));
|
|
}
|
|
}
|
|
target->bounds.x0 = target->icon_bounds.x0;
|
|
}
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
struct ui_mouse_target *target = &ui_mouse_targets[index];
|
|
|
|
if (target->kind != _ui_mouse_target_button)
|
|
continue;
|
|
for (other = 0; other < ui_mouse_target_count; other++)
|
|
{
|
|
struct ui_mouse_target const *next = &ui_mouse_targets[other];
|
|
|
|
if (other != index && next->kind == _ui_mouse_target_button &&
|
|
next->icon_bounds.x0 > target->icon_bounds.x0 &&
|
|
next->bounds.y0 < target->bounds.y1 && next->bounds.y1 > target->bounds.y0 &&
|
|
next->icon_bounds.x0 - 1 < target->bounds.x1)
|
|
{
|
|
target->bounds.x1 = MAX(next->icon_bounds.x0 - 1, target->icon_bounds.x1);
|
|
}
|
|
}
|
|
}
|
|
for (index = 0; index < ui_mouse_target_count; index++)
|
|
{
|
|
struct ui_mouse_target *target = &ui_mouse_targets[index];
|
|
short top;
|
|
|
|
if (target->kind != _ui_mouse_target_button || !ui_mouse_pointer_is_coarse())
|
|
continue;
|
|
if (target->bounds.y1 >= 400)
|
|
{
|
|
short bottom = 480;
|
|
|
|
/* running down to the screen's edge must not take the tap from an
|
|
item, value or list slot that a mod puts under the legend, as
|
|
growing upward must not above it: the legend is noted later, so it
|
|
would win the overlap */
|
|
for (other = 0; other < ui_mouse_target_count; other++)
|
|
{
|
|
struct ui_mouse_target const *below = &ui_mouse_targets[other];
|
|
|
|
if (below->kind != _ui_mouse_target_button &&
|
|
below->bounds.y0 >= target->bounds.y1 &&
|
|
below->bounds.x0 < target->bounds.x1 && below->bounds.x1 > target->bounds.x0)
|
|
{
|
|
bottom = MIN(bottom, below->bounds.y0);
|
|
}
|
|
}
|
|
target->bounds.y1 = bottom;
|
|
}
|
|
/* growing upward must not take the tap from an item, value or list
|
|
slot above the legend: those keep their areas, and the legend is noted
|
|
later, so it would win the overlap */
|
|
top = target->bounds.y0 - 8;
|
|
for (other = 0; other < ui_mouse_target_count; other++)
|
|
{
|
|
struct ui_mouse_target const *above = &ui_mouse_targets[other];
|
|
|
|
if (above->kind != _ui_mouse_target_button &&
|
|
above->bounds.y1 <= target->bounds.y0 &&
|
|
above->bounds.x0 < target->bounds.x1 && above->bounds.x1 > target->bounds.x0)
|
|
{
|
|
top = MAX(top, above->bounds.y1);
|
|
}
|
|
}
|
|
target->bounds.y0 = top;
|
|
}
|
|
|
|
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;
|
|
|
|
for (index = ui_mouse_target_count - 1; index >= 0; index--)
|
|
{
|
|
rectangle2d const *bounds = &ui_mouse_targets[index].bounds;
|
|
|
|
if (x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1)
|
|
return &ui_mouse_targets[index];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
ui_mouse_focused_last = TRUE;
|
|
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;
|
|
}
|
|
|
|
/* port: whether the pointer moved the menus' focus last, rather than the
|
|
d-pad (menu_functions.c's lists) */
|
|
boolean ui_widget_port_pointer_focused(
|
|
void)
|
|
{
|
|
return ui_mouse_focused_last;
|
|
}
|
|
|
|
/* 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; a touch drag skips a list showing one value at a time
|
|
(a setting), moving between the rows, so a drag never changes a value; that
|
|
takes a tap on one of its halves; the desktop wheel keeps the game's own
|
|
d-pad reach and steps a setting's value */
|
|
static struct widget_instance *ui_mouse_wheel_widget(
|
|
struct widget_instance *root,
|
|
boolean skip_settings)
|
|
{
|
|
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 (skip_settings && widget->type == _ui_widget_type_spinner_list && !ui_mouse_list_shows_several(widget))
|
|
continue;
|
|
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;
|
|
}
|
|
|
|
/* whether the focus passes over a child of a list, which gives a press no
|
|
place to land; the PC version's lists skip their labels and hidden rows
|
|
(widget_instance_port_is_label) at both ends and wrap past them, so
|
|
counting one would let a drag step past the last usable row */
|
|
static boolean ui_mouse_wheel_skips_child(
|
|
struct widget_instance *list,
|
|
struct widget_instance *child)
|
|
{
|
|
return list->type == _ui_widget_type_column_list && pc_menu_tag(list->definition_tag_index) &&
|
|
widget_instance_port_is_label(child);
|
|
}
|
|
|
|
/* how many presses of a d-pad button can step the focus before it would
|
|
wrap around to the other end; the game's lists wrap, so a long touch drag
|
|
would lap a short list: the drag stops at the ends; the press goes down from
|
|
the screen to the first widget on the focus's way that tabs in its direction
|
|
(widget_instance_process_one_event_recursive), and the ends are that
|
|
widget's; 0 at the end or for a setting (changed by clicks only), NONE when
|
|
no widget tabs that way */
|
|
static long ui_mouse_wheel_room(
|
|
struct widget_instance *root,
|
|
short button,
|
|
boolean forward)
|
|
{
|
|
boolean horizontal = button == _widget_event_dpad_left || button == _widget_event_dpad_right;
|
|
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);
|
|
boolean list = widget->type == _ui_widget_type_spinner_list || widget->type == _ui_widget_type_column_list;
|
|
struct widget_instance *child;
|
|
long room = 0;
|
|
|
|
if (TEST_FLAG(definition->flags, horizontal ?
|
|
_widget_dpad_leftright_tabs_thru_children_bit :
|
|
_widget_dpad_updown_tabs_thru_children_bit) &&
|
|
widget->focused_child)
|
|
{
|
|
/* a child the tab functions would skip gives no press a place to
|
|
land, so only the ones that take events, pass them on, or sit in
|
|
a list count */
|
|
for (child = forward ? widget->focused_child->next : widget->focused_child->previous;
|
|
child;
|
|
child = forward ? child->next : child->previous)
|
|
{
|
|
struct ui_widget_definition *child_definition = ui_widget_definition_get(child->definition_tag_index);
|
|
|
|
if ((child_definition->event_handlers.count > 0 ||
|
|
TEST_FLAG(child_definition->flags, _widget_pass_unhandled_events_to_children_bit) ||
|
|
list) &&
|
|
!ui_mouse_wheel_skips_child(widget, child))
|
|
{
|
|
room++;
|
|
}
|
|
}
|
|
|
|
return room;
|
|
}
|
|
if (list && TEST_FLAG(definition->flags, horizontal ?
|
|
_widget_dpad_leftright_tabs_thru_list_items_bit :
|
|
_widget_dpad_updown_tabs_thru_list_items_bit))
|
|
{
|
|
/* a setting is changed by clicks only */
|
|
if (widget->type == _ui_widget_type_spinner_list && !ui_mouse_list_shows_several(widget))
|
|
return 0;
|
|
if (widget->parameters.list.list_items && widget->parameters.list.number_of_items > 0)
|
|
{
|
|
room = forward ?
|
|
widget->parameters.list.number_of_items - 1 - widget->parameters.list.selected_index :
|
|
widget->parameters.list.selected_index;
|
|
}
|
|
else if (widget->focused_child)
|
|
{
|
|
/* a list of the tag's children, which the focus walks; the
|
|
focus skips a disabled child to the next that takes events, or
|
|
wraps (widget_instance_give_focus_directly): the main menu ends
|
|
with its hidden "game demos" */
|
|
for (child = forward ? widget->focused_child->next : widget->focused_child->previous;
|
|
child;
|
|
child = forward ? child->next : child->previous)
|
|
{
|
|
if (!child->disabled && !ui_mouse_wheel_skips_child(widget, child))
|
|
room++;
|
|
}
|
|
if (forward && widget->parameters.list.number_of_items > 0)
|
|
{
|
|
room = MIN(room, widget->parameters.list.number_of_items - 1 -
|
|
widget->parameters.list.selected_index);
|
|
}
|
|
}
|
|
|
|
return MAX(room, 0);
|
|
}
|
|
}
|
|
|
|
return NONE;
|
|
}
|
|
|
|
/* 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);
|
|
int config_default(char const *name, char *text, size_t size);
|
|
|
|
enum
|
|
{
|
|
_vr_option_turning,
|
|
_vr_option_snap_angle,
|
|
_vr_option_smooth_speed,
|
|
_vr_option_vignette,
|
|
_vr_option_foot_vignette,
|
|
_vr_option_foot_vignette_strength,
|
|
_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_voice_chat,
|
|
/* (the game's actions on the buttons, HALO_VR_ACTION_*'s order) */
|
|
_vr_option_button_first,
|
|
_vr_option_button_last = _vr_option_button_first + HALO_VR_ACTION_COUNT - 1,
|
|
_vr_option_buttons_reset,
|
|
_vr_option_graphics,
|
|
_vr_option_foveation,
|
|
_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,
|
|
|
|
_vr_options_page_play = 0,
|
|
_vr_options_page_comfort,
|
|
_vr_options_page_buttons,
|
|
_vr_options_page_more,
|
|
NUMBER_OF_VR_OPTION_PAGES,
|
|
|
|
/* (the pages' tabs, beside the title, as wide as there is room) */
|
|
VR_OPTIONS_TABS_LEFT = 216,
|
|
VR_OPTIONS_TABS_RIGHT = 528,
|
|
VR_OPTIONS_TAB_WIDTH = (VR_OPTIONS_TABS_RIGHT - VR_OPTIONS_TABS_LEFT) / NUMBER_OF_VR_OPTION_PAGES,
|
|
|
|
_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", "COMFORT", "BUTTONS", "MORE" };
|
|
static short const vr_option_pages[NUMBER_OF_VR_OPTION_PAGES][VR_OPTIONS_PER_PAGE] =
|
|
{
|
|
{ _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_wheel,
|
|
_vr_option_voice_chat, NONE, NONE, NONE },
|
|
{ _vr_option_turning, _vr_option_snap_angle, _vr_option_smooth_speed, _vr_option_vignette,
|
|
_vr_option_foot_vignette, _vr_option_foot_vignette_strength,
|
|
NONE, NONE, NONE, NONE, NONE, NONE },
|
|
{ _vr_option_button_first + HALO_VR_ACTION_JUMP, _vr_option_button_first + HALO_VR_ACTION_LIGHT,
|
|
_vr_option_button_first + HALO_VR_ACTION_ACTION, _vr_option_button_first + HALO_VR_ACTION_WEAPON,
|
|
_vr_option_button_first + HALO_VR_ACTION_CROUCH, _vr_option_button_first + HALO_VR_ACTION_ZOOM,
|
|
_vr_option_button_first + HALO_VR_ACTION_MELEE, _vr_option_button_first + HALO_VR_ACTION_TALK,
|
|
_vr_option_buttons_reset, _vr_option_controls, NONE, NONE },
|
|
{ _vr_option_graphics, _vr_option_foveation, _vr_option_refresh_rate, _vr_option_resolution, _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");
|
|
}
|
|
|
|
/* Where a button the game's actions go on (HALO_VR_INPUT_*) is, as the
|
|
hands are set: its controller (0 the left, 1 the right) and its name,
|
|
short under its controller on the controls page, or full in the
|
|
options' rows. On the Quest A and B are on the right, X and Y on the
|
|
left, and mirrored the other way round; on the Steam Frame all four are
|
|
on the right, and mirrored the left D-pad takes them (down, left, right,
|
|
up). The sticks' clicks change sides with them; the grip is the weapon
|
|
hand's, the free grip the other hand's. */
|
|
static char const *ui_vr_input_name(
|
|
short input,
|
|
boolean full,
|
|
short *column)
|
|
{
|
|
static char const *const buttons[] = { "A", "B", "X", "Y" };
|
|
static char const *const quest_mirrored[] = { "X", "Y", "A", "B" };
|
|
static char const *const frame_mirrored[] = { "D-pad down", "D-pad left", "D-pad right", "D-pad up" };
|
|
boolean frame = halo_vr_controller_layout() == 1;
|
|
boolean mirrored = ui_vr_controls_mirrored();
|
|
short side;
|
|
char const *name;
|
|
|
|
switch (input)
|
|
{
|
|
case HALO_VR_INPUT_MOVE_STICK:
|
|
case HALO_VR_INPUT_TURN_STICK:
|
|
side = (short)((input == HALO_VR_INPUT_TURN_STICK) != (mirrored != 0));
|
|
name = full ? (side ? "Right stick click" : "Left stick click") : "Stick click";
|
|
break;
|
|
case HALO_VR_INPUT_GRIP:
|
|
case HALO_VR_INPUT_FREE_GRIP:
|
|
side = (short)((input == HALO_VR_INPUT_FREE_GRIP) != (ui_vr_left_handed() == 0));
|
|
name = full ? (side ? "Right grip" : "Left grip") : "Grip";
|
|
break;
|
|
default:
|
|
input = (short)PIN(input, HALO_VR_INPUT_A, HALO_VR_INPUT_Y);
|
|
if (frame)
|
|
{
|
|
side = (short)(mirrored ? 0 : 1);
|
|
name = mirrored ? frame_mirrored[input] : buttons[input];
|
|
}
|
|
else
|
|
{
|
|
side = (short)((input <= HALO_VR_INPUT_B) != (mirrored != 0));
|
|
name = mirrored ? quest_mirrored[input] : buttons[input];
|
|
}
|
|
break;
|
|
}
|
|
if (column)
|
|
*column = side;
|
|
return name;
|
|
}
|
|
|
|
/* the game's actions on the buttons (HALO_VR_ACTION_*'s order): as the
|
|
options' rows name them, and in short (the Steam Frame's stick lines) */
|
|
static char const *const vr_action_names[HALO_VR_ACTION_COUNT] =
|
|
{
|
|
"Jump", "Light; held: grenades", "Action, reload", "Switch weapon", "Crouch", "Zoom", "Melee",
|
|
"Push to talk",
|
|
};
|
|
static char const *const vr_action_short_names[HALO_VR_ACTION_COUNT] =
|
|
{
|
|
"jump", "light", "action", "weapon", "crouch", "zoom", "melee", "talk",
|
|
};
|
|
|
|
/* an action moved to the next button or the one before: the action that
|
|
was there takes its old one (so that every action keeps a button) */
|
|
static void ui_vr_options_button_change(
|
|
short action,
|
|
short change)
|
|
{
|
|
short input = (short)halo_vr_action_input(action);
|
|
short wanted = (short)((input + change + HALO_VR_INPUT_COUNT) % HALO_VR_INPUT_COUNT);
|
|
short other;
|
|
|
|
for (other = 0; other < HALO_VR_ACTION_COUNT; other++)
|
|
{
|
|
if (other != action && halo_vr_action_input(other) == wanted)
|
|
config_write(halo_vr_action_setting(other), halo_vr_input_value(input));
|
|
}
|
|
config_write(halo_vr_action_setting(action), halo_vr_input_value(wanted));
|
|
}
|
|
|
|
static boolean ui_vr_options_buttons_default(
|
|
void)
|
|
{
|
|
short action;
|
|
|
|
for (action = 0; action < HALO_VR_ACTION_COUNT; action++)
|
|
{
|
|
if (halo_vr_action_input(action) != action)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void ui_vr_options_buttons_reset(
|
|
void)
|
|
{
|
|
short action;
|
|
|
|
for (action = 0; action < HALO_VR_ACTION_COUNT; action++)
|
|
{
|
|
char text[32];
|
|
|
|
if (config_default(halo_vr_action_setting(action), text, sizeof(text)))
|
|
config_write(halo_vr_action_setting(action), text);
|
|
}
|
|
}
|
|
|
|
void platform_request_quit(void);
|
|
|
|
/* (in finer steps than they had: the old values among them) */
|
|
static real const vr_snap_angles[] = { 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 35.0f, 40.0f, 45.0f, 60.0f, 75.0f, 90.0f };
|
|
static real const vr_smooth_speeds[] = { 30.0f, 45.0f, 60.0f, 75.0f, 90.0f, 105.0f, 120.0f, 135.0f, 150.0f, 180.0f,
|
|
210.0f, 240.0f, 270.0f, 300.0f };
|
|
static real const vr_vignettes[] = { 0.0f, 0.15f, 0.25f, 0.35f, 0.5f, 0.6f, 0.7f, 0.85f, 1.0f };
|
|
/* the on-foot vignette's strengths (vr.foot_vignette_strength: the
|
|
vignettes' without Off), and vr.foot_vignette's values */
|
|
static real const vr_foot_vignette_strengths[] = { 0.15f, 0.25f, 0.35f, 0.5f, 0.6f, 0.7f, 0.85f, 1.0f };
|
|
static char const *const vr_foot_vignettes[] = { "off", "turning", "moving" };
|
|
static char const *const vr_foot_vignette_names[] = { "Off", "Smooth turns", "Moving, turning" };
|
|
|
|
static short ui_vr_options_foot_vignette(
|
|
void)
|
|
{
|
|
char const *mode = config_string("vr.foot_vignette");
|
|
short index;
|
|
|
|
for (index = 0; index < (short)NUMBEROF(vr_foot_vignettes); index++)
|
|
{
|
|
if (mode && strcmp(mode, vr_foot_vignettes[index]) == 0)
|
|
return index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* a vignette's strength as the rows show it: Off, or how far it darkens */
|
|
static void ui_vr_options_vignette_text(
|
|
char *text,
|
|
size_t size,
|
|
real strength)
|
|
{
|
|
if (strength <= 0.0f)
|
|
snprintf(text, size, "Off");
|
|
else
|
|
snprintf(text, size, "%.0f%%", strength * 100.0f);
|
|
}
|
|
/* vr.body's values, and as the panel shows them */
|
|
static char const *const vr_bodies[] = { "full", "arms", "hands", "off" };
|
|
static char const *const vr_body_names[] = { "Full body", "Arms and hands", "Hands only", "Weapon only" };
|
|
/* audio.voice_chat's (network_voice.c); push to talk's button is on the
|
|
BUTTONS page (vr.button_talk) */
|
|
static char const *const vr_voice_chats[] = { "push_to_talk", "open_mic", "off" };
|
|
static char const *const vr_voice_chat_names[] = { "Push to talk", "Open mic", "Off" };
|
|
|
|
static short ui_vr_options_voice_chat(
|
|
void)
|
|
{
|
|
char const *voice = config_string("audio.voice_chat");
|
|
short index;
|
|
|
|
for (index = 0; index < (short)NUMBEROF(vr_voice_chats); index++)
|
|
{
|
|
if (voice && strcmp(voice, vr_voice_chats[index]) == 0)
|
|
return index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/* 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", "auto" };
|
|
static char const *const vr_graphics_names[] = { "Low", "Medium", "High", "Max", "Auto" };
|
|
static real const vr_resolution_scales[] = { 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f };
|
|
|
|
/* vr.foveation's (vr.c: the Quest's GPU only) */
|
|
static char const *const vr_foveation_levels[] = { "off", "low", "medium" };
|
|
static char const *const vr_foveation_names[] = { "Off", "Low", "Medium" };
|
|
|
|
static short ui_vr_options_foveation(
|
|
void)
|
|
{
|
|
char const *foveation = config_string("vr.foveation");
|
|
short index;
|
|
|
|
for (index = 0; index < (short)NUMBEROF(vr_foveation_levels); index++)
|
|
{
|
|
if (foveation && strcmp(foveation, vr_foveation_levels[index]) == 0)
|
|
return index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
/* (the default, vr.c's halo_vr_graphics) */
|
|
return 1;
|
|
}
|
|
|
|
/* 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 1; /* (vr.c: anything else is "arms") */
|
|
}
|
|
|
|
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; off the
|
|
headset with debug.vr_options, to check the pages) */
|
|
return (halo_vr_active() || config_boolean("debug.vr_options")) && 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_foot_vignette:
|
|
config_write("vr.foot_vignette", vr_foot_vignettes[(ui_vr_options_foot_vignette() + change +
|
|
(short)NUMBEROF(vr_foot_vignettes)) % (short)NUMBEROF(vr_foot_vignettes)]);
|
|
break;
|
|
case _vr_option_foot_vignette_strength:
|
|
ui_vr_options_step("vr.foot_vignette_strength", vr_foot_vignette_strengths,
|
|
NUMBEROF(vr_foot_vignette_strengths), 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_voice_chat:
|
|
config_write("audio.voice_chat", vr_voice_chats[(ui_vr_options_voice_chat() + change +
|
|
(short)NUMBEROF(vr_voice_chats)) % (short)NUMBEROF(vr_voice_chats)]);
|
|
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_foveation:
|
|
config_write("vr.foveation", vr_foveation_levels[(ui_vr_options_foveation() + change +
|
|
(short)NUMBEROF(vr_foveation_levels)) % (short)NUMBEROF(vr_foveation_levels)]);
|
|
break;
|
|
case _vr_option_refresh_rate:
|
|
{
|
|
real rates[16], current;
|
|
short chosen;
|
|
short count = ui_vr_options_refresh_rates(rates, &chosen, ¤t);
|
|
|
|
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_buttons_reset:
|
|
ui_vr_options_buttons_reset();
|
|
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;
|
|
}
|
|
if (ui_vr_options_globals.selection >= _vr_option_button_first &&
|
|
ui_vr_options_globals.selection <= _vr_option_button_last)
|
|
{
|
|
ui_vr_options_button_change((short)(ui_vr_options_globals.selection - _vr_option_button_first), change);
|
|
}
|
|
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_globals.selection != _vr_option_buttons_reset)
|
|
{
|
|
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 >= VR_OPTIONS_TABS_LEFT && x < VR_OPTIONS_TABS_RIGHT)
|
|
{
|
|
/* (a tab: its page) */
|
|
short tab = (short)PIN((x - VR_OPTIONS_TABS_LEFT) / VR_OPTIONS_TAB_WIDTH, 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++;
|
|
}
|
|
}
|
|
|
|
/* the action on a button (vr.button_*; the swaps keep one on each) */
|
|
static short ui_vr_input_action(
|
|
short input)
|
|
{
|
|
short action;
|
|
|
|
for (action = 0; action < HALO_VR_ACTION_COUNT; action++)
|
|
{
|
|
if (halo_vr_action_input(action) == input)
|
|
return action;
|
|
}
|
|
return input;
|
|
}
|
|
|
|
/* in a vehicle: the action button gets in and out, the light's is the
|
|
flashlight */
|
|
static void ui_vr_controls_add_vehicle_actions(
|
|
struct ui_vr_controls_column columns[2])
|
|
{
|
|
short column;
|
|
char const *name = ui_vr_input_name((short)halo_vr_action_input(HALO_VR_ACTION_ACTION), FALSE, &column);
|
|
|
|
ui_vr_controls_add(&columns[column], name, "Get in, get out");
|
|
name = ui_vr_input_name((short)halo_vr_action_input(HALO_VR_ACTION_LIGHT), FALSE, &column);
|
|
ui_vr_controls_add(&columns[column], name, "Flashlight");
|
|
}
|
|
|
|
/* an action's line under the controller its button is on (vr.button_*), as
|
|
the Quest's page words it or, shorter, the Steam Frame's (whose page has
|
|
more lines) */
|
|
static void ui_vr_controls_add_action(
|
|
struct ui_vr_controls_column columns[2],
|
|
short action,
|
|
boolean frame)
|
|
{
|
|
/* (one action is held: its name lives on with the column) */
|
|
static char held[24];
|
|
short column;
|
|
char const *name = ui_vr_input_name((short)halo_vr_action_input(action), FALSE, &column);
|
|
|
|
switch (action)
|
|
{
|
|
case HALO_VR_ACTION_LIGHT:
|
|
if (frame)
|
|
{
|
|
ui_vr_controls_add(&columns[column], name, "Light; held: grenades");
|
|
}
|
|
else
|
|
{
|
|
snprintf(held, sizeof(held), "%s held", strcmp(name, "Stick click") == 0 ? "Click" : name);
|
|
ui_vr_controls_add(&columns[column], name, "Flashlight");
|
|
ui_vr_controls_add(&columns[column], held, "Switch grenades");
|
|
}
|
|
break;
|
|
case HALO_VR_ACTION_MELEE:
|
|
if (frame)
|
|
{
|
|
ui_vr_controls_add(&columns[column], name, "Melee (or punch)");
|
|
}
|
|
else
|
|
{
|
|
ui_vr_controls_add(&columns[column], name, "Melee (or punch");
|
|
ui_vr_controls_add(&columns[column], "", "forward)");
|
|
}
|
|
break;
|
|
default:
|
|
ui_vr_controls_add(&columns[column], name, vr_action_names[action]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* the free hand's grip: the weapon in both hands at its foregrip, else the
|
|
action on it (vr.button_talk's push to talk by default) */
|
|
static void ui_vr_controls_add_free_grip(
|
|
struct ui_vr_controls_column columns[2],
|
|
short free_column)
|
|
{
|
|
static char line[32];
|
|
short action = ui_vr_input_action(HALO_VR_INPUT_FREE_GRIP);
|
|
|
|
if (action != HALO_VR_ACTION_TALK)
|
|
snprintf(line, sizeof(line), "Both hands; else %s", vr_action_short_names[action]);
|
|
else
|
|
snprintf(line, sizeof(line), "%s", ui_vr_options_voice_chat() == 0 ? "Both hands; else talk" :
|
|
"Weapon in both hands");
|
|
ui_vr_controls_add(&columns[free_column], "Grip", line);
|
|
}
|
|
|
|
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). The sticks'
|
|
clicks on the sticks' lines, to keep to the page's lines */
|
|
static char move_line[32], turn_line[32];
|
|
|
|
switch (ui_vr_options_globals.page)
|
|
{
|
|
case _vr_controls_on_foot:
|
|
snprintf(move_line, sizeof(move_line), "Move; click: %s",
|
|
vr_action_short_names[ui_vr_input_action(HALO_VR_INPUT_MOVE_STICK)]);
|
|
snprintf(turn_line, sizeof(turn_line), "Turn; click: %s",
|
|
vr_action_short_names[ui_vr_input_action(HALO_VR_INPUT_TURN_STICK)]);
|
|
ui_vr_controls_add(&columns[move], "Stick", move_line);
|
|
ui_vr_controls_add(&columns[turn], "Stick", turn_line);
|
|
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
|
ui_vr_controls_add(&columns[free_column], "Trigger", "Grenade: hold, throw");
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_GRIP), TRUE);
|
|
ui_vr_controls_add_free_grip(columns, free_column);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_A), TRUE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_B), TRUE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_X), TRUE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_Y), TRUE);
|
|
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_vehicle_actions(columns);
|
|
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 */
|
|
switch (ui_vr_options_globals.page)
|
|
{
|
|
case _vr_controls_on_foot:
|
|
ui_vr_controls_add(&columns[move], "Stick", "Move");
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_MOVE_STICK), FALSE);
|
|
ui_vr_controls_add(&columns[turn], "Stick", "Turn");
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_TURN_STICK), FALSE);
|
|
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_GRIP), FALSE);
|
|
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_free_grip(columns, free_column);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_A), FALSE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_B), FALSE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_X), FALSE);
|
|
ui_vr_controls_add_action(columns, ui_vr_input_action(HALO_VR_INPUT_Y), FALSE);
|
|
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_vehicle_actions(columns);
|
|
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_on_foot:
|
|
/* (its notes are with its lines, above) */
|
|
break;
|
|
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, VR_OPTIONS_TABS_LEFT, 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)(VR_OPTIONS_TABS_LEFT + tab * VR_OPTIONS_TAB_WIDTH);
|
|
|
|
if (tab == page)
|
|
ui_join_code_draw_quad(x0, 68, (short)(x0 + VR_OPTIONS_TAB_WIDTH - 4), 88, 0x803A6EA5);
|
|
ui_join_code_draw_text(x0, (short)(x0 + VR_OPTIONS_TAB_WIDTH - 4), 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 vignette";
|
|
ui_vr_options_vignette_text(value, sizeof(value), vr_vignettes[ui_vr_options_nearest(
|
|
(real)config_real("vr.comfort_vignette"), vr_vignettes, NUMBEROF(vr_vignettes))]);
|
|
break;
|
|
case _vr_option_foot_vignette:
|
|
label = "On-foot vignette";
|
|
snprintf(value, sizeof(value), "%s", vr_foot_vignette_names[ui_vr_options_foot_vignette()]);
|
|
/* (smooth turns alone, with snap turning: nothing to darken) */
|
|
unused = ui_vr_options_foot_vignette() == 1 && !smooth;
|
|
break;
|
|
case _vr_option_foot_vignette_strength:
|
|
label = "On-foot vignette strength";
|
|
ui_vr_options_vignette_text(value, sizeof(value), vr_foot_vignette_strengths[ui_vr_options_nearest(
|
|
(real)config_real("vr.foot_vignette_strength"), vr_foot_vignette_strengths,
|
|
NUMBEROF(vr_foot_vignette_strengths))]);
|
|
unused = ui_vr_options_foot_vignette() == 0;
|
|
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 = strcmp(vr_bodies[ui_vr_options_body()], "off") == 0;
|
|
break;
|
|
case _vr_option_voice_chat:
|
|
label = "Voice chat";
|
|
snprintf(value, sizeof(value), "%s", vr_voice_chat_names[ui_vr_options_voice_chat()]);
|
|
break;
|
|
case _vr_option_graphics:
|
|
label = "Graphics";
|
|
snprintf(value, sizeof(value), "%s", vr_graphics_names[ui_vr_options_graphics()]);
|
|
break;
|
|
case _vr_option_foveation:
|
|
/* (dim where the GPU has none: the Steam Frame's) */
|
|
label = "Foveation";
|
|
snprintf(value, sizeof(value), "%s", vr_foveation_names[ui_vr_options_foveation()]);
|
|
unused = !halo_vr_foveation_supported();
|
|
break;
|
|
case _vr_option_refresh_rate:
|
|
{
|
|
real rates[16], current;
|
|
short chosen;
|
|
short count = ui_vr_options_refresh_rates(rates, &chosen, ¤t);
|
|
|
|
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_buttons_reset:
|
|
label = "Reset buttons to defaults";
|
|
unused = ui_vr_options_buttons_default();
|
|
break;
|
|
case _vr_option_controls:
|
|
label = "Controller buttons...";
|
|
break;
|
|
default:
|
|
if (option >= _vr_option_button_first && option <= _vr_option_button_last)
|
|
{
|
|
short action = (short)(option - _vr_option_button_first);
|
|
|
|
label = vr_action_names[action];
|
|
snprintf(value, sizeof(value), "%s", ui_vr_input_name((short)halo_vr_action_input(action), TRUE,
|
|
NULL));
|
|
}
|
|
break;
|
|
case _vr_option_quit:
|
|
label = ui_vr_options_globals.quit_armed && ui_vr_options_globals.selection == _vr_option_quit ?
|
|
"Quit the game? Once more to quit" : "Quit the game";
|
|
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 == _vr_options_page_comfort)
|
|
{
|
|
/* (what the vignettes do) */
|
|
ui_join_code_draw_text(128, 512, 248, "The vignette darkens the view from its edges while the", FALSE, 0,
|
|
&dim_color);
|
|
ui_join_code_draw_text(128, 512, 266, "game moves you: in a vehicle as it turns you; on foot, if", FALSE, 0,
|
|
&dim_color);
|
|
ui_join_code_draw_text(128, 512, 284, "on, as the sticks turn you smoothly (and move you).", FALSE, 0,
|
|
&dim_color);
|
|
}
|
|
if (page == _vr_options_page_buttons)
|
|
{
|
|
/* (what the buttons' rows do) */
|
|
ui_join_code_draw_text(128, 512, 302, "An action moved to a button swaps with the one there;", FALSE, 0,
|
|
&dim_color);
|
|
ui_join_code_draw_text(128, 512, 320, "left-handed and mirrored, the buttons are mirrored too.", FALSE, 0,
|
|
&dim_color);
|
|
ui_join_code_draw_text(128, 512, 338, "The menus keep their buttons.", FALSE, 0, &dim_color);
|
|
}
|
|
if (page == _vr_options_page_more)
|
|
{
|
|
/* (what the graphics rows do) */
|
|
ui_join_code_draw_text(128, 512, 230, "Graphics: Auto leaves effects out while the GPU falls behind;", FALSE,
|
|
0, &dim_color);
|
|
ui_join_code_draw_text(128, 512, 248, "Medium and Low always (shadows, reflections, flares; grass...).",
|
|
FALSE, 0, &dim_color);
|
|
ui_join_code_draw_text(128, 512, 266, "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(128, 512, 320, "Foveation draws the view's edges coarser (Quest only).", 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
|
|
|
|
/* 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));
|
|
}
|
|
|
|
/* logs a tap the menus resolved: where it was and the target the click acts
|
|
on; a click waits for the presses the mouse queued, so it is resolved
|
|
against the targets of a later frame than the one the tap arrived in; the
|
|
frames between are logged so the target can be read against the screen that
|
|
showed then; a value's line also gives the point where it splits into
|
|
previous and next */
|
|
static void ui_debug_log_click(
|
|
short x,
|
|
short y,
|
|
struct ui_mouse_target const *target,
|
|
long frames)
|
|
{
|
|
/* in the order of enum ui_mouse_target_kind */
|
|
static char const *const kinds[] = { "item", "value", "list slot", "button", "list back", "list forward" };
|
|
|
|
if (target)
|
|
{
|
|
char const *kind = kinds[PIN(target->kind, 0, 5)];
|
|
char const *name = tag_get_name(target->widget->definition_tag_index);
|
|
|
|
/* a value steps by the side of its split the tap is on, and once
|
|
widened the split is not the middle of the logged box
|
|
(ui_mouse_widen_values) */
|
|
if (target->kind == _ui_mouse_target_value)
|
|
{
|
|
platform_log("touch targets: tap at %d,%d hit %s %s [%d,%d,%d,%d] split %d,%d after %ld frames", x, y,
|
|
kind, name, target->bounds.x0, target->bounds.y0, target->bounds.x1, target->bounds.y1,
|
|
target->split_x, target->split_y, frames);
|
|
}
|
|
else
|
|
{
|
|
platform_log("touch targets: tap at %d,%d hit %s %s [%d,%d,%d,%d] after %ld frames", x, y,
|
|
kind, name, target->bounds.x0, target->bounds.y0, target->bounds.x1, target->bounds.y1, frames);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
platform_log("touch targets: tap at %d,%d hit none after %ld frames", x, y, frames);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* logs a tap while the virtual keyboard is up: the key or legend that took it */
|
|
static void ui_debug_log_keyboard_tap(
|
|
short x,
|
|
short y,
|
|
long hit)
|
|
{
|
|
rectangle2d rectangles[UI_DEBUG_KEYBOARD_RECTANGLES];
|
|
long count = virtual_keyboard_target_rectangles(rectangles, UI_DEBUG_KEYBOARD_RECTANGLES);
|
|
|
|
if (hit == NONE || hit >= count)
|
|
{
|
|
platform_log("touch targets: tap at %d,%d hit no keyboard key", x, y);
|
|
}
|
|
else
|
|
{
|
|
platform_log("touch targets: tap at %d,%d hit keyboard %s %ld [%d,%d,%d,%d]", x, y,
|
|
hit == count - 2 ? "BACK legend" : hit == count - 1 ? "ENTER legend" : "key",
|
|
hit, rectangles[hit].x0, rectangles[hit].y0, rectangles[hit].x1, rectangles[hit].y1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* turns the pointer's motion, clicks and wheel since the last frame into the
|
|
first player's controller events; while the virtual keyboard is up it gets
|
|
the clicks and the menu behind gets nothing; a touch drag stops at the list's
|
|
ends and skips settings; the desktop wheel wraps as the d-pad does; it
|
|
records whether the pointer read is the touchscreen because the legends'
|
|
taller tap areas are for a finger only; with debug.touch_targets on it also
|
|
notes the finger-down and tap points for the debug view, and logs each tap: a
|
|
menu click where it is resolved, a keyboard click where it is taken; a click
|
|
on the band beside a list's slots steps the list by one; a PC list's row is
|
|
selected by its first click and used by a click on the selected row; it
|
|
forgets the frame's targets at the end, so that the next frame's render notes
|
|
and settles them anew */
|
|
static void ui_widgets_process_mouse(
|
|
void)
|
|
{
|
|
struct halo_ui_pointer pointer;
|
|
struct ui_mouse_target *target;
|
|
short controller_index = 0;
|
|
boolean pointer_active;
|
|
boolean keyboard_active;
|
|
|
|
ui_debug_frame++;
|
|
#ifdef HALO_ANDROID
|
|
/* port: in VR the keyboard takes the controller's pointer itself, the
|
|
key under it highlighted (virtual_keyboard.c) */
|
|
if (halo_vr_active() && virtual_keyboard_active())
|
|
pointer_active = FALSE;
|
|
else
|
|
#endif
|
|
pointer_active = halo_ui_pointer_update(ui_mouse_menus_active(), &pointer) != 0;
|
|
if (pointer_active)
|
|
ui_mouse_pointer_is_touch = pointer.touch != 0;
|
|
if (pointer_active && ui_debug_targets_enabled())
|
|
{
|
|
if (pointer.downs)
|
|
ui_debug_set_mark(&ui_debug_down_mark, pointer.down_x, pointer.down_y);
|
|
if (pointer.left_clicks)
|
|
{
|
|
ui_debug_set_mark(&ui_debug_tap_mark, pointer.click_x, pointer.click_y);
|
|
ui_debug_click_frame = ui_debug_frame;
|
|
}
|
|
}
|
|
/* the virtual keyboard takes the pointer's clicks itself; a click that
|
|
closes it (Done) is not also a click on the menu behind */
|
|
keyboard_active = virtual_keyboard_active();
|
|
if (pointer_active && keyboard_active && pointer.left_clicks)
|
|
{
|
|
long hit;
|
|
|
|
virtual_keyboard_click(pointer.click_x, pointer.click_y, &hit);
|
|
if (ui_debug_targets_enabled())
|
|
ui_debug_log_keyboard_tap(pointer.click_x, pointer.click_y, hit);
|
|
}
|
|
if (!pointer_active || keyboard_active)
|
|
{
|
|
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))
|
|
{
|
|
/* (the frame's targets forgotten all the same, as below) */
|
|
ui_mouse_target_count = 0;
|
|
ui_mouse_targets_settled = FALSE;
|
|
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())
|
|
{
|
|
boolean touch = pointer.touch != 0;
|
|
struct widget_instance *widget = ui_mouse_wheel_widget(ui_mouse_menu(), touch);
|
|
boolean going_forward = pointer.wheel_steps < 0;
|
|
short back = _widget_event_dpad_up, forward = _widget_event_dpad_down, button;
|
|
long room = NONE;
|
|
long index;
|
|
long step;
|
|
|
|
if (widget)
|
|
ui_mouse_list_directions(widget, &back, &forward);
|
|
/* a sideways PC widget (typically a screen's button bar) steps
|
|
sideways only, and the d-pad's up and down pass over it to a widget
|
|
above it on the focus's way, so a touch drag leaves it the same way */
|
|
if (touch && widget && back == _widget_event_dpad_left && pc_menu_tag(widget->definition_tag_index) &&
|
|
ui_mouse_wheel_room(ui_mouse_menu(), _widget_event_dpad_up, FALSE) != NONE)
|
|
{
|
|
back = _widget_event_dpad_up;
|
|
forward = _widget_event_dpad_down;
|
|
}
|
|
button = going_forward ? forward : back;
|
|
/* only a touch drag stops at the ends, and does nothing with no list
|
|
to step; the desktop wheel wraps as the d-pad does, and falls back
|
|
to up and down */
|
|
if (touch)
|
|
{
|
|
room = widget ? ui_mouse_wheel_room(ui_mouse_menu(), button, going_forward) : 0;
|
|
/* the presses still waiting step the list first */
|
|
for (index = 0; widget && room != NONE && index < ui_mouse_press_count; index++)
|
|
{
|
|
if (ui_mouse_presses[index] == button)
|
|
room = MAX(room - 1, 0);
|
|
else if (ui_mouse_presses[index] == (going_forward ? back : forward))
|
|
room++;
|
|
}
|
|
}
|
|
for (step = 0; step < ABS(pointer.wheel_steps) && step < 4 && (room == NONE || step < room); step++)
|
|
ui_mouse_press(button);
|
|
}
|
|
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;
|
|
/* stepping is a click's action: hovering a band does nothing, so
|
|
that passing a finger over a list's side never moves its focus or
|
|
its items */
|
|
case _ui_mouse_target_list_back:
|
|
case _ui_mouse_target_list_forward:
|
|
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 (ui_debug_targets_enabled())
|
|
ui_debug_log_click(ui_mouse_click_x, ui_mouse_click_y, target, ui_debug_frame - ui_debug_click_frame);
|
|
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->split_x :
|
|
ui_mouse_click_y < target->split_y;
|
|
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;
|
|
case _ui_mouse_target_list_back:
|
|
case _ui_mouse_target_list_forward:
|
|
{
|
|
short back, forward;
|
|
|
|
ui_mouse_give_focus(target->widget);
|
|
/* as ui_mouse_step_list_to_slot: a list with no focused item
|
|
(empty, or one the focus left, which cleared its focused_child)
|
|
has no item to step from, so the first tap on its band only
|
|
gives it the focus, and a d-pad press would move the focus off
|
|
the list instead */
|
|
if (!target->widget->focused_child)
|
|
break;
|
|
ui_mouse_list_directions(target->widget, &back, &forward);
|
|
ui_mouse_press(target->kind == _ui_mouse_target_list_back ? back : forward);
|
|
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;
|
|
ui_mouse_targets_settled = FALSE;
|
|
|
|
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;
|
|
/* port: (custom_edition_maps_picture) */
|
|
struct bitmap_data *custom_edition_picture;
|
|
short frame_index;
|
|
|
|
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);
|
|
/* port: a Custom Edition map's picture, drawn over the whole widget, or
|
|
the unknown level's frame for a map without one
|
|
(port/linux/game/custom_edition_maps.c) */
|
|
frame_index = widget->animation.current_frame_index;
|
|
custom_edition_picture = custom_edition_maps_picture(definition->background_bitmap.index, &frame_index);
|
|
bitmap = custom_edition_picture ? custom_edition_picture : bitmap_group_get_bitmap_from_sequence(
|
|
definition->background_bitmap.index,
|
|
0,
|
|
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);
|
|
{
|
|
/* port: a frame of ui.map's that the menus scale (the Xbox's
|
|
picture of the button settings, in the profile settings' smaller
|
|
box): drawn at their size, from where they place it, in units of
|
|
that size rather than one to a texel. A Custom Edition map's own
|
|
picture is stretched over the widget; a stock campaign level's is
|
|
laid out as the stock pictures are */
|
|
rectangle2d texels = bounds;
|
|
short frame_x, frame_y, frame_width, frame_height;
|
|
boolean shown = TRUE;
|
|
boolean stretched = custom_edition_picture &&
|
|
custom_edition_maps_campaign_level(widget->animation.current_frame_index) == NONE;
|
|
|
|
if (pc_menu_frame_placement(bitmap, &frame_x, &frame_y, &frame_width, &frame_height))
|
|
{
|
|
bounds.x0 += frame_x;
|
|
bounds.y0 += frame_y;
|
|
texels.x0 = 0;
|
|
texels.y0 = 0;
|
|
texels.x1 = (short)((long)(bounds.x1 - bounds.x0) * bitmap->width / frame_width);
|
|
texels.y1 = (short)((long)(bounds.y1 - bounds.y0) * bitmap->height / frame_height);
|
|
shown = bounds.x1 > bounds.x0 && bounds.y1 > bounds.y0;
|
|
}
|
|
if (shown)
|
|
{
|
|
draw_bitmap_in_rect(
|
|
bitmap,
|
|
&bounds,
|
|
stretched ? NULL : &texels,
|
|
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));
|
|
/* port: what the menus draw beside a text (the lobby's speaker
|
|
icons: port/linux/game/menu_functions.c), its bounds as it was
|
|
drawn in them (widget_instance_render_text_box), its font and
|
|
justification still set */
|
|
{
|
|
rectangle2d text_bounds = definition->bounds;
|
|
|
|
offset_rectangle2d(&text_bounds, offset.x, offset.y);
|
|
text_bounds.x0 += definition->horizontal_offset;
|
|
text_bounds.y0 += definition->vertical_offset;
|
|
menu_functions_text_box_drawn(widget, &text_bounds);
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* renders the active widgets for one local player's viewport (or the whole
|
|
screen), noting and settling the first player's tap targets in the frame's
|
|
first render that can, and draws the debug view of the targets and the
|
|
virtual keyboard when they are up */
|
|
void render_ui_widgets(
|
|
short local_player_index,
|
|
rectangle2d const *window_bounds)
|
|
{
|
|
rectangle2d bounds;
|
|
long widget_index;
|
|
boolean first_players_render = local_player_index == NONE || local_player_index == 0;
|
|
|
|
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; a widget shown in
|
|
every viewport (a dialog for everyone) is noted in the first
|
|
player's render only, and once a frame */
|
|
ui_mouse_noting_targets = first_players_render && !ui_mouse_targets_settled &&
|
|
(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));
|
|
}
|
|
}
|
|
}
|
|
if (first_players_render && !ui_mouse_targets_settled)
|
|
{
|
|
/* fit the legends while the rows are still targets (merging
|
|
removes them); widen after merging (the merged values are
|
|
row-tall). A finger's targets only: the mouse clicks the
|
|
labels and values where CE's menus have them (port: a VR
|
|
controller's ray gets the wider values too, not the merged
|
|
rows: a click on a row's label presses A there, as before) */
|
|
ui_mouse_fit_button_targets();
|
|
if (ui_mouse_pointer_is_touch)
|
|
ui_mouse_merge_setting_rows();
|
|
if (ui_mouse_pointer_is_coarse())
|
|
ui_mouse_widen_values();
|
|
ui_mouse_targets_settled = TRUE;
|
|
}
|
|
#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();
|
|
}
|
|
ui_debug_draw_targets(first_players_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: with one person playing, any controller drives the first player's
|
|
menus, not only the first port's: a phone can list a device of its own (its
|
|
touch controls) before the gamepad, which then reads port 2, and a player
|
|
picks up whichever pad is at hand. Its screens read every controller's
|
|
events (process_ui_widgets), and take them
|
|
(widget_takes_events_of_controller). With two or more players, each
|
|
controller keeps to its own player's menus */
|
|
static boolean widget_takes_any_controller(
|
|
struct widget_instance const *widget)
|
|
{
|
|
return widget->local_player_index == 0 && !pc_menu_split_players() &&
|
|
(we_are_at_the_main_menu || players_port_local_player_count() <= 1);
|
|
}
|
|
|
|
/* port: the controller whose events a screen reads (NONE: every one's) */
|
|
static short widget_event_controller(
|
|
struct widget_instance const *widget)
|
|
{
|
|
return widget_takes_any_controller(widget) ? NONE : widget->local_player_index;
|
|
}
|
|
|
|
/* 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 (controller_index > 0 && controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS &&
|
|
widget_takes_any_controller(widget))
|
|
{
|
|
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.index >= _widget_event_dpad_up &&
|
|
event->data.button.index <= _widget_event_dpad_right)
|
|
{
|
|
ui_mouse_focused_last = FALSE;
|
|
}
|
|
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_event_controller(widget)))
|
|
{
|
|
/* 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_event_controller(widget)));
|
|
}
|
|
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;
|
|
}
|