mirror of
https://github.com/BanjoRecomp/BanjoRecomp
synced 2026-06-11 12:39:51 -04:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc687ab318 | |||
| 61842d980c | |||
| 6f53279cf0 | |||
| b4f8db80e2 | |||
| e1e786ef1b | |||
| d14731120b | |||
| ce12002c28 | |||
| 1ac45d11a6 | |||
| d8f8d056f5 | |||
| 77d720d640 | |||
| f7500550cd | |||
| 9fa9602772 | |||
| 261ed2b44b | |||
| e4a8338e25 | |||
| 01638373c0 | |||
| 61c842cbc8 | |||
| b2395b5d3a | |||
| ac51bee933 | |||
| 447d282eed | |||
| b887dd99e7 | |||
| b1f0f1b3ac | |||
| f8ba7ac002 | |||
| 7a14a33f55 | |||
| 78334bff14 | |||
| 1b933908c1 | |||
| a7a4aabeed | |||
| 9247fc6b43 |
+12
-1
@@ -168,7 +168,12 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_assign_players_modal.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_example.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_controls.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_controls_element.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_player_card.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
||||
@@ -182,17 +187,23 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_binding_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_icon_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_config_page.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pill_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_select.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_svg.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_theme.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp
|
||||
@@ -285,7 +296,7 @@ if (WIN32)
|
||||
PROPERTIES
|
||||
LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE"
|
||||
LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
|
||||
LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
|
||||
LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE" # "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
|
||||
LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
|
||||
)
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
}
|
||||
</style>
|
||||
<link type="text/template" href="config_menu/general.rml" />
|
||||
<link type="text/template" href="config_menu/controls.rml" />
|
||||
<link type="text/template" href="config_menu/graphics.rml" />
|
||||
<link type="text/template" href="config_menu/sound.rml" />
|
||||
<link type="text/template" href="config_menu/mods.rml" />
|
||||
@@ -47,8 +46,8 @@
|
||||
<div>Controls</div>
|
||||
<div class="tab__indicator"></div>
|
||||
</tab>
|
||||
<panel class="config" data-model="controls_model">
|
||||
<template src="config-menu__controls" />
|
||||
<panel class="config">
|
||||
<recomp-config-page-controls />
|
||||
</panel>
|
||||
<tab class="tab" id="tab_graphics">
|
||||
<div>Graphics</div>
|
||||
@@ -78,6 +77,13 @@
|
||||
<panel class="config" data-model="debug_model">
|
||||
<template src="config-menu__debug" />
|
||||
</panel>
|
||||
<tab class="tab" id="tab_example">
|
||||
<div>Example</div>
|
||||
<div class="tab__indicator"></div>
|
||||
</tab>
|
||||
<panel class="config">
|
||||
<recomp-config-page-example />
|
||||
</panel>
|
||||
</tabset>
|
||||
<div class="config__icon-buttons">
|
||||
<button
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
<template name="config-menu__controls">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<form class="config__form" data-attr-cur-input="cur_input_row" data-attr-cur-binding-slot="active_binding_slot">
|
||||
<div class="config__header">
|
||||
<div class="config__header-left">
|
||||
<button
|
||||
class="toggle"
|
||||
id="cont_kb_toggle"
|
||||
data-class-toggle--checked="input_device_is_keyboard"
|
||||
onclick="toggle_input_device"
|
||||
style="nav-down: #input_row_button_0_0; nav-up: #tab_controls"
|
||||
>
|
||||
<div class="toggle__border" />
|
||||
<div class="toggle__floater" />
|
||||
<div class="toggle__icons">
|
||||
<div class="toggle__icon toggle__icon--left"><div></div></div>
|
||||
<div class="toggle__icon toggle__icon--right"><div></div></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="button button--warning"
|
||||
style="nav-down:#input_row_button_0_0"
|
||||
data-event-click="reset_input_bindings_to_defaults"
|
||||
>
|
||||
<div class="button__label">Reset to defaults</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config__wrapper input-config">
|
||||
<div class="input-config__horizontal-split">
|
||||
<div class="input-config__mappings" data-event-mouseout="set_input_row_focus(-1)">
|
||||
<div class="input-config__mappings-scroll">
|
||||
<div class="input-config__mappings-wrapper">
|
||||
<div
|
||||
class="control-option"
|
||||
data-attr-id="'input_row_' + i"
|
||||
data-for="input_bindings, i : inputs.array"
|
||||
data-event-mouseover="set_input_row_focus(i)"
|
||||
data-class-control-option--active="get_input_enum_name(i)==cur_input_row"
|
||||
data-if="!input_device_is_keyboard || (get_input_enum_name(i) != 'TOGGLE_MENU' && get_input_enum_name(i) != 'ACCEPT_MENU' && get_input_enum_name(i) != 'APPLY_MENU')"
|
||||
>
|
||||
<label
|
||||
class="control-option__label"
|
||||
>{{get_input_name(i)}}</label>
|
||||
<div class="control-option__bindings">
|
||||
<button
|
||||
data-attr-id="'input_row_button_' + i + '_' + j"
|
||||
data-event-blur="set_input_row_focus(-1)"
|
||||
data-event-focus="set_input_row_focus(i)"
|
||||
data-for="cur_binding, j : input_bindings"
|
||||
data-event-click="set_input_binding(i,j)"
|
||||
class="prompt-font control-option__binding"
|
||||
data-attr-bind-slot="j"
|
||||
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
||||
>
|
||||
<div class="control-option__binding-recording">
|
||||
<div class="control-option__binding-circle" />
|
||||
<div class="control-option__binding-edge">
|
||||
<svg class="control-option__binding-edge-svg" src="icons/RecordBorder.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-option__binding-icon">{{cur_binding}}</div>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
data-if="get_input_enum_name(i) != 'TOGGLE_MENU' && get_input_enum_name(i) != 'ACCEPT_MENU'"
|
||||
data-event-blur="set_input_row_focus(-1)"
|
||||
data-event-focus="set_input_row_focus(i)"
|
||||
data-event-click="clear_input_bindings(i)"
|
||||
class="icon-button icon-button--danger"
|
||||
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
||||
>
|
||||
<svg src="icons/Trash.svg" />
|
||||
</button>
|
||||
<button
|
||||
data-if="get_input_enum_name(i) == 'TOGGLE_MENU' || get_input_enum_name(i) == 'ACCEPT_MENU'"
|
||||
data-event-blur="set_input_row_focus(-1)"
|
||||
data-event-focus="set_input_row_focus(i)"
|
||||
data-event-click="reset_single_input_binding_to_default(i)"
|
||||
class="icon-button icon-button--danger"
|
||||
data-attr-style="i == 0 ? 'nav-up:#cont_kb_toggle' : 'nav-up:auto'"
|
||||
>
|
||||
<svg src="icons/Reset.svg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-config__visual-wrapper">
|
||||
<div class="input-config__visual-aspect">
|
||||
<div class="input-config__visual">
|
||||
<!-- stick only -->
|
||||
<div class="input-config__visual-stick-wrapper">
|
||||
<div
|
||||
class="input-viz input-config__visual-stick"
|
||||
visual-input="X_AXIS_NEG X_AXIS_POS Y_AXIS_NEG Y_AXIS_POS"
|
||||
>
|
||||
<div class="input-viz__stick-split input-viz__stick-split--vertical">
|
||||
<div class="input-viz input-viz__mappings" visual-input="Y_AXIS_POS">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--up" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.Y_AXIS_POS"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__dpad-divider" />
|
||||
<div class="input-viz input-viz__mappings" visual-input="Y_AXIS_NEG">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--down" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.Y_AXIS_NEG"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__stick-split input-viz__stick-split--horizontal">
|
||||
<div class="input-viz input-viz__mappings" visual-input="X_AXIS_NEG">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--left" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.X_AXIS_NEG"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__dpad-divider" />
|
||||
<div class="input-viz input-viz__mappings" visual-input="X_AXIS_POS">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--right" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.X_AXIS_POS"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- top half -->
|
||||
<div class="input-config__visual-half">
|
||||
<div class="input-config__visual-quarter-left">
|
||||
<div
|
||||
class="input-viz input-viz__dpad"
|
||||
visual-input="DPAD_UP DPAD_DOWN DPAD_LEFT DPAD_RIGHT"
|
||||
>
|
||||
<svg src="icons/VizMap/DPad.svg" />
|
||||
<div class="input-viz__dpad-split input-viz__dpad-split--vertical">
|
||||
<div class="input-viz input-viz__mappings" visual-input="DPAD_UP">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--up" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.DPAD_UP"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__dpad-divider" />
|
||||
<div class="input-viz input-viz__mappings" visual-input="DPAD_DOWN">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--down" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.DPAD_DOWN"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__dpad-split input-viz__dpad-split--horizontal">
|
||||
<div class="input-viz input-viz__mappings" visual-input="DPAD_LEFT">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--left" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.DPAD_LEFT"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-viz__dpad-divider" />
|
||||
<div class="input-viz input-viz__mappings" visual-input="DPAD_RIGHT">
|
||||
<svg class="input-viz__dpad-arrow input-viz__dpad-arrow--right" src="icons/VizMap/DPadArrow.svg" />
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.DPAD_RIGHT"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-config__visual-quarter-right">
|
||||
<div class="input-config__main-buttons">
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--sm input-viz__button--Start"
|
||||
visual-input="START"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonSmall.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.START"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--lg input-viz__button--B"
|
||||
visual-input="B"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonLarge.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.B"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--lg input-viz__button--A"
|
||||
visual-input="A"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonLarge.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.A"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-config__c-buttons">
|
||||
<div class="input-config__c-buttons-lr">
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--md input-viz__button--C"
|
||||
visual-input="C_LEFT"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonMedium.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.C_LEFT"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--md input-viz__button--C"
|
||||
visual-input="C_RIGHT"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonMedium.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.C_RIGHT"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-config__c-buttons-du">
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--md input-viz__button--C"
|
||||
visual-input="C_DOWN"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonMedium.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.C_DOWN"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__button input-viz__button--sm input-viz__button--C"
|
||||
visual-input="C_UP"
|
||||
>
|
||||
<svg src="icons/VizMap/ButtonMedium.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.C_UP"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- bottom half -->
|
||||
<div class="input-config__visual-half input-config__visual-half--bottom">
|
||||
<div
|
||||
class="input-viz input-viz__Z"
|
||||
visual-input="Z"
|
||||
>
|
||||
<svg src="icons/VizMap/Target.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.Z"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__R"
|
||||
visual-input="R"
|
||||
>
|
||||
<svg src="icons/VizMap/Shield.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.R"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="input-viz input-viz__L"
|
||||
visual-input="L"
|
||||
>
|
||||
<svg src="icons/VizMap/Map.svg" />
|
||||
<div class="input-viz__mappings">
|
||||
<div
|
||||
class="input-config__visual-mapping"
|
||||
data-for="cur_binding, i : inputs.L"
|
||||
>
|
||||
<div>{{cur_binding}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 11L15.2929 22.2929C15.6834 22.6834 16.3166 22.6834 16.7071 22.2929L28 11" stroke="#FFFFFF" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.72625 23.3925C8.13475 23.0659 8.61021 22.3958 9.032 21.7129C9.78396 20.4953 11.0499 19.6669 12.4578 19.6669H19.5422C20.9501 19.6669 22.216 20.4953 22.968 21.7129C23.3898 22.3958 23.8653 23.0659 24.2738 23.3925C25.2857 24.2015 26.6008 24.2035 27.6585 23.3925C28.4268 22.8033 29 21.7232 29 20.2983C29 18.8735 28.6384 12.9609 27.6585 9.98045C26.6786 7 23.4285 7 16 7C8.57147 7 5.32145 7 4.34153 9.98045C3.36162 12.9609 3 18.8735 3 20.2983C3 21.7232 3.5732 22.8033 4.34153 23.3925C5.39916 24.2035 6.71431 24.2015 7.72625 23.3925Z" stroke="white" stroke-width="4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 676 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27 7C29.2091 7 31 8.79086 31 11V21C31 23.2091 29.2091 25 27 25H5C2.79086 25 1 23.2091 1 21V11C1 8.79086 2.79086 7 5 7H27ZM5 19V21H7V19H5ZM9 19V21H23V19H9ZM25 19V21H27V19H25ZM5 15V17H9V15H5ZM11 15V17H13V15H11ZM15 15V17H17V15H15ZM19 15V17H21V15H19ZM23 15V17H27V15H23ZM5 11V13H7V11H5ZM9 11V13H11V11H9ZM13 11V13H15V11H13ZM17 11V13H19V11H17ZM21 11V13H23V11H21ZM25 11V13H27V11H25Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 504 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 3C24.091 3.00019 28.7417 10.0042 28.9902 16.082C29.0372 17.2318 28.8162 18.5326 28.2207 19.4746C27.7079 20.2859 26.8271 21 25 21H7C5.17292 21 4.29117 20.2859 3.77832 19.4746C3.18306 18.5326 2.96285 17.2316 3.00977 16.082C3.25811 10.0041 7.90882 3 16 3Z" stroke="white" stroke-width="4" stroke-linecap="round"/>
|
||||
<circle cx="16" cy="14.5" r="2.5" fill="white"/>
|
||||
<circle cx="22.5" cy="14.5" r="2.5" fill="white"/>
|
||||
<circle cx="9.5" cy="14.5" r="2.5" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 577 B |
+28
-646
@@ -31,7 +31,7 @@ h3, .tab {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.label-md, .config-debug-option__label, .button, .config-option__title, .config-option--hz .config-option__title, .config-group__title, .control-option__label {
|
||||
.label-md, .config-debug-option__label, .button, .config-option__title, .config-option--hz .config-option__title, .config-group__title {
|
||||
font-size: 28dp;
|
||||
letter-spacing: 3.08dp;
|
||||
line-height: 28dp;
|
||||
@@ -81,7 +81,7 @@ h3, .tab {
|
||||
line-height: 40dp;
|
||||
}
|
||||
|
||||
.prompt-font-sm, .input-viz__mappings div {
|
||||
.prompt-font-sm {
|
||||
font-family: promptfont;
|
||||
font-size: 32dp;
|
||||
font-style: normal;
|
||||
@@ -103,21 +103,21 @@ h3, .tab {
|
||||
*/
|
||||
.nav-vert, .nav-dir, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
nav-up: auto;
|
||||
nav-down: auto;
|
||||
}
|
||||
|
||||
.nav-horiz, .nav-dir, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
nav-right: auto;
|
||||
nav-left: auto;
|
||||
}
|
||||
|
||||
.nav-foc, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
focus: auto;
|
||||
tab-index: auto;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ h3, .tab {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.label-md, .config-debug-option__label, .button, .config-option__title, .config-option--hz .config-option__title, .config-group__title, .control-option__label {
|
||||
.label-md, .config-debug-option__label, .button, .config-option__title, .config-option--hz .config-option__title, .config-group__title {
|
||||
font-size: 28dp;
|
||||
letter-spacing: 3.08dp;
|
||||
line-height: 28dp;
|
||||
@@ -208,7 +208,7 @@ h3, .tab {
|
||||
line-height: 40dp;
|
||||
}
|
||||
|
||||
.prompt-font-sm, .input-viz__mappings div {
|
||||
.prompt-font-sm {
|
||||
font-family: promptfont;
|
||||
font-size: 32dp;
|
||||
font-style: normal;
|
||||
@@ -230,21 +230,21 @@ h3, .tab {
|
||||
*/
|
||||
.nav-vert, .nav-dir, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
nav-up: auto;
|
||||
nav-down: auto;
|
||||
}
|
||||
|
||||
.nav-horiz, .nav-dir, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
nav-right: auto;
|
||||
nav-left: auto;
|
||||
}
|
||||
|
||||
.nav-foc, .nav-all, .config-debug__select-wrapper select selectbox option, .config-debug__select-wrapper select, .config-debug__select-wrapper input, .toggle, .subtitle-title:not(:disabled, [disabled]), .menu-list-item:not(:disabled, [disabled]), .icon-button:not([disabled]), .button:not([disabled]), .button, .config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option, .config-option-dropdown__select, .config-option-dropdown__wrapper, .config-option-textfield__select, .config-option-textfield__wrapper, .config-option__radio-tabs input.radio,
|
||||
.config-option__list input.radio, .config-option__radio-tabs .config-option__checkbox,
|
||||
.config-option__list .config-option__checkbox, .tab, .control-option__binding:not([disabled]) {
|
||||
.config-option__list .config-option__checkbox, .tab {
|
||||
focus: auto;
|
||||
tab-index: auto;
|
||||
}
|
||||
@@ -433,7 +433,7 @@ scrollbarhorizontal sliderbar {
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
border-width: 1.1dp;
|
||||
border-radius: 16dp;
|
||||
border-radius: 24dp;
|
||||
border-color: Border;
|
||||
background: ModalOverlay;
|
||||
}
|
||||
@@ -477,173 +477,6 @@ scrollbarhorizontal sliderbar {
|
||||
margin-right: 4dp;
|
||||
}
|
||||
|
||||
.control-option {
|
||||
color: TextDim;
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out, opacity 0.05s linear-in-out;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 4dp 16dp 4dp 20dp;
|
||||
border-radius: 8dp;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.control-option svg {
|
||||
image-color: TextDim;
|
||||
}
|
||||
.control-option svg {
|
||||
transition: image-color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
.control-option:focus-visible:not(:disabled, [disabled]), .control-option:hover:not(:disabled, [disabled]) {
|
||||
color: Text;
|
||||
background-color: BGOverlay;
|
||||
}
|
||||
.control-option:focus-visible:not(:disabled, [disabled]) svg, .control-option:hover:not(:disabled, [disabled]) svg {
|
||||
image-color: Text;
|
||||
}
|
||||
.control-option:disabled, .control-option[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
[cur-binding-slot="0"] .control-option--active .control-option__binding[bind-slot="0"] {
|
||||
border-color: Danger;
|
||||
}
|
||||
[cur-binding-slot="0"] .control-option--active .control-option__binding[bind-slot="0"] .control-option__binding-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
[cur-binding-slot="0"] .control-option--active .control-option__binding[bind-slot="0"] .control-option__binding-recording {
|
||||
opacity: 1;
|
||||
}
|
||||
[cur-binding-slot="1"] .control-option--active .control-option__binding[bind-slot="1"] {
|
||||
border-color: Danger;
|
||||
}
|
||||
[cur-binding-slot="1"] .control-option--active .control-option__binding[bind-slot="1"] .control-option__binding-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
[cur-binding-slot="1"] .control-option--active .control-option__binding[bind-slot="1"] .control-option__binding-recording {
|
||||
opacity: 1;
|
||||
}
|
||||
.control-option .icon-button {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.control-option__label {
|
||||
flex: 2 1 300dp;
|
||||
height: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.control-option__bindings {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 2 1 400dp;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 56dp;
|
||||
padding: 0 12dp 0 4dp;
|
||||
}
|
||||
|
||||
.control-option__binding {
|
||||
color: TextDim;
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out, opacity 0.05s linear-in-out, border-color 0.05s linear-in-out;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1 1 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 56dp;
|
||||
margin: 0 4dp;
|
||||
padding: 8dp;
|
||||
border-width: 1.1dp;
|
||||
border-radius: 8dp;
|
||||
border-color: BGOverlay;
|
||||
background-color: BGOverlay;
|
||||
}
|
||||
.control-option__binding svg {
|
||||
image-color: TextDim;
|
||||
}
|
||||
.control-option__binding svg {
|
||||
transition: image-color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
.control-option__binding:focus, .control-option__binding:hover {
|
||||
color: Text;
|
||||
border-color: Text;
|
||||
background-color: BorderSoft;
|
||||
}
|
||||
.control-option__binding:focus svg, .control-option__binding:hover svg {
|
||||
image-color: Text;
|
||||
}
|
||||
.control-option__binding:active {
|
||||
color: TextActive;
|
||||
}
|
||||
.control-option__binding:active svg {
|
||||
image-color: TextActive;
|
||||
}
|
||||
.control-option__binding:disabled, .control-option__binding[disabled] {
|
||||
color: TextDim;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.control-option__binding:disabled svg, .control-option__binding[disabled] svg {
|
||||
image-color: TextDim;
|
||||
}
|
||||
.control-option__binding:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-option__binding-icon {
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out, opacity 0.05s linear-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes control-option__binding-recording-scale {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.control-option__binding-recording {
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out, opacity 0.05s linear-in-out;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
}
|
||||
.control-option__binding-recording .control-option__binding-circle {
|
||||
width: 24dp;
|
||||
height: 24dp;
|
||||
animation: 1.5s sine-in-out infinite control-option__binding-recording-scale;
|
||||
border-radius: 24dp;
|
||||
background-color: Danger;
|
||||
}
|
||||
.control-option__binding-recording .control-option__binding-edge {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 36dp;
|
||||
height: 36dp;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.control-option__binding-recording .control-option__binding-edge > svg.control-option__binding-edge-svg {
|
||||
width: 36dp;
|
||||
height: 36dp;
|
||||
image-color: Danger;
|
||||
}
|
||||
|
||||
/*
|
||||
Example:
|
||||
<tab class="tab">
|
||||
@@ -722,8 +555,8 @@ scrollbarhorizontal sliderbar {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-bottom-right-radius: 16dp;
|
||||
border-bottom-left-radius: 16dp;
|
||||
border-bottom-right-radius: 24dp;
|
||||
border-bottom-left-radius: 24dp;
|
||||
}
|
||||
|
||||
.config__wrapper {
|
||||
@@ -732,8 +565,8 @@ scrollbarhorizontal sliderbar {
|
||||
height: auto;
|
||||
padding: 16dp;
|
||||
border-radius: 0dp;
|
||||
border-bottom-right-radius: 16dp;
|
||||
border-bottom-left-radius: 16dp;
|
||||
border-bottom-right-radius: 24dp;
|
||||
border-bottom-left-radius: 24dp;
|
||||
background-color: BGShadow;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -778,8 +611,8 @@ scrollbarhorizontal sliderbar {
|
||||
border-top-width: 1.1dp;
|
||||
border-top-color: BorderSoft;
|
||||
padding: 20dp 20dp;
|
||||
border-bottom-right-radius: 16dp;
|
||||
border-bottom-left-radius: 16dp;
|
||||
border-bottom-right-radius: 24dp;
|
||||
border-bottom-left-radius: 24dp;
|
||||
}
|
||||
|
||||
.config__header-left {
|
||||
@@ -891,7 +724,7 @@ scrollbarhorizontal sliderbar {
|
||||
width: 32dp;
|
||||
height: 32dp;
|
||||
margin: 4dp 12dp 0;
|
||||
border-radius: 8dp;
|
||||
border-radius: 12dp;
|
||||
opacity: 0.5;
|
||||
background-color: BGOverlay;
|
||||
cursor: pointer;
|
||||
@@ -1016,7 +849,7 @@ scrollbarhorizontal sliderbar {
|
||||
width: 88dp;
|
||||
height: 100%;
|
||||
border-width: 1.1dp;
|
||||
border-radius: 16dp;
|
||||
border-radius: 24dp;
|
||||
border-color: Border;
|
||||
}
|
||||
.config-option-color__hsv-wrapper {
|
||||
@@ -1149,7 +982,7 @@ scrollbarhorizontal sliderbar {
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 100%;
|
||||
width: auto;
|
||||
border-radius: 12dp;
|
||||
border-radius: 18dp;
|
||||
background-color: WhiteA5;
|
||||
}
|
||||
.config-option-dropdown__select svg, .config-option-dropdown__wrapper svg, .config-option-textfield__select svg, .config-option-textfield__wrapper svg {
|
||||
@@ -1170,7 +1003,7 @@ scrollbarhorizontal sliderbar {
|
||||
border-color: Border;
|
||||
margin-top: 2dp;
|
||||
padding: 4dp 0;
|
||||
border-radius: 12dp;
|
||||
border-radius: 18dp;
|
||||
background-color: Background3;
|
||||
}
|
||||
.config-option-dropdown__select selectbox option, .config-option-dropdown__wrapper selectbox option, .config-option-textfield__select selectbox option, .config-option-textfield__wrapper selectbox option {
|
||||
@@ -1197,8 +1030,8 @@ scrollbarhorizontal sliderbar {
|
||||
height: auto;
|
||||
padding: 16dp;
|
||||
border-radius: 0dp;
|
||||
border-bottom-right-radius: 16dp;
|
||||
border-bottom-left-radius: 16dp;
|
||||
border-bottom-right-radius: 24dp;
|
||||
border-bottom-left-radius: 24dp;
|
||||
background-color: BGShadow;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1215,457 +1048,6 @@ scrollbarhorizontal sliderbar {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.input-config {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input-config__horizontal-split {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input-config__mappings {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
min-width: 640dp;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input-config__mappings-scroll {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.input-config__mappings-wrapper {
|
||||
padding: 8dp;
|
||||
}
|
||||
|
||||
.input-config__visual-wrapper {
|
||||
display: block;
|
||||
flex: 1 1 100%;
|
||||
width: auto;
|
||||
max-width: 1040.4444444444dp;
|
||||
height: auto;
|
||||
max-height: 780.3333333333dp;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.input-config__visual-aspect {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: auto 0;
|
||||
padding-bottom: 75%;
|
||||
background-color: BGShadow;
|
||||
}
|
||||
|
||||
.input-config__visual {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 16dp;
|
||||
right: 16dp;
|
||||
bottom: 16dp;
|
||||
left: 16dp;
|
||||
flex-direction: column;
|
||||
border-radius: 108dp;
|
||||
background-color: WhiteA5;
|
||||
}
|
||||
|
||||
.input-config__visual-half {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: row;
|
||||
padding: 6%;
|
||||
}
|
||||
.input-config__visual-half--bottom {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.input-config__visual-quarter-left {
|
||||
display: flex;
|
||||
flex: 1 1 50%;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.input-config__visual-quarter-right {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.input-config__visual-stick-wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-viz {
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out, opacity 0.05s linear-in-out;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.input-viz > svg:not(.input-viz__dpad-arrow) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
[cur-input=NONE] .input-viz[visual-input] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=A] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=A] .input-viz[visual-input~=A] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=B] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=B] .input-viz[visual-input~=B] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=Z] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=Z] .input-viz[visual-input~=Z] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=START] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=START] .input-viz[visual-input~=START] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=DPAD_UP] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=DPAD_UP] .input-viz[visual-input~=DPAD_UP] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=DPAD_DOWN] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=DPAD_DOWN] .input-viz[visual-input~=DPAD_DOWN] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=DPAD_LEFT] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=DPAD_LEFT] .input-viz[visual-input~=DPAD_LEFT] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=DPAD_RIGHT] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=DPAD_RIGHT] .input-viz[visual-input~=DPAD_RIGHT] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=L] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=L] .input-viz[visual-input~=L] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=R] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=R] .input-viz[visual-input~=R] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=C_UP] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=C_UP] .input-viz[visual-input~=C_UP] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=C_DOWN] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=C_DOWN] .input-viz[visual-input~=C_DOWN] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=C_LEFT] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=C_LEFT] .input-viz[visual-input~=C_LEFT] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=C_RIGHT] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=C_RIGHT] .input-viz[visual-input~=C_RIGHT] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=X_AXIS_NEG] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=X_AXIS_NEG] .input-viz[visual-input~=X_AXIS_NEG] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=X_AXIS_POS] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=X_AXIS_POS] .input-viz[visual-input~=X_AXIS_POS] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=Y_AXIS_NEG] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=Y_AXIS_NEG] .input-viz[visual-input~=Y_AXIS_NEG] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz[visual-input~=Y_AXIS_POS] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[cur-input=Y_AXIS_POS] .input-viz[visual-input~=Y_AXIS_POS] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-viz__button {
|
||||
color: Text;
|
||||
}
|
||||
.input-viz__button svg {
|
||||
image-color: Text;
|
||||
}
|
||||
.input-viz__button--sm {
|
||||
width: 64dp;
|
||||
height: 64dp;
|
||||
}
|
||||
.input-viz__button--sm > svg {
|
||||
width: 64dp;
|
||||
height: 64dp;
|
||||
}
|
||||
.input-viz__button--md {
|
||||
width: 76dp;
|
||||
height: 76dp;
|
||||
}
|
||||
.input-viz__button--md > svg {
|
||||
width: 76dp;
|
||||
height: 76dp;
|
||||
}
|
||||
.input-viz__button--lg {
|
||||
width: 84dp;
|
||||
height: 84dp;
|
||||
}
|
||||
.input-viz__button--lg > svg {
|
||||
width: 84dp;
|
||||
height: 84dp;
|
||||
}
|
||||
.input-viz__button--C svg {
|
||||
image-color: Warning;
|
||||
}
|
||||
.input-viz__button--A {
|
||||
margin-top: auto;
|
||||
}
|
||||
.input-viz__button--A svg {
|
||||
image-color: A;
|
||||
}
|
||||
.input-viz__button--B svg {
|
||||
image-color: Success;
|
||||
}
|
||||
.input-viz__button--Start svg {
|
||||
image-color: Danger;
|
||||
}
|
||||
|
||||
.input-viz__Z {
|
||||
width: 136dp;
|
||||
height: 136dp;
|
||||
}
|
||||
.input-viz__Z svg {
|
||||
image-color: Warning;
|
||||
}
|
||||
.input-viz__Z > svg {
|
||||
width: 136dp;
|
||||
height: 136dp;
|
||||
}
|
||||
|
||||
.input-viz.input-viz__dpad {
|
||||
width: 192dp;
|
||||
height: 192dp;
|
||||
position: relative;
|
||||
}
|
||||
.input-viz.input-viz__dpad svg {
|
||||
image-color: Text;
|
||||
}
|
||||
.input-viz.input-viz__dpad > svg {
|
||||
width: 192dp;
|
||||
height: 192dp;
|
||||
}
|
||||
|
||||
.input-config__visual-stick {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200dp;
|
||||
height: 200dp;
|
||||
border-radius: 100dp;
|
||||
background-color: WhiteA5;
|
||||
}
|
||||
|
||||
.input-viz__dpad-split,
|
||||
.input-viz__stick-split {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.input-viz__dpad-split--vertical,
|
||||
.input-viz__stick-split--vertical {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.input-viz__dpad-split--horizontal,
|
||||
.input-viz__stick-split--horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.input-viz__dpad-split > div,
|
||||
.input-viz__stick-split > div {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-viz__dpad-split > div {
|
||||
width: 64dp;
|
||||
height: 64dp;
|
||||
}
|
||||
|
||||
.input-viz__stick-split > div {
|
||||
width: 66.6666666667dp;
|
||||
height: 66.6666666667dp;
|
||||
}
|
||||
|
||||
.input-viz__dpad-arrow {
|
||||
position: absolute;
|
||||
width: 60dp;
|
||||
height: 60dp;
|
||||
}
|
||||
.input-viz__dpad-arrow--up {
|
||||
top: 4dp;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.input-viz__dpad-arrow--down {
|
||||
bottom: 4dp;
|
||||
margin: 0 auto;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.input-viz__dpad-arrow--left {
|
||||
left: 4dp;
|
||||
margin: auto 0;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.input-viz__dpad-arrow--right {
|
||||
right: 4dp;
|
||||
margin: auto 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.input-viz__R {
|
||||
width: 96dp;
|
||||
height: 96dp;
|
||||
}
|
||||
.input-viz__R svg {
|
||||
image-color: White;
|
||||
}
|
||||
.input-viz__R > svg {
|
||||
width: 96dp;
|
||||
height: 96dp;
|
||||
}
|
||||
|
||||
.input-viz__L {
|
||||
width: 136dp;
|
||||
height: 136dp;
|
||||
}
|
||||
.input-viz__L svg {
|
||||
image-color: Secondary;
|
||||
}
|
||||
.input-viz__L > svg {
|
||||
width: 136dp;
|
||||
height: 136dp;
|
||||
}
|
||||
|
||||
.input-config__c-buttons {
|
||||
position: relative;
|
||||
width: 208dp;
|
||||
height: 132dp;
|
||||
}
|
||||
.input-config__c-buttons-lr, .input-config__c-buttons-du {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.input-config__c-buttons-lr {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.input-config__c-buttons-du {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.input-config__c-buttons .input-viz[visual-input=C_UP] {
|
||||
margin-top: -32dp;
|
||||
}
|
||||
|
||||
.input-config__main-buttons {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 268dp;
|
||||
height: 128dp;
|
||||
margin-right: 10dp;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-color: PrimaryA80;
|
||||
background-color: PrimaryA5;
|
||||
@@ -1676,7 +1058,7 @@ scrollbarhorizontal sliderbar {
|
||||
height: auto;
|
||||
padding: 23dp;
|
||||
border-width: 1.1dp;
|
||||
border-radius: 12dp;
|
||||
border-radius: 18dp;
|
||||
}
|
||||
.button:focus, .button:hover {
|
||||
border-color: Primary;
|
||||
@@ -2039,7 +1421,7 @@ scrollbarhorizontal sliderbar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 16dp;
|
||||
border-radius: 8dp;
|
||||
border-radius: 12dp;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -2259,7 +1641,7 @@ scrollbarhorizontal sliderbar {
|
||||
height: auto;
|
||||
margin: auto;
|
||||
border-width: 1.1dp;
|
||||
border-radius: 16dp;
|
||||
border-radius: 24dp;
|
||||
border-color: Border;
|
||||
background: ModalOverlay;
|
||||
}
|
||||
@@ -2409,7 +1791,7 @@ scrollbarhorizontal sliderbar {
|
||||
flex: 1 1 100%;
|
||||
width: auto;
|
||||
height: 48dp;
|
||||
border-radius: 12dp;
|
||||
border-radius: 18dp;
|
||||
background-color: WhiteA5;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
@@ -2435,7 +1817,7 @@ scrollbarhorizontal sliderbar {
|
||||
background-color: Background3;
|
||||
padding: 4dp 0;
|
||||
margin-top: 2dp;
|
||||
border-radius: 12dp;
|
||||
border-radius: 18dp;
|
||||
}
|
||||
.config-debug__select-wrapper select selectbox option {
|
||||
transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:math';
|
||||
|
||||
.control-option {
|
||||
@include set-color($color-text-dim);
|
||||
@include trans-colors-svg;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: space(4) space(16) space(4) space(20);
|
||||
border-radius: $border-radius-sm;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
|
||||
&:focus-visible:not(:disabled, [disabled]),
|
||||
&:hover:not(:disabled, [disabled]) {
|
||||
@include set-color($color-text);
|
||||
background-color: $color-bg-overlay;
|
||||
}
|
||||
|
||||
&:disabled, &[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&--active {
|
||||
// while actively looking for inputs, set styles to the correct slots
|
||||
$valid-binding-slots: 0, 1;
|
||||
@each $slot in $valid-binding-slots {
|
||||
// global attr -> this active row -> binding slot
|
||||
[cur-binding-slot="#{$slot}"] & .control-option__binding[bind-slot="#{$slot}"] {
|
||||
border-color: $color-danger;
|
||||
|
||||
.control-option__binding-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.control-option__binding-recording {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.control-option__label {
|
||||
@extend %label-md;
|
||||
flex: 2 1 space(300);
|
||||
height: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.control-option__bindings {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 2 1 space(400);
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: space(56);
|
||||
padding: 0 space(12) 0 space(4);
|
||||
}
|
||||
|
||||
.control-option__binding {
|
||||
@include set-color($color-text-dim);
|
||||
@include trans-colors-border;
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
flex: 1 1 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: space(56);
|
||||
margin: 0 space(4);
|
||||
padding: space(8);
|
||||
border-width: $border-width-thickness;
|
||||
border-radius: $border-radius-sm;
|
||||
border-color: $color-bg-overlay;
|
||||
background-color: $color-bg-overlay;
|
||||
|
||||
&:focus, &:hover {
|
||||
@include set-color($color-text);
|
||||
border-color: $color-text;
|
||||
background-color: $color-border-soft;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include set-color($color-text-active);
|
||||
}
|
||||
|
||||
&:disabled, &[disabled] {
|
||||
@include set-color($color-text-dim);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
@extend %nav-all;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.control-option__binding-icon {
|
||||
@include trans-colors-opa;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes control-option__binding-recording-scale {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.control-option__binding-recording {
|
||||
@include trans-colors-opa;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
|
||||
.control-option__binding-circle {
|
||||
$rec-size: 24;
|
||||
|
||||
width: space($rec-size);
|
||||
height: space($rec-size);
|
||||
animation: 1.5s sine-in-out infinite control-option__binding-recording-scale;
|
||||
border-radius: space($rec-size);
|
||||
background-color: $color-danger;
|
||||
}
|
||||
|
||||
.control-option__binding-edge {
|
||||
$edge-size: 36;
|
||||
$h-edge-size: math.div($edge-size, 2);
|
||||
|
||||
position: absolute;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: space($edge-size);
|
||||
height: space($edge-size);
|
||||
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
> svg.control-option__binding-edge-svg {
|
||||
width: space($edge-size);
|
||||
height: space($edge-size);
|
||||
image-color: $color-danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
@use 'sass:math';
|
||||
|
||||
// Probably will need to adjust for other langs...
|
||||
$mapping-min-width: 80 * 8;
|
||||
$visual-max-width: $base-modal-max-width - $mapping-min-width - $scrollbar-width;
|
||||
|
||||
.input-config {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input-config__horizontal-split {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input-config__mappings {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
min-width: space($mapping-min-width);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input-config__mappings-scroll {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.input-config__mappings-wrapper {
|
||||
padding: space(8);
|
||||
}
|
||||
|
||||
.input-config__visual-wrapper {
|
||||
display: block;
|
||||
flex: 1 1 100%;
|
||||
width: auto;
|
||||
max-width: space($visual-max-width);
|
||||
height: auto;
|
||||
max-height: space(math.div($visual-max-width, 4) * 3);
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.input-config__visual-aspect {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: auto 0;
|
||||
padding-bottom: 75%;
|
||||
background-color: $color-bg-shadow;
|
||||
}
|
||||
|
||||
.input-config__visual {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: space(16);
|
||||
right: space(16);
|
||||
bottom: space(16);
|
||||
left: space(16);
|
||||
flex-direction: column;
|
||||
border-radius: space(108);
|
||||
background-color: $color-white-a5;
|
||||
}
|
||||
|
||||
.input-config__visual-half {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: row;
|
||||
padding: 6%;
|
||||
|
||||
&--bottom {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.input-config__visual-quarter-left {
|
||||
display: flex;
|
||||
flex: 1 1 50%;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.input-config__visual-quarter-right {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.input-config__visual-stick-wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-viz {
|
||||
@include trans-colors-opa;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> svg:not(.input-viz__dpad-arrow) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&__mappings div {
|
||||
@extend %prompt-font-sm;
|
||||
}
|
||||
}
|
||||
|
||||
$all-inputs: A,
|
||||
B,
|
||||
Z,
|
||||
START,
|
||||
DPAD_UP,
|
||||
DPAD_DOWN,
|
||||
DPAD_LEFT,
|
||||
DPAD_RIGHT,
|
||||
L,
|
||||
R,
|
||||
C_UP,
|
||||
C_DOWN,
|
||||
C_LEFT,
|
||||
C_RIGHT,
|
||||
X_AXIS_NEG,
|
||||
X_AXIS_POS,
|
||||
Y_AXIS_NEG,
|
||||
Y_AXIS_POS;
|
||||
|
||||
// Show default state while no inputs are active
|
||||
[cur-input="NONE"] .input-viz[visual-input] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@each $inp in $all-inputs {
|
||||
.input-viz[visual-input~="#{$inp}"] {
|
||||
opacity: 0.25;
|
||||
|
||||
[cur-input="#{$inp}"] & {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin set-sizes($sz) {
|
||||
width: space($sz);
|
||||
height: space($sz);
|
||||
|
||||
> svg {
|
||||
width: space($sz);
|
||||
height: space($sz);
|
||||
}
|
||||
}
|
||||
|
||||
.input-viz__button {
|
||||
@include set-color($color-text);
|
||||
|
||||
&--sm {
|
||||
@include set-sizes(64);
|
||||
}
|
||||
|
||||
&--md {
|
||||
@include set-sizes(76);
|
||||
}
|
||||
|
||||
&--lg {
|
||||
@include set-sizes(84);
|
||||
}
|
||||
|
||||
&--C {
|
||||
@include set-svgs-color($color-warning);
|
||||
}
|
||||
|
||||
&--A {
|
||||
@include set-svgs-color($color-a);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
&--B {
|
||||
@include set-svgs-color($color-success);
|
||||
}
|
||||
|
||||
&--Start {
|
||||
@include set-svgs-color($color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.input-viz__Z {
|
||||
@include set-svgs-color($color-warning);
|
||||
@include set-sizes(136);
|
||||
}
|
||||
|
||||
$dpad-size: 192;
|
||||
|
||||
.input-viz.input-viz__dpad {
|
||||
@include set-svgs-color($color-text);
|
||||
@include set-sizes($dpad-size);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
$stick-size: 200;
|
||||
|
||||
.input-config__visual-stick {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: space($stick-size);
|
||||
height: space($stick-size);
|
||||
border-radius: space(math.div($stick-size, 2));
|
||||
background-color: $color-white-a5;
|
||||
}
|
||||
|
||||
.input-viz__dpad-split,
|
||||
.input-viz__stick-split {
|
||||
@include inset-block(0);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&--vertical {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&--horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.input-viz__dpad-split > div {
|
||||
width: space(math.div($dpad-size, 3));
|
||||
height: space(math.div($dpad-size, 3));
|
||||
}
|
||||
|
||||
.input-viz__stick-split > div {
|
||||
width: space(math.div($stick-size, 3));
|
||||
height: space(math.div($stick-size, 3));
|
||||
}
|
||||
|
||||
.input-viz__dpad-arrow {
|
||||
$edge-dist: space(4);
|
||||
position: absolute;
|
||||
width: space(60);
|
||||
height: space(60);
|
||||
|
||||
&--up {
|
||||
top: $edge-dist;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&--down {
|
||||
bottom: $edge-dist;
|
||||
margin: 0 auto;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&--left {
|
||||
left: $edge-dist;
|
||||
margin: auto 0;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
&--right {
|
||||
right: $edge-dist;
|
||||
margin: auto 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.input-viz__R {
|
||||
@include set-svgs-color($color-white);
|
||||
@include set-sizes(96);
|
||||
}
|
||||
|
||||
.input-viz__L {
|
||||
@include set-svgs-color($color-secondary);
|
||||
@include set-sizes(136);
|
||||
}
|
||||
|
||||
.input-config__c-buttons {
|
||||
position: relative;
|
||||
width: space(76 + 76 + 56);
|
||||
height: space(76 + 56);
|
||||
|
||||
&-lr, &-du {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&-lr {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-du {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.input-viz {
|
||||
&[visual-input="C_UP"] {
|
||||
margin-top: space(-32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-config__main-buttons {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: space(268);
|
||||
height: space(128);
|
||||
margin-right: space(10);
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
@import "./CenteredPage";
|
||||
@import "./ControlOption";
|
||||
@import "./Tabs";
|
||||
@import "./Config";
|
||||
@import "./ConfigGroup";
|
||||
@import "./ConfigOption";
|
||||
@import "./ConfigDescription";
|
||||
@import "./InputConfig";
|
||||
@import "./Button";
|
||||
@import "./IconButton";
|
||||
@import "./Launcher";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
$border-radius-sm: 8dp;
|
||||
$border-radius-md: 12dp;
|
||||
$border-radius-sm: 12dp;
|
||||
$border-radius-md: 18dp;
|
||||
// modals/pages
|
||||
$border-radius-lg: 16dp;
|
||||
$border-radius-lg: 24dp;
|
||||
|
||||
$border-radius-modal: $border-radius-lg;
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace banjo {
|
||||
void save_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings();
|
||||
void reset_kb_input_bindings();
|
||||
void reset_single_input_binding(recomp::InputDevice device, recomp::GameInput input);
|
||||
void reset_cont_input_bindings(int profile_index);
|
||||
void reset_kb_input_bindings(int profile_index);
|
||||
void reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input);
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
|
||||
+122
-16
@@ -13,6 +13,9 @@
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "chrono"
|
||||
|
||||
namespace recomp {
|
||||
// x-macros to build input enums and arrays.
|
||||
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
||||
@@ -62,21 +65,32 @@ namespace recomp {
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
// What type of source an input comes from (SDL_Scancode, SDL_GameControllerButton, SDL_GameControllerAxis, SDL_BUTTON, etc.)
|
||||
enum class InputType {
|
||||
None = 0, // Using zero for None ensures that default initialized InputFields are unbound.
|
||||
Keyboard,
|
||||
Mouse,
|
||||
ControllerDigital,
|
||||
ControllerAnalog // Axis input_id values are the SDL value + 1
|
||||
};
|
||||
|
||||
// A single input. Combines the source of the input (see InputType) and a specific key/button/axis.
|
||||
struct InputField {
|
||||
uint32_t input_type;
|
||||
InputType input_type;
|
||||
// Represents a single source input. e.g. A keyboard's shift key, or a controller's R trigger
|
||||
int32_t input_id;
|
||||
std::string to_string() const;
|
||||
auto operator<=>(const InputField& rhs) const = default;
|
||||
};
|
||||
|
||||
void poll_inputs();
|
||||
float get_input_analog(const InputField& field);
|
||||
float get_input_analog(const std::span<const recomp::InputField> fields);
|
||||
bool get_input_digital(const InputField& field);
|
||||
bool get_input_digital(const std::span<const recomp::InputField> fields);
|
||||
float get_input_analog(int controller_num, const InputField& field);
|
||||
float get_input_analog(int controller_num, const std::span<const recomp::InputField> fields);
|
||||
bool get_input_digital(int controller_num, const InputField& field);
|
||||
bool get_input_digital(int controller_num, const std::span<const recomp::InputField> fields);
|
||||
void get_gyro_deltas(float* x, float* y);
|
||||
void get_mouse_deltas(float* x, float* y);
|
||||
void get_right_analog(float* x, float* y);
|
||||
void get_right_analog(int controller_num, float* x, float* y);
|
||||
|
||||
enum class InputDevice {
|
||||
Controller,
|
||||
@@ -84,13 +98,12 @@ namespace recomp {
|
||||
COUNT
|
||||
};
|
||||
|
||||
void start_scanning_input(InputDevice device);
|
||||
void stop_scanning_input();
|
||||
void finish_scanning_input(InputField scanned_field);
|
||||
void cancel_scanning_input();
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::InputDevice, {
|
||||
{ recomp::InputDevice::Controller, "Controller" },
|
||||
{ recomp::InputDevice::Keyboard, "Keyboard" },
|
||||
});
|
||||
|
||||
void config_menu_set_cont_or_kb(bool cont_interacted);
|
||||
InputField get_scanned_input();
|
||||
int get_scanned_input_index();
|
||||
|
||||
struct DefaultN64Mappings {
|
||||
std::vector<InputField> a;
|
||||
@@ -157,11 +170,47 @@ namespace recomp {
|
||||
const std::string& get_input_name(GameInput input);
|
||||
const std::string& get_input_enum_name(GameInput input);
|
||||
GameInput get_input_from_enum_name(const std::string_view name);
|
||||
InputField& get_input_binding(GameInput input, size_t binding_index, InputDevice device);
|
||||
void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value);
|
||||
InputField& get_input_binding(int profile_index, GameInput input, size_t binding_index);
|
||||
void set_input_binding(int profile_index, GameInput input, size_t binding_index, InputField value);
|
||||
void clear_input_binding(int profile_index, GameInput input);
|
||||
void reset_input_binding(int profile_index, InputDevice device, GameInput input);
|
||||
void reset_profile_bindings(int profile_index, recomp::InputDevice device);
|
||||
int add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom);
|
||||
int get_input_profile_by_key(const std::string &key);
|
||||
const std::string &get_input_profile_key(int profile_index);
|
||||
const std::string &get_input_profile_name(int profile_index);
|
||||
InputDevice get_input_profile_device(int profile_index);
|
||||
bool is_input_profile_custom(int profile_index);
|
||||
int get_input_profile_count();
|
||||
const std::vector<int> get_indices_for_custom_profiles(InputDevice device);
|
||||
void set_input_profile_for_player(int player_index, int profile_index, InputDevice device);
|
||||
int get_input_profile_for_player(int player_index, InputDevice device);
|
||||
|
||||
bool get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out);
|
||||
void set_rumble(int controller_num, bool);
|
||||
struct ControllerGUID {
|
||||
uint64_t hash;
|
||||
std::string serial;
|
||||
int vendor{};
|
||||
int product{};
|
||||
int version{};
|
||||
int crc16{};
|
||||
};
|
||||
|
||||
int add_controller(ControllerGUID guid, int profile_index);
|
||||
const ControllerGUID &get_controller_guid(int controller_index);
|
||||
int get_controller_profile_index(int controller_index);
|
||||
int get_controller_by_guid(ControllerGUID guid);
|
||||
int get_controller_count();
|
||||
ControllerGUID get_guid_from_sdl_controller(SDL_GameController* game_controller);
|
||||
int get_controller_profile_index_from_sdl_controller(SDL_GameController* game_controller);
|
||||
std::string get_string_from_controller_guid(ControllerGUID guid);
|
||||
|
||||
void initialize_input_bindings();
|
||||
int get_sp_controller_profile_index();
|
||||
int get_sp_keyboard_profile_index();
|
||||
int get_mp_keyboard_profile_index(int player_index);
|
||||
|
||||
bool get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out);
|
||||
void set_rumble(int player_index, bool);
|
||||
void update_rumble();
|
||||
void handle_events();
|
||||
|
||||
@@ -195,8 +244,65 @@ namespace recomp {
|
||||
BackgroundInputMode get_background_input_mode();
|
||||
void set_background_input_mode(BackgroundInputMode mode);
|
||||
|
||||
bool get_single_controller_mode();
|
||||
void set_single_controller_mode(bool single_controller);
|
||||
|
||||
bool game_input_disabled();
|
||||
bool all_input_disabled();
|
||||
|
||||
}
|
||||
|
||||
namespace recompinput {
|
||||
struct BindingState {
|
||||
bool active = false;
|
||||
// Designates when binding has been cancelled or completed and the event queue should be purged/ignored.
|
||||
bool skip_events = false;
|
||||
int player_index = -1;
|
||||
recomp::GameInput game_input = recomp::GameInput::COUNT;
|
||||
int binding_index = -1;
|
||||
recomp::InputField new_binding = {};
|
||||
recomp::InputDevice device = recomp::InputDevice::COUNT;
|
||||
};
|
||||
|
||||
void start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index, recomp::InputDevice device);
|
||||
void stop_scanning_for_binding();
|
||||
bool is_binding();
|
||||
|
||||
BindingState& get_binding_state();
|
||||
|
||||
int get_num_players();
|
||||
|
||||
struct AssignedPlayer {
|
||||
SDL_GameController* controller = nullptr;
|
||||
bool is_assigned = false;
|
||||
bool keyboard_enabled = false;
|
||||
std::chrono::high_resolution_clock::duration last_button_press_timestamp = std::chrono::high_resolution_clock::duration::zero();
|
||||
|
||||
AssignedPlayer() : controller(nullptr), is_assigned(false), keyboard_enabled(false), last_button_press_timestamp(std::chrono::high_resolution_clock::duration::zero()) {};
|
||||
};
|
||||
|
||||
constexpr size_t temp_max_players = 4;
|
||||
|
||||
struct PlayerAssignmentState {
|
||||
bool is_assigning = false;
|
||||
int player_index = 0;
|
||||
std::array<AssignedPlayer, temp_max_players> temp_assigned_players;
|
||||
|
||||
PlayerAssignmentState() : is_assigning(false), player_index(0), temp_assigned_players{} {};
|
||||
};
|
||||
|
||||
AssignedPlayer& get_assigned_player(int player_index, bool temp_player = false);
|
||||
|
||||
bool get_player_is_assigned(int player_index);
|
||||
recomp::InputDevice get_assigned_player_input_device(int player_index);
|
||||
void start_player_assignment(void);
|
||||
void stop_player_assignment(void);
|
||||
void stop_player_assignment_and_close_modal(void);
|
||||
void commit_player_assignment(void);
|
||||
bool is_player_assignment_active();
|
||||
std::chrono::steady_clock::duration get_player_time_since_last_button_press(int player_index);
|
||||
|
||||
bool does_player_have_controller(int player_index);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+124
-52
@@ -129,6 +129,18 @@ namespace recomp {
|
||||
j.at("input_type").get_to(field.input_type);
|
||||
j.at("input_id").get_to(field.input_id);
|
||||
}
|
||||
|
||||
void to_json(json& j, const ControllerGUID& guid) {
|
||||
j = json{ {"serial", guid.serial}, {"vendor", guid.vendor}, {"product", guid.product}, {"version", guid.version}, {"crc16", guid.crc16} };
|
||||
}
|
||||
|
||||
void from_json(const json& j, ControllerGUID& guid) {
|
||||
j.at("serial").get_to(guid.serial);
|
||||
j.at("vendor").get_to(guid.vendor);
|
||||
j.at("product").get_to(guid.product);
|
||||
j.at("version").get_to(guid.version);
|
||||
j.at("crc16").get_to(guid.crc16);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path banjo::get_app_folder_path() {
|
||||
@@ -266,65 +278,65 @@ bool load_general_config(const std::filesystem::path& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void assign_mapping(recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
void assign_mapping(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
for (size_t binding_index = 0; binding_index < std::min(value.size(), recomp::bindings_per_input); binding_index++) {
|
||||
recomp::set_input_binding(input, binding_index, device, value[binding_index]);
|
||||
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
|
||||
}
|
||||
};
|
||||
|
||||
// same as assign_mapping, except will clear unassigned bindings if not in value
|
||||
void assign_mapping_complete(recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
void assign_mapping_complete(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= value.size()) {
|
||||
recomp::set_input_binding(input, binding_index, device, recomp::InputField{});
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(input, binding_index, device, value[binding_index]);
|
||||
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void assign_all_mappings(recomp::InputDevice device, const recomp::DefaultN64Mappings& values) {
|
||||
assign_mapping_complete(device, recomp::GameInput::A, values.a);
|
||||
assign_mapping_complete(device, recomp::GameInput::B, values.b);
|
||||
assign_mapping_complete(device, recomp::GameInput::Z, values.z);
|
||||
assign_mapping_complete(device, recomp::GameInput::START, values.start);
|
||||
assign_mapping_complete(device, recomp::GameInput::DPAD_UP, values.dpad_up);
|
||||
assign_mapping_complete(device, recomp::GameInput::DPAD_DOWN, values.dpad_down);
|
||||
assign_mapping_complete(device, recomp::GameInput::DPAD_LEFT, values.dpad_left);
|
||||
assign_mapping_complete(device, recomp::GameInput::DPAD_RIGHT, values.dpad_right);
|
||||
assign_mapping_complete(device, recomp::GameInput::L, values.l);
|
||||
assign_mapping_complete(device, recomp::GameInput::R, values.r);
|
||||
assign_mapping_complete(device, recomp::GameInput::C_UP, values.c_up);
|
||||
assign_mapping_complete(device, recomp::GameInput::C_DOWN, values.c_down);
|
||||
assign_mapping_complete(device, recomp::GameInput::C_LEFT, values.c_left);
|
||||
assign_mapping_complete(device, recomp::GameInput::C_RIGHT, values.c_right);
|
||||
void assign_all_mappings(int profile_index, const recomp::DefaultN64Mappings& values) {
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::A, values.a);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::B, values.b);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Z, values.z);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::START, values.start);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_UP, values.dpad_up);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_DOWN, values.dpad_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_LEFT, values.dpad_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_RIGHT, values.dpad_right);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::L, values.l);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::R, values.r);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_UP, values.c_up);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_DOWN, values.c_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_LEFT, values.c_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_RIGHT, values.c_right);
|
||||
|
||||
assign_mapping_complete(device, recomp::GameInput::X_AXIS_NEG, values.analog_left);
|
||||
assign_mapping_complete(device, recomp::GameInput::X_AXIS_POS, values.analog_right);
|
||||
assign_mapping_complete(device, recomp::GameInput::Y_AXIS_NEG, values.analog_down);
|
||||
assign_mapping_complete(device, recomp::GameInput::Y_AXIS_POS, values.analog_up);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_NEG, values.analog_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_POS, values.analog_right);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_NEG, values.analog_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_POS, values.analog_up);
|
||||
|
||||
assign_mapping_complete(device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
assign_mapping_complete(device, recomp::GameInput::ACCEPT_MENU, values.accept_menu);
|
||||
assign_mapping_complete(device, recomp::GameInput::APPLY_MENU, values.apply_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::ACCEPT_MENU, values.accept_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::APPLY_MENU, values.apply_menu);
|
||||
};
|
||||
|
||||
void banjo::reset_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_cont_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
void banjo::reset_cont_input_bindings(int profile_index) {
|
||||
assign_all_mappings(profile_index, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_kb_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
void banjo::reset_kb_input_bindings(int profile_index) {
|
||||
assign_all_mappings(profile_index, recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_single_input_binding(recomp::InputDevice device, recomp::GameInput input) {
|
||||
void banjo::reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input) {
|
||||
assign_mapping_complete(
|
||||
device,
|
||||
profile_index,
|
||||
input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
@@ -368,32 +380,50 @@ bool load_graphics_config(const std::filesystem::path& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::InputDevice device) {
|
||||
void add_input_bindings(nlohmann::json& out, int profile_index, recomp::GameInput input) {
|
||||
const std::string& input_name = recomp::get_input_enum_name(input);
|
||||
nlohmann::json& out_array = out[input_name];
|
||||
out_array = nlohmann::json::array();
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
out_array[binding_index] = recomp::get_input_binding(input, binding_index, device);
|
||||
out_array[binding_index] = recomp::get_input_binding(profile_index, input, binding_index);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int controls_version = 3;
|
||||
|
||||
bool save_controls_config(const std::filesystem::path& path) {
|
||||
int profile_count = recomp::get_input_profile_count();
|
||||
int controller_count = recomp::get_controller_count();
|
||||
nlohmann::json config_json{};
|
||||
config_json["version"] = controls_version;
|
||||
config_json["profiles"] = std::vector<nlohmann::json>(profile_count);
|
||||
config_json["controllers"] = std::vector<nlohmann::json>(controller_count);
|
||||
|
||||
config_json["keyboard"] = {};
|
||||
config_json["controller"] = {};
|
||||
nlohmann::json &profiles = config_json["profiles"];
|
||||
for (int i = 0; i < profile_count; i++) {
|
||||
nlohmann::json &profile = profiles[i];
|
||||
profile["key"] = recomp::get_input_profile_key(i);
|
||||
profile["name"] = recomp::get_input_profile_name(i);
|
||||
profile["device"] = recomp::get_input_profile_device(i);
|
||||
profile["custom"] = recomp::is_input_profile_custom(i);
|
||||
profile["mappings"] = nlohmann::json();
|
||||
|
||||
for (int j = 0; j < (int)(recomp::GameInput::COUNT); j++) {
|
||||
add_input_bindings(profile["mappings"], i, (recomp::GameInput)(j));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < recomp::get_num_inputs(); i++) {
|
||||
recomp::GameInput cur_input = static_cast<recomp::GameInput>(i);
|
||||
|
||||
add_input_bindings(config_json["keyboard"], cur_input, recomp::InputDevice::Keyboard);
|
||||
add_input_bindings(config_json["controller"], cur_input, recomp::InputDevice::Controller);
|
||||
nlohmann::json &controllers = config_json["controllers"];
|
||||
for (int i = 0; i < controller_count; i++) {
|
||||
nlohmann::json &controller = controllers[i];
|
||||
controller["guid"] = recomp::get_controller_guid(i);
|
||||
controller["profile"] = recomp::get_input_profile_key(recomp::get_controller_profile_index(i));
|
||||
}
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, recomp::InputDevice device, const std::string& key) {
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, int profile_index, recomp::InputDevice device, const std::string& key) {
|
||||
// Check if the json object for the given key exists.
|
||||
auto find_it = config_json.find(key);
|
||||
if (find_it == config_json.end()) {
|
||||
@@ -410,7 +440,7 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
auto find_input_it = mappings_json.find(input_name);
|
||||
if (find_input_it == mappings_json.end() || !find_input_it->is_array()) {
|
||||
assign_mapping(
|
||||
device,
|
||||
profile_index,
|
||||
cur_input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
@@ -427,7 +457,7 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
for (size_t binding_index = 0; binding_index < std::min(recomp::bindings_per_input, input_json.size()); binding_index++) {
|
||||
recomp::InputField cur_field{};
|
||||
recomp::from_json(input_json[binding_index], cur_field);
|
||||
recomp::set_input_binding(cur_input, binding_index, device, cur_field);
|
||||
recomp::set_input_binding(profile_index, cur_input, binding_index, cur_field);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,13 +470,53 @@ bool load_controls_config(const std::filesystem::path& path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
auto version_it = config_json.find("version");
|
||||
if (version_it != config_json.end()) {
|
||||
auto profiles = config_json.find("profiles");
|
||||
if (profiles == config_json.end() || !profiles->is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const nlohmann::json &profile : *profiles) {
|
||||
std::string key = profile.value("key", std::string());
|
||||
std::string name = profile.value("name", std::string());
|
||||
recomp::InputDevice device = profile.value("device", recomp::InputDevice::COUNT);
|
||||
bool custom = profile.value("custom", false);
|
||||
if (!key.empty() && !name.empty() && device != recomp::InputDevice::COUNT) {
|
||||
int profile_index = recomp::add_input_profile(key, name, device, custom);
|
||||
if (!load_input_device_from_json(profile, profile_index, device, "mappings")) {
|
||||
assign_all_mappings(profile_index, device == recomp::InputDevice::Keyboard ? recomp::default_n64_keyboard_mappings : recomp::default_n64_controller_mappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto controllers = config_json.find("controllers");
|
||||
if (controllers == config_json.end() || !controllers->is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const nlohmann::json &controller : *controllers) {
|
||||
auto guid = controller.find("guid");
|
||||
auto profile = controller.find("profile");
|
||||
if (guid != controller.end() && guid->is_object() && profile != controller.end() && profile->is_string()) {
|
||||
int profile_index = recomp::get_input_profile_by_key(*profile);
|
||||
if (profile_index >= 0) {
|
||||
recomp::add_controller(*guid, profile_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Version 1 of the format only had bindings for Player 1 on the root element.
|
||||
if (!load_input_device_from_json(config_json, recomp::get_sp_keyboard_profile_index(), recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::get_sp_controller_profile_index(), recomp::InputDevice::Controller, "controller")) {
|
||||
assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings);
|
||||
}
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Controller, "controller")) {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -497,6 +567,8 @@ void banjo::load_config() {
|
||||
save_graphics_config(graphics_path);
|
||||
}
|
||||
|
||||
recomp::initialize_input_bindings();
|
||||
|
||||
if (!load_controls_config(controls_path)) {
|
||||
banjo::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
|
||||
+385
-32
@@ -1,5 +1,7 @@
|
||||
#include <array>
|
||||
|
||||
#include "xxHash/xxh3.h"
|
||||
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "recomp_input.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
@@ -7,8 +9,38 @@
|
||||
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||
using input_mapping_array = std::array<input_mapping, static_cast<size_t>(recomp::GameInput::COUNT)>;
|
||||
static input_mapping_array keyboard_input_mappings{};
|
||||
static input_mapping_array controller_input_mappings{};
|
||||
|
||||
struct InputProfile {
|
||||
std::string key;
|
||||
std::string name;
|
||||
recomp::InputDevice device;
|
||||
input_mapping_array mappings;
|
||||
bool custom = false;
|
||||
};
|
||||
|
||||
static std::vector<InputProfile> input_profiles{};
|
||||
static std::array<std::pair<int, int>, 4> players_input_profile_indices{};
|
||||
static std::unordered_map<std::string, int> input_profile_key_index_map{};
|
||||
static std::vector<int> input_profile_custom_indices[2]{};
|
||||
|
||||
struct Controller {
|
||||
recomp::ControllerGUID guid;
|
||||
int profile_index;
|
||||
};
|
||||
|
||||
static std::vector<Controller> controllers;
|
||||
static std::unordered_map<uint64_t, int> controller_hash_index_map{};
|
||||
|
||||
static int keyboard_sp_profile_index = -1;
|
||||
static int controller_sp_profile_index = -1;
|
||||
|
||||
static const std::string keyboard_sp_profile_key = "keyboard_sp";
|
||||
static const std::string controller_sp_profile_key = "controller_sp";
|
||||
static const std::string keyboard_sp_profile_name = "Keyboard (SP)";
|
||||
static const std::string controller_sp_profile_name = "Controller (SP)";
|
||||
|
||||
static const std::string keyboard_mp_profile_key = "keyboard_mp_player_"; // + player index
|
||||
static const std::string keyboard_mp_profile_name = "Keyboard "; // + "(player number)"
|
||||
|
||||
// Make the button value array, which maps a button index to its bit field.
|
||||
#define DEFINE_INPUT(name, value, readable) uint16_t(value##u),
|
||||
@@ -53,10 +85,8 @@ recomp::GameInput recomp::get_input_from_enum_name(const std::string_view enum_n
|
||||
}
|
||||
|
||||
// Due to an RmlUi limitation this can't be const. Ideally it would return a const reference or even just a straight up copy.
|
||||
recomp::InputField& recomp::get_input_binding(GameInput input, size_t binding_index, recomp::InputDevice device) {
|
||||
input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings;
|
||||
input_mapping& cur_input_mapping = device_mappings.at(static_cast<size_t>(input));
|
||||
|
||||
recomp::InputField& recomp::get_input_binding(int profile_index, GameInput input, size_t binding_index) {
|
||||
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
|
||||
if (binding_index < cur_input_mapping.size()) {
|
||||
return cur_input_mapping[binding_index];
|
||||
}
|
||||
@@ -66,46 +96,369 @@ recomp::InputField& recomp::get_input_binding(GameInput input, size_t binding_in
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_input_binding(recomp::GameInput input, size_t binding_index, recomp::InputDevice device, recomp::InputField value) {
|
||||
input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings;
|
||||
input_mapping& cur_input_mapping = device_mappings.at(static_cast<size_t>(input));
|
||||
|
||||
void recomp::set_input_binding(int profile_index, recomp::GameInput input, size_t binding_index, recomp::InputField value) {
|
||||
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
|
||||
if (binding_index < cur_input_mapping.size()) {
|
||||
cur_input_mapping[binding_index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
void recomp::clear_input_binding(int profile_index, GameInput input) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::reset_input_binding(int profile_index, InputDevice device, GameInput input) {
|
||||
std::vector<recomp::InputField> new_mappings = recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
input
|
||||
);
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= new_mappings.size()) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::reset_profile_bindings(int profile_index, recomp::InputDevice device) {
|
||||
const recomp::DefaultN64Mappings &defaults = (device == recomp::InputDevice::Keyboard)
|
||||
? recomp::default_n64_keyboard_mappings
|
||||
: recomp::default_n64_controller_mappings;
|
||||
|
||||
// multiplayer keyboard profiles just get cleared completely because of overlapping key bindings.
|
||||
bool is_multiplayer_kb = false;
|
||||
if (device == recomp::InputDevice::Keyboard) {
|
||||
for (size_t i = 0; i < recompinput::get_num_players(); i++) {
|
||||
if (recomp::get_mp_keyboard_profile_index(i) == profile_index) {
|
||||
is_multiplayer_kb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < recomp::get_num_inputs(); i++) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(i);
|
||||
if (is_multiplayer_kb) {
|
||||
recomp::clear_input_binding(profile_index, input);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &new_mappings = recomp::get_default_mapping_for_input(defaults, input);
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= new_mappings.size()) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom) {
|
||||
auto it = input_profile_key_index_map.find(key);
|
||||
if (it != input_profile_key_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int index = (int)(input_profiles.size());
|
||||
InputProfile profile;
|
||||
profile.key = key;
|
||||
profile.name = name;
|
||||
profile.device = device;
|
||||
profile.custom = custom;
|
||||
input_profiles.emplace_back(profile);
|
||||
input_profile_key_index_map.emplace(key, index);
|
||||
|
||||
if (custom) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
input_profile_custom_indices[0].emplace_back(index);
|
||||
break;
|
||||
case InputDevice::Keyboard:
|
||||
input_profile_custom_indices[1].emplace_back(index);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_by_key(const std::string &key) {
|
||||
auto it = input_profile_key_index_map.find(key);
|
||||
if (it != input_profile_key_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &recomp::get_input_profile_key(int profile_index) {
|
||||
return input_profiles[profile_index].key;
|
||||
}
|
||||
|
||||
const std::string &recomp::get_input_profile_name(int profile_index) {
|
||||
return input_profiles[profile_index].name;
|
||||
}
|
||||
|
||||
recomp::InputDevice recomp::get_input_profile_device(int profile_index) {
|
||||
return input_profiles[profile_index].device;
|
||||
}
|
||||
|
||||
bool recomp::is_input_profile_custom(int profile_index) {
|
||||
return input_profiles[profile_index].custom;
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_count() {
|
||||
return (int)(input_profiles.size());
|
||||
}
|
||||
|
||||
const std::vector<int> recomp::get_indices_for_custom_profiles(InputDevice device) {
|
||||
static std::vector<int> empty;
|
||||
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
return input_profile_custom_indices[0];
|
||||
case InputDevice::Keyboard:
|
||||
return input_profile_custom_indices[1];
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_input_profile_for_player(int player_index, int profile_index, InputDevice device) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
players_input_profile_indices[player_index].first = profile_index;
|
||||
break;
|
||||
case InputDevice::Keyboard:
|
||||
players_input_profile_indices[player_index].second = profile_index;
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_for_player(int player_index, InputDevice device) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
return players_input_profile_indices[player_index].first;
|
||||
case InputDevice::Keyboard:
|
||||
return players_input_profile_indices[player_index].second;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::add_controller(ControllerGUID guid, int profile_index) {
|
||||
auto it = controller_hash_index_map.find(guid.hash);
|
||||
if (it != controller_hash_index_map.end()) {
|
||||
controllers[it->second].profile_index = profile_index;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int index = (int)(controllers.size());
|
||||
Controller controller;
|
||||
controller.guid = guid;
|
||||
controller.profile_index = profile_index;
|
||||
controllers.emplace_back(controller);
|
||||
controller_hash_index_map.emplace(guid.hash, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
const recomp::ControllerGUID &recomp::get_controller_guid(int controller_index) {
|
||||
return controllers[controller_index].guid;
|
||||
}
|
||||
|
||||
int recomp::get_controller_profile_index(int controller_index) {
|
||||
return controllers[controller_index].profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_controller_by_guid(ControllerGUID guid) {
|
||||
auto it = controller_hash_index_map.find(guid.hash);
|
||||
if (it != controller_hash_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_controller_count() {
|
||||
return (int)(controllers.size());
|
||||
}
|
||||
|
||||
recomp::ControllerGUID recomp::get_guid_from_sdl_controller(SDL_GameController *game_controller) {
|
||||
if (game_controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(game_controller);
|
||||
if (joystick == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Uint16 vendor, product, version, crc16;
|
||||
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
|
||||
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
|
||||
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
|
||||
|
||||
recomp::ControllerGUID guid;
|
||||
guid.serial = joystick_serial != nullptr ? joystick_serial : "";
|
||||
guid.vendor = vendor;
|
||||
guid.product = product;
|
||||
guid.version = version;
|
||||
guid.crc16 = crc16;
|
||||
|
||||
// Compute the hash from the GUID.
|
||||
XXH3_state_t state;
|
||||
XXH3_64bits_reset(&state);
|
||||
XXH3_64bits_update(&state, &guid.vendor, sizeof(guid.vendor));
|
||||
XXH3_64bits_update(&state, &guid.product, sizeof(guid.product));
|
||||
XXH3_64bits_update(&state, &guid.version, sizeof(guid.version));
|
||||
XXH3_64bits_update(&state, &guid.crc16, sizeof(guid.crc16));
|
||||
guid.hash = XXH3_64bits_digest(&state);
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
int recomp::get_controller_profile_index_from_sdl_controller(SDL_GameController *game_controller) {
|
||||
if (game_controller == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ControllerGUID guid = recomp::get_guid_from_sdl_controller(game_controller);
|
||||
if (guid.hash == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int controller_index = recomp::get_controller_by_guid(guid);
|
||||
if (controller_index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return recomp::get_controller_profile_index(controller_index);
|
||||
}
|
||||
|
||||
std::string get_mp_keyboard_profile_key(int player_index) {
|
||||
return keyboard_mp_profile_key + std::to_string(player_index);
|
||||
}
|
||||
|
||||
std::string get_mp_keyboard_profile_name(int player_index) {
|
||||
return keyboard_mp_profile_name + "(Player " + std::to_string(player_index + 1) + ")";
|
||||
}
|
||||
|
||||
void clear_mapping(int profile_index, recomp::GameInput input) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
}
|
||||
};
|
||||
|
||||
// Used primarily for multiplayer keyboard input profiles
|
||||
void clear_all_mappings(int profile_index) {
|
||||
clear_mapping(profile_index, recomp::GameInput::A);
|
||||
clear_mapping(profile_index, recomp::GameInput::B);
|
||||
clear_mapping(profile_index, recomp::GameInput::Z);
|
||||
clear_mapping(profile_index, recomp::GameInput::START);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_UP);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_DOWN);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_LEFT);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_RIGHT);
|
||||
clear_mapping(profile_index, recomp::GameInput::L);
|
||||
clear_mapping(profile_index, recomp::GameInput::R);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_UP);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_DOWN);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_LEFT);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_RIGHT);
|
||||
|
||||
clear_mapping(profile_index, recomp::GameInput::X_AXIS_NEG);
|
||||
clear_mapping(profile_index, recomp::GameInput::X_AXIS_POS);
|
||||
clear_mapping(profile_index, recomp::GameInput::Y_AXIS_NEG);
|
||||
clear_mapping(profile_index, recomp::GameInput::Y_AXIS_POS);
|
||||
|
||||
clear_mapping(profile_index, recomp::GameInput::TOGGLE_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::ACCEPT_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::APPLY_MENU);
|
||||
};
|
||||
|
||||
void recomp::initialize_input_bindings() {
|
||||
keyboard_sp_profile_index = recomp::add_input_profile(keyboard_sp_profile_key, keyboard_sp_profile_name, recomp::InputDevice::Keyboard, false);
|
||||
controller_sp_profile_index = recomp::add_input_profile(controller_sp_profile_key, controller_sp_profile_name, recomp::InputDevice::Controller, false);
|
||||
|
||||
// Set Player 1 to the SP profiles by default.
|
||||
recomp::set_input_profile_for_player(0, keyboard_sp_profile_index, recomp::InputDevice::Keyboard);
|
||||
recomp::set_input_profile_for_player(0, controller_sp_profile_index, recomp::InputDevice::Controller);
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
int profile_index = recomp::add_input_profile(
|
||||
get_mp_keyboard_profile_key(i),
|
||||
get_mp_keyboard_profile_name(i),
|
||||
recomp::InputDevice::Keyboard,
|
||||
false
|
||||
);
|
||||
clear_all_mappings(profile_index);
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_sp_controller_profile_index() {
|
||||
return controller_sp_profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_sp_keyboard_profile_index() {
|
||||
return keyboard_sp_profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_mp_keyboard_profile_index(int player_index) {
|
||||
return recomp::get_input_profile_by_key(get_mp_keyboard_profile_key(player_index));
|
||||
}
|
||||
|
||||
std::string recomp::get_string_from_controller_guid(ControllerGUID guid) {
|
||||
return "SERIAL_" + guid.serial + "_VID_" + std::to_string(guid.vendor) + "_PID_" + std::to_string(guid.product) +"_VERSION_" + std::to_string(guid.version) +"_CRC16_" + std::to_string(guid.crc16);
|
||||
}
|
||||
|
||||
bool recomp::get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
uint16_t cur_buttons = 0;
|
||||
float cur_x = 0.0f;
|
||||
float cur_y = 0.0f;
|
||||
|
||||
if (controller_num != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!recomp::game_input_disabled()) {
|
||||
for (size_t i = 0; i < n64_button_values.size(); i++) {
|
||||
size_t input_index = (size_t)GameInput::N64_BUTTON_START + i;
|
||||
cur_buttons |= recomp::get_input_digital(keyboard_input_mappings[input_index]) ? n64_button_values[i] : 0;
|
||||
cur_buttons |= recomp::get_input_digital(controller_input_mappings[input_index]) ? n64_button_values[i] : 0;
|
||||
}
|
||||
auto check_buttons = [&](int profile_index) {
|
||||
if (profile_index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float joystick_deadzone = recomp::get_joystick_deadzone() / 100.0f;
|
||||
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
|
||||
for (size_t i = 0; i < n64_button_values.size(); i++) {
|
||||
cur_buttons |= recomp::get_input_digital(player_index, mappings[(size_t)(GameInput::N64_BUTTON_START) + i]) ? n64_button_values[i] : 0;
|
||||
}
|
||||
};
|
||||
|
||||
float joystick_x = recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::X_AXIS_POS])
|
||||
- recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::X_AXIS_NEG]);
|
||||
auto check_joystick = [&](int profile_index) {
|
||||
if (profile_index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float joystick_y = recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_POS])
|
||||
- recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_NEG]);
|
||||
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
|
||||
cur_x += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_NEG]);
|
||||
cur_y += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_NEG]);
|
||||
};
|
||||
|
||||
recomp::apply_joystick_deadzone(joystick_x, joystick_y, &joystick_x, &joystick_y);
|
||||
check_buttons(players_input_profile_indices[player_index].first);
|
||||
check_buttons(players_input_profile_indices[player_index].second);
|
||||
|
||||
cur_x = recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_POS])
|
||||
- recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_NEG]) + joystick_x;
|
||||
|
||||
cur_y = recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::Y_AXIS_POS])
|
||||
- recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::Y_AXIS_NEG]) + joystick_y;
|
||||
check_joystick(players_input_profile_indices[player_index].first);
|
||||
recomp::apply_joystick_deadzone(cur_x, cur_y, &cur_x, &cur_y);
|
||||
check_joystick(players_input_profile_indices[player_index].second);
|
||||
}
|
||||
|
||||
*buttons_out = cur_buttons;
|
||||
|
||||
+398
-152
@@ -9,6 +9,8 @@
|
||||
#include "SDL.h"
|
||||
#include "promptfont.h"
|
||||
#include "GamepadMotion.hpp"
|
||||
#include "../ui/ui_assign_players_modal.h"
|
||||
#include "../ui/ui_config_page_controls_element.h"
|
||||
|
||||
constexpr float axis_threshold = 0.5f;
|
||||
|
||||
@@ -28,9 +30,11 @@ static struct {
|
||||
SDL_Keymod keymod = SDL_Keymod::KMOD_NONE;
|
||||
int numkeys = 0;
|
||||
std::atomic_int32_t mouse_wheel_pos = 0;
|
||||
std::mutex cur_controllers_mutex;
|
||||
std::vector<SDL_GameController*> cur_controllers{};
|
||||
std::mutex controllers_mutex;
|
||||
std::vector<SDL_GameController*> detected_controllers{};
|
||||
std::array<recompinput::AssignedPlayer, recompinput::temp_max_players> assigned_controllers{}; // Only used when Multiplayer is enabled.
|
||||
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
|
||||
bool single_controller = false;
|
||||
|
||||
std::array<float, 2> rotation_delta{};
|
||||
std::array<float, 2> mouse_delta{};
|
||||
@@ -38,47 +42,232 @@ static struct {
|
||||
std::array<float, 2> pending_rotation_delta{};
|
||||
std::array<float, 2> pending_mouse_delta{};
|
||||
|
||||
float cur_rumble;
|
||||
bool rumble_active;
|
||||
std::array<float, 4> cur_rumble{};
|
||||
std::array<bool, 4> rumble_active{};
|
||||
} InputState;
|
||||
|
||||
static struct {
|
||||
std::list<std::filesystem::path> files_dropped;
|
||||
} DropState;
|
||||
|
||||
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
|
||||
std::atomic<recomp::InputField> scanned_input;
|
||||
static recompinput::BindingState binding_state;
|
||||
static recompinput::PlayerAssignmentState player_assignment_state{};
|
||||
|
||||
enum class InputType {
|
||||
None = 0, // Using zero for None ensures that default initialized InputFields are unbound.
|
||||
Keyboard,
|
||||
Mouse,
|
||||
ControllerDigital,
|
||||
ControllerAnalog // Axis input_id values are the SDL value + 1
|
||||
};
|
||||
void recompinput::start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index, recomp::InputDevice device) {
|
||||
binding_state.active = true;
|
||||
binding_state.skip_events = false;
|
||||
binding_state.player_index = player_index;
|
||||
binding_state.game_input = game_input;
|
||||
binding_state.binding_index = binding_index;
|
||||
binding_state.device = device;
|
||||
}
|
||||
|
||||
void recompinput::stop_scanning_for_binding() {
|
||||
binding_state = recompinput::BindingState{};
|
||||
binding_state.skip_events = true;
|
||||
}
|
||||
|
||||
bool recompinput::is_binding() {
|
||||
return binding_state.active;
|
||||
}
|
||||
|
||||
bool is_controller_being_bound(SDL_JoystickID joystick_id) {
|
||||
if (binding_state.device != recomp::InputDevice::Controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recompinput::get_num_players() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& player = InputState.assigned_controllers[binding_state.player_index];
|
||||
if (player.controller != nullptr) {
|
||||
SDL_JoystickID assigned_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(player.controller));
|
||||
if (assigned_id == joystick_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
recompinput::BindingState& recompinput::get_binding_state() {
|
||||
return binding_state;
|
||||
}
|
||||
|
||||
int recompinput::get_num_players() {
|
||||
return static_cast<int>(InputState.assigned_controllers.size());
|
||||
}
|
||||
|
||||
recompinput::AssignedPlayer& recompinput::get_assigned_player(int player_index, bool temp_player) {
|
||||
if (temp_player) {
|
||||
return player_assignment_state.temp_assigned_players[player_index];
|
||||
} else {
|
||||
return InputState.assigned_controllers[player_index];
|
||||
}
|
||||
}
|
||||
|
||||
recomp::InputDevice recompinput::get_assigned_player_input_device(int player_index) {
|
||||
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
|
||||
return recomp::InputDevice::COUNT;
|
||||
}
|
||||
|
||||
const auto& assigned_player = InputState.assigned_controllers[player_index];
|
||||
if (assigned_player.controller != nullptr) {
|
||||
return recomp::InputDevice::Controller;
|
||||
} else if (assigned_player.keyboard_enabled) {
|
||||
return recomp::InputDevice::Keyboard;
|
||||
} else {
|
||||
return recomp::InputDevice::COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
bool recompinput::get_player_is_assigned(int player_index) {
|
||||
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return player_assignment_state.temp_assigned_players[player_index].is_assigned;
|
||||
}
|
||||
|
||||
void recompinput::start_player_assignment() {
|
||||
player_assignment_state.is_assigning = true;
|
||||
player_assignment_state.player_index = 0;
|
||||
|
||||
for (auto& player : player_assignment_state.temp_assigned_players) {
|
||||
player = AssignedPlayer{};
|
||||
}
|
||||
}
|
||||
|
||||
static bool queue_close_player_assignment_modal = false;
|
||||
|
||||
void recompinput::stop_player_assignment() {
|
||||
player_assignment_state.is_assigning = false;
|
||||
player_assignment_state.player_index = -1;
|
||||
}
|
||||
|
||||
void recompinput::stop_player_assignment_and_close_modal() {
|
||||
recompinput::stop_player_assignment();
|
||||
queue_close_player_assignment_modal = true;
|
||||
}
|
||||
|
||||
void recompinput::commit_player_assignment() {
|
||||
recompinput::stop_player_assignment_and_close_modal();
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
InputState.assigned_controllers[i] = player_assignment_state.temp_assigned_players[i];
|
||||
if (InputState.assigned_controllers[i].controller != nullptr) {
|
||||
int cont_profile_index = recomp::get_controller_profile_index_from_sdl_controller(InputState.assigned_controllers[i].controller);
|
||||
if (cont_profile_index >= 0) {
|
||||
recomp::set_input_profile_for_player(i, cont_profile_index, InputDevice::Controller);
|
||||
}
|
||||
} else {
|
||||
recomp::set_input_profile_for_player(i, recomp::get_mp_keyboard_profile_index(i), InputDevice::Keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool recompinput::is_player_assignment_active() {
|
||||
return player_assignment_state.is_assigning;
|
||||
}
|
||||
|
||||
bool recompinput::does_player_have_controller(int player_index) {
|
||||
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
|
||||
return false;
|
||||
}
|
||||
return player_assignment_state.temp_assigned_players[player_index].controller != nullptr;
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration recompinput::get_player_time_since_last_button_press(int player_index) {
|
||||
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
|
||||
return std::chrono::steady_clock::duration::zero();
|
||||
}
|
||||
return ultramodern::time_since_start() - player_assignment_state.temp_assigned_players[player_index].last_button_press_timestamp;
|
||||
}
|
||||
|
||||
void process_player_assignment(SDL_Event* event) {
|
||||
if (queue_close_player_assignment_modal) {
|
||||
recompui::assign_players_modal->close();
|
||||
queue_close_player_assignment_modal = false;
|
||||
if (recompui::controls_page != nullptr) {
|
||||
recompui::controls_page->force_update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recompinput::is_player_assignment_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EventType::SDL_KEYDOWN: {
|
||||
SDL_KeyboardEvent* keyevent = &event->key;
|
||||
|
||||
switch (keyevent->keysym.scancode) {
|
||||
case SDL_Scancode::SDL_SCANCODE_ESCAPE:
|
||||
// TODO: Restore previous assignment?
|
||||
recompinput::stop_player_assignment();
|
||||
return;
|
||||
case SDL_Scancode::SDL_SCANCODE_SPACE:
|
||||
player_assignment_state.temp_assigned_players[player_assignment_state.player_index].is_assigned = true;
|
||||
player_assignment_state.temp_assigned_players[player_assignment_state.player_index].keyboard_enabled = true;
|
||||
player_assignment_state.player_index++;
|
||||
printf("Assigned keyboard to player %d\n", player_assignment_state.player_index - 1);
|
||||
break;
|
||||
default:
|
||||
for (int i = 0; i < player_assignment_state.player_index; i++) {
|
||||
if (player_assignment_state.temp_assigned_players[i].keyboard_enabled && player_assignment_state.temp_assigned_players[i].controller == nullptr) {
|
||||
player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: {
|
||||
SDL_ControllerButtonEvent* button_event = &event->cbutton;
|
||||
SDL_JoystickID joystick_id = button_event->which;
|
||||
auto controller_state = InputState.controller_states[joystick_id];
|
||||
|
||||
bool can_be_mapped = true;
|
||||
for (int i = 0; i < player_assignment_state.player_index; i++) {
|
||||
if (player_assignment_state.temp_assigned_players[i].controller == controller_state.controller) {
|
||||
can_be_mapped = false;
|
||||
player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (can_be_mapped) {
|
||||
recompinput::AssignedPlayer& assigned_player = player_assignment_state.temp_assigned_players[player_assignment_state.player_index];
|
||||
assigned_player.is_assigned = true;
|
||||
assigned_player.controller = controller_state.controller;
|
||||
assigned_player.last_button_press_timestamp = ultramodern::time_since_start();
|
||||
player_assignment_state.player_index++;
|
||||
printf("Assigned controller %d to player %d\n", joystick_id, player_assignment_state.player_index - 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_assignment_state.player_index >= recompinput::get_num_players()) {
|
||||
recompinput::stop_player_assignment();
|
||||
}
|
||||
}
|
||||
|
||||
void set_scanned_input(recomp::InputField value) {
|
||||
scanning_device.store(recomp::InputDevice::COUNT);
|
||||
scanned_input.store(value);
|
||||
}
|
||||
|
||||
recomp::InputField recomp::get_scanned_input() {
|
||||
recomp::InputField ret = scanned_input.load();
|
||||
scanned_input.store({});
|
||||
return ret;
|
||||
}
|
||||
|
||||
void recomp::start_scanning_input(recomp::InputDevice device) {
|
||||
scanned_input.store({});
|
||||
scanning_device.store(device);
|
||||
}
|
||||
|
||||
void recomp::stop_scanning_input() {
|
||||
scanning_device.store(recomp::InputDevice::COUNT);
|
||||
recomp::set_input_binding(
|
||||
recomp::get_input_profile_for_player(binding_state.player_index, binding_state.device),
|
||||
binding_state.game_input,
|
||||
binding_state.binding_index,
|
||||
value
|
||||
);
|
||||
recompinput::stop_scanning_for_binding();
|
||||
}
|
||||
|
||||
void queue_if_enabled(SDL_Event* event) {
|
||||
if (!recomp::all_input_disabled()) {
|
||||
if (!recomp::all_input_disabled() && !binding_state.skip_events) {
|
||||
recompui::queue_event(*event);
|
||||
}
|
||||
}
|
||||
@@ -117,12 +306,12 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
) {
|
||||
recompui::toggle_fullscreen();
|
||||
}
|
||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||
if (recompinput::is_binding()) {
|
||||
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||
recomp::cancel_scanning_input();
|
||||
recompinput::stop_scanning_for_binding();
|
||||
}
|
||||
else if (scanning_device == recomp::InputDevice::Keyboard) {
|
||||
set_scanned_input({ (uint32_t)InputType::Keyboard, keyevent->keysym.scancode });
|
||||
else if (binding_state.device == recomp::InputDevice::Keyboard) {
|
||||
set_scanned_input({ recomp::InputType::Keyboard, keyevent->keysym.scancode });
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -139,6 +328,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
printf("Controller added: %d\n", controller_event->which);
|
||||
if (controller != nullptr) {
|
||||
printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
|
||||
printf(" Path: %s\n", SDL_JoystickPath(SDL_GameControllerGetJoystick(controller)));
|
||||
ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))];
|
||||
state.controller = controller;
|
||||
|
||||
@@ -147,6 +337,18 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
recomp::ControllerGUID guid = recomp::get_guid_from_sdl_controller(controller);
|
||||
if (recomp::get_controller_by_guid(guid) < 0) {
|
||||
std::string default_profile_key = recomp::get_string_from_controller_guid(guid);
|
||||
int profile_index = recomp::get_input_profile_by_key(default_profile_key);
|
||||
if (profile_index < 0) {
|
||||
profile_index = recomp::add_input_profile(default_profile_key, "Controller", recomp::InputDevice::Controller, false);
|
||||
banjo::reset_cont_input_bindings(profile_index);
|
||||
}
|
||||
|
||||
recomp::add_controller(guid, profile_index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED:
|
||||
@@ -174,20 +376,21 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
queue_if_enabled(event);
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
|
||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
|
||||
if (recompinput::is_binding() && is_controller_being_bound(event->cbutton.which)) {
|
||||
// TODO: Needs the controller profile index.
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuToggleBinding0.input_type != 0 && event->cbutton.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != 0 && event->cbutton.button == menuToggleBinding1.input_id)) {
|
||||
recomp::cancel_scanning_input();
|
||||
if ((menuToggleBinding0.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding1.input_id)) {
|
||||
recompinput::stop_scanning_for_binding();
|
||||
}
|
||||
else if (scanning_device == recomp::InputDevice::Controller) {
|
||||
else if (binding_state.device == recomp::InputDevice::Controller) {
|
||||
SDL_ControllerButtonEvent* button_event = &event->cbutton;
|
||||
auto scanned_input_index = recomp::get_scanned_input_index();
|
||||
if ((scanned_input_index == static_cast<int>(recomp::GameInput::TOGGLE_MENU) ||
|
||||
scanned_input_index == static_cast<int>(recomp::GameInput::ACCEPT_MENU) ||
|
||||
scanned_input_index == static_cast<int>(recomp::GameInput::APPLY_MENU)) && (
|
||||
recomp::GameInput scanning_game_input = binding_state.game_input;
|
||||
if ((scanning_game_input == recomp::GameInput::TOGGLE_MENU ||
|
||||
scanning_game_input == recomp::GameInput::ACCEPT_MENU ||
|
||||
scanning_game_input == recomp::GameInput::APPLY_MENU) && (
|
||||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP ||
|
||||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN ||
|
||||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
|
||||
@@ -195,7 +398,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
break;
|
||||
}
|
||||
|
||||
set_scanned_input({ (uint32_t)InputType::ControllerDigital, button_event->button });
|
||||
set_scanned_input({ recomp::InputType::ControllerDigital, button_event->button });
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -203,11 +406,11 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERAXISMOTION:
|
||||
if (scanning_device == recomp::InputDevice::Controller) {
|
||||
auto scanned_input_index = recomp::get_scanned_input_index();
|
||||
if (scanned_input_index == static_cast<int>(recomp::GameInput::TOGGLE_MENU) ||
|
||||
scanned_input_index == static_cast<int>(recomp::GameInput::ACCEPT_MENU) ||
|
||||
scanned_input_index == static_cast<int>(recomp::GameInput::APPLY_MENU)) {
|
||||
if (is_controller_being_bound(event->caxis.which)) {
|
||||
recomp::GameInput scanning_game_input = binding_state.game_input;
|
||||
if (scanning_game_input == recomp::GameInput::TOGGLE_MENU ||
|
||||
scanning_game_input == recomp::GameInput::ACCEPT_MENU ||
|
||||
scanning_game_input == recomp::GameInput::APPLY_MENU) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -221,7 +424,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
set_stick_return_event.user.data2 = nullptr;
|
||||
recompui::queue_event(set_stick_return_event);
|
||||
|
||||
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, axis_event->axis + 1 });
|
||||
set_scanned_input({ recomp::InputType::ControllerAnalog, axis_event->axis + 1 });
|
||||
}
|
||||
else if (axis_value < -axis_threshold) {
|
||||
SDL_Event set_stick_return_event;
|
||||
@@ -231,7 +434,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
set_stick_return_event.user.data2 = nullptr;
|
||||
recompui::queue_event(set_stick_return_event);
|
||||
|
||||
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1 });
|
||||
set_scanned_input({ recomp::InputType::ControllerAnalog, -axis_event->axis - 1 });
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -300,6 +503,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
queue_if_enabled(event);
|
||||
break;
|
||||
}
|
||||
process_player_assignment(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -323,6 +527,8 @@ void recomp::handle_events() {
|
||||
SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
|
||||
binding_state.skip_events = false;
|
||||
|
||||
if (!started && ultramodern::is_game_started()) {
|
||||
started = true;
|
||||
recompui::process_game_started();
|
||||
@@ -336,154 +542,159 @@ constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_NORTH = SDL_CONTROLLER_
|
||||
|
||||
const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = {
|
||||
.a = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE}
|
||||
},
|
||||
.b = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT}
|
||||
},
|
||||
.l = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_E}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_E}
|
||||
},
|
||||
.r = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_R}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_R}
|
||||
},
|
||||
.z = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_Q}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_Q}
|
||||
},
|
||||
.start = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
|
||||
},
|
||||
.c_left = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT}
|
||||
},
|
||||
.c_right = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT}
|
||||
},
|
||||
.c_up = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_UP}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_UP}
|
||||
},
|
||||
.c_down = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN}
|
||||
},
|
||||
.dpad_left = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_J}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_J}
|
||||
},
|
||||
.dpad_right = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_L}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_L}
|
||||
},
|
||||
.dpad_up = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_I}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_I}
|
||||
},
|
||||
.dpad_down = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_K}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_K}
|
||||
},
|
||||
.analog_left = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_A}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_A}
|
||||
},
|
||||
.analog_right = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_D}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_D}
|
||||
},
|
||||
.analog_up = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_W}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_W}
|
||||
},
|
||||
.analog_down = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_S}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_S}
|
||||
},
|
||||
.toggle_menu = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE}
|
||||
},
|
||||
.accept_menu = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
|
||||
},
|
||||
.apply_menu = {
|
||||
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_F}
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F}
|
||||
}
|
||||
};
|
||||
|
||||
const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = {
|
||||
.a = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
|
||||
},
|
||||
.b = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
|
||||
},
|
||||
.l = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
},
|
||||
.r = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1},
|
||||
},
|
||||
.z = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1},
|
||||
},
|
||||
.start = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START},
|
||||
},
|
||||
.c_left = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)},
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH},
|
||||
},
|
||||
.c_right = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1},
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST},
|
||||
},
|
||||
.c_up = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)},
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
},
|
||||
.c_down = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1},
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
},
|
||||
.dpad_left = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||
},
|
||||
.dpad_right = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||
},
|
||||
.dpad_up = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||
},
|
||||
.dpad_down = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||
},
|
||||
.analog_left = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)},
|
||||
},
|
||||
.analog_right = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1},
|
||||
},
|
||||
.analog_up = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)},
|
||||
},
|
||||
.analog_down = {
|
||||
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1},
|
||||
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1},
|
||||
},
|
||||
.toggle_menu = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK},
|
||||
},
|
||||
.accept_menu = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
|
||||
},
|
||||
.apply_menu = {
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
|
||||
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}
|
||||
}
|
||||
};
|
||||
|
||||
void recomp::poll_inputs() {
|
||||
InputState.keys = SDL_GetKeyboardState(&InputState.numkeys);
|
||||
InputState.keymod = SDL_GetModState();
|
||||
static bool first_poll = true;
|
||||
|
||||
{
|
||||
std::lock_guard lock{ InputState.cur_controllers_mutex };
|
||||
InputState.cur_controllers.clear();
|
||||
std::lock_guard lock{ InputState.controllers_mutex };
|
||||
InputState.detected_controllers.clear();
|
||||
|
||||
static std::vector<size_t> free_controllers;
|
||||
free_controllers.clear();
|
||||
|
||||
for (const auto& [id, state] : InputState.controller_states) {
|
||||
(void)id; // Avoid unused variable warning.
|
||||
SDL_GameController* controller = state.controller;
|
||||
if (controller != nullptr) {
|
||||
InputState.cur_controllers.push_back(controller);
|
||||
free_controllers.emplace_back(InputState.detected_controllers.size());
|
||||
InputState.detected_controllers.push_back(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,9 +712,7 @@ void recomp::poll_inputs() {
|
||||
}
|
||||
|
||||
void recomp::set_rumble(int controller_num, bool on) {
|
||||
if (controller_num == 0) {
|
||||
InputState.rumble_active = on;
|
||||
}
|
||||
InputState.rumble_active[controller_num] = on;
|
||||
}
|
||||
|
||||
ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) {
|
||||
@@ -528,36 +737,52 @@ static float smoothstep(float from, float to, float amount) {
|
||||
|
||||
// Update rumble to attempt to mimic the way n64 rumble ramps up and falls off
|
||||
void recomp::update_rumble() {
|
||||
// Note: values are not accurate! just approximations based on feel
|
||||
if (InputState.rumble_active) {
|
||||
InputState.cur_rumble += 0.17f;
|
||||
if (InputState.cur_rumble > 1) InputState.cur_rumble = 1;
|
||||
}
|
||||
else {
|
||||
InputState.cur_rumble *= 0.92f;
|
||||
InputState.cur_rumble -= 0.01f;
|
||||
if (InputState.cur_rumble < 0) InputState.cur_rumble = 0;
|
||||
}
|
||||
float smooth_rumble = smoothstep(0, 1, InputState.cur_rumble);
|
||||
for (size_t i = 0; i < InputState.cur_rumble.size(); i++) {
|
||||
// Note: values are not accurate! just approximations based on feel
|
||||
if (InputState.rumble_active[i]) {
|
||||
InputState.cur_rumble[i] += 0.17f;
|
||||
if (InputState.cur_rumble[i] > 1) InputState.cur_rumble[i] = 1;
|
||||
}
|
||||
else {
|
||||
InputState.cur_rumble[i] *= 0.92f;
|
||||
InputState.cur_rumble[i] -= 0.01f;
|
||||
if (InputState.cur_rumble[i] < 0) InputState.cur_rumble[i] = 0;
|
||||
}
|
||||
float smooth_rumble = smoothstep(0, 1, InputState.cur_rumble[i]);
|
||||
|
||||
uint16_t rumble_strength = smooth_rumble * (recomp::get_rumble_strength() * 0xFFFF / 100);
|
||||
uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own.
|
||||
{
|
||||
std::lock_guard lock{ InputState.cur_controllers_mutex };
|
||||
for (const auto& controller : InputState.cur_controllers) {
|
||||
SDL_GameControllerRumble(controller, 0, rumble_strength, duration);
|
||||
uint16_t rumble_strength = smooth_rumble * (recomp::get_rumble_strength() * 0xFFFF / 100);
|
||||
uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own.
|
||||
{
|
||||
std::lock_guard lock{ InputState.controllers_mutex };
|
||||
if (InputState.single_controller) {
|
||||
for (const auto &controller : InputState.detected_controllers) {
|
||||
SDL_GameControllerRumble(controller, 0, rumble_strength, duration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (InputState.assigned_controllers[i].controller != nullptr) {
|
||||
SDL_GameControllerRumble(InputState.assigned_controllers[i].controller, 0, rumble_strength, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool controller_button_state(int32_t input_id) {
|
||||
bool controller_button_state(int controller_num, int32_t input_id) {
|
||||
if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) {
|
||||
SDL_GameControllerButton button = (SDL_GameControllerButton)input_id;
|
||||
bool ret = false;
|
||||
{
|
||||
std::lock_guard lock{ InputState.cur_controllers_mutex };
|
||||
for (const auto& controller : InputState.cur_controllers) {
|
||||
ret |= SDL_GameControllerGetButton(controller, button);
|
||||
std::lock_guard lock{ InputState.controllers_mutex };
|
||||
if (InputState.single_controller) {
|
||||
for (const auto &controller : InputState.detected_controllers) {
|
||||
ret |= SDL_GameControllerGetButton(controller, button);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (InputState.assigned_controllers[controller_num].controller != nullptr) {
|
||||
ret |= SDL_GameControllerGetButton(InputState.assigned_controllers[controller_num].controller, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,16 +793,15 @@ bool controller_button_state(int32_t input_id) {
|
||||
|
||||
static std::atomic_bool right_analog_suppressed = false;
|
||||
|
||||
float controller_axis_state(int32_t input_id, bool allow_suppression) {
|
||||
float controller_axis_state(int controller_num, int32_t input_id, bool allow_suppression) {
|
||||
if (abs(input_id) - 1 < SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_MAX) {
|
||||
SDL_GameControllerAxis axis = (SDL_GameControllerAxis)(abs(input_id) - 1);
|
||||
bool negative_range = input_id < 0;
|
||||
float ret = 0.0f;
|
||||
|
||||
{
|
||||
std::lock_guard lock{ InputState.cur_controllers_mutex };
|
||||
for (const auto& controller : InputState.cur_controllers) {
|
||||
float cur_val = SDL_GameControllerGetAxis(controller, axis) * (1/32768.0f);
|
||||
auto gather_axis_state = [&](SDL_GameController* controller) {
|
||||
float cur_val = SDL_GameControllerGetAxis(controller, axis) * (1 / 32768.0f);
|
||||
if (negative_range) {
|
||||
cur_val = -cur_val;
|
||||
}
|
||||
@@ -588,6 +812,18 @@ float controller_axis_state(int32_t input_id, bool allow_suppression) {
|
||||
cur_val = 0;
|
||||
}
|
||||
ret += std::clamp(cur_val, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
std::lock_guard lock{ InputState.controllers_mutex };
|
||||
if (InputState.single_controller) {
|
||||
for (SDL_GameController *controller : InputState.detected_controllers) {
|
||||
gather_axis_state(controller);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (InputState.assigned_controllers[controller_num].controller != nullptr) {
|
||||
gather_axis_state(InputState.assigned_controllers[controller_num].controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,8 +832,8 @@ float controller_axis_state(int32_t input_id, bool allow_suppression) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float recomp::get_input_analog(const recomp::InputField& field) {
|
||||
switch ((InputType)field.input_type) {
|
||||
float recomp::get_input_analog(int controller_num, const recomp::InputField& field) {
|
||||
switch (field.input_type) {
|
||||
case InputType::Keyboard:
|
||||
if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) {
|
||||
if (should_override_keystate(static_cast<SDL_Scancode>(field.input_id), InputState.keymod)) {
|
||||
@@ -607,9 +843,9 @@ float recomp::get_input_analog(const recomp::InputField& field) {
|
||||
}
|
||||
return 0.0f;
|
||||
case InputType::ControllerDigital:
|
||||
return controller_button_state(field.input_id) ? 1.0f : 0.0f;
|
||||
return controller_button_state(controller_num, field.input_id) ? 1.0f : 0.0f;
|
||||
case InputType::ControllerAnalog:
|
||||
return controller_axis_state(field.input_id, true);
|
||||
return controller_axis_state(controller_num, field.input_id, true);
|
||||
case InputType::Mouse:
|
||||
// TODO mouse support
|
||||
return 0.0f;
|
||||
@@ -618,16 +854,16 @@ float recomp::get_input_analog(const recomp::InputField& field) {
|
||||
}
|
||||
}
|
||||
|
||||
float recomp::get_input_analog(const std::span<const recomp::InputField> fields) {
|
||||
float recomp::get_input_analog(int controller_num, const std::span<const recomp::InputField> fields) {
|
||||
float ret = 0.0f;
|
||||
for (const auto& field : fields) {
|
||||
ret += get_input_analog(field);
|
||||
ret += get_input_analog(controller_num, field);
|
||||
}
|
||||
return std::clamp(ret, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool recomp::get_input_digital(const recomp::InputField& field) {
|
||||
switch ((InputType)field.input_type) {
|
||||
bool recomp::get_input_digital(int controller_num, const recomp::InputField& field) {
|
||||
switch (field.input_type) {
|
||||
case InputType::Keyboard:
|
||||
if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) {
|
||||
if (should_override_keystate(static_cast<SDL_Scancode>(field.input_id), InputState.keymod)) {
|
||||
@@ -637,10 +873,10 @@ bool recomp::get_input_digital(const recomp::InputField& field) {
|
||||
}
|
||||
return false;
|
||||
case InputType::ControllerDigital:
|
||||
return controller_button_state(field.input_id);
|
||||
return controller_button_state(controller_num, field.input_id);
|
||||
case InputType::ControllerAnalog:
|
||||
// TODO adjustable threshold
|
||||
return controller_axis_state(field.input_id, true) >= axis_threshold;
|
||||
return controller_axis_state(controller_num, field.input_id, true) >= axis_threshold;
|
||||
case InputType::Mouse:
|
||||
// TODO mouse support
|
||||
return false;
|
||||
@@ -649,10 +885,10 @@ bool recomp::get_input_digital(const recomp::InputField& field) {
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_input_digital(const std::span<const recomp::InputField> fields) {
|
||||
bool recomp::get_input_digital(int controller_num, const std::span<const recomp::InputField> fields) {
|
||||
bool ret = 0;
|
||||
for (const auto& field : fields) {
|
||||
ret |= get_input_digital(field);
|
||||
ret |= get_input_digital(controller_num, field);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -706,13 +942,13 @@ void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float
|
||||
*y_out = y_in;
|
||||
}
|
||||
|
||||
void recomp::get_right_analog(float* x, float* y) {
|
||||
void recomp::get_right_analog(int controller_num, float* x, float* y) {
|
||||
float x_val =
|
||||
controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false) -
|
||||
controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false);
|
||||
controller_axis_state(controller_num, (SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false) -
|
||||
controller_axis_state(controller_num, -(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false);
|
||||
float y_val =
|
||||
controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false) -
|
||||
controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false);
|
||||
controller_axis_state(controller_num, (SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false) -
|
||||
controller_axis_state(controller_num, -(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false);
|
||||
recomp::apply_joystick_deadzone(x_val, y_val, x, y);
|
||||
}
|
||||
|
||||
@@ -727,7 +963,17 @@ bool recomp::game_input_disabled() {
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
// Disable all input if an input is being polled.
|
||||
return scanning_device != recomp::InputDevice::COUNT;
|
||||
return
|
||||
recompinput::is_player_assignment_active() ||
|
||||
recompinput::is_binding();
|
||||
}
|
||||
|
||||
bool recomp::get_single_controller_mode() {
|
||||
return InputState.single_controller;
|
||||
}
|
||||
|
||||
void recomp::set_single_controller_mode(bool single_controller) {
|
||||
InputState.single_controller = single_controller;
|
||||
}
|
||||
|
||||
std::string controller_button_to_string(SDL_GameControllerButton button) {
|
||||
@@ -882,7 +1128,7 @@ std::string controller_axis_to_string(int axis) {
|
||||
}
|
||||
|
||||
std::string recomp::InputField::to_string() const {
|
||||
switch ((InputType)input_type) {
|
||||
switch (input_type) {
|
||||
case InputType::None:
|
||||
return "";
|
||||
case InputType::ControllerDigital:
|
||||
@@ -892,6 +1138,6 @@ std::string recomp::InputField::to_string() const {
|
||||
case InputType::Keyboard:
|
||||
return keyboard_input_to_string((SDL_Scancode)input_id);
|
||||
default:
|
||||
return std::to_string(input_type) + "," + std::to_string(input_id);
|
||||
return std::to_string((uint32_t)input_type) + "," + std::to_string(input_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
||||
float x, y;
|
||||
|
||||
recomp::get_right_analog(&x, &y);
|
||||
recomp::get_right_analog(0, &x, &y);
|
||||
|
||||
float magnitude = sqrtf(x * x + y * y);
|
||||
|
||||
|
||||
@@ -663,6 +663,9 @@ int main(int argc, char** argv) {
|
||||
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
|
||||
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
|
||||
|
||||
// TODO: Where is it best to place this?
|
||||
recomp::set_single_controller_mode(false);
|
||||
|
||||
recomp::start(
|
||||
project_version,
|
||||
{},
|
||||
|
||||
+85
-81
@@ -2,87 +2,91 @@
|
||||
#include "theme.h"
|
||||
|
||||
void recomptheme::set_custom_theme() {
|
||||
recompui::set_theme_color(recompui::ThemeColor::Background1, recompui::Color{2, 7, 18, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Background2, recompui::Color{7, 15, 34, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Background3, recompui::Color{18, 24, 38, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BGOverlay, recompui::Color{182, 194, 221, 26});
|
||||
recompui::set_theme_color(recompui::ThemeColor::ModalOverlay, recompui::Color{2, 7, 18, 229});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BGShadow, recompui::Color{0, 0, 0, 89});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BGShadow2, recompui::Color{2, 7, 18, 184});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Text, recompui::Color{242, 242, 242, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextActive, recompui::Color{245, 245, 245, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextDim, recompui::Color{204, 204, 204, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextInactive, recompui::Color{255, 255, 255, 153});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextA5, recompui::Color{242, 242, 242, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextA20, recompui::Color{242, 242, 242, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextA30, recompui::Color{242, 242, 242, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextA50, recompui::Color{242, 242, 242, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::TextA80, recompui::Color{242, 242, 242, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Primary, recompui::Color{29, 93, 226, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryL, recompui::Color{167, 191, 241, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryD, recompui::Color{0, 38, 117, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryA5, recompui::Color{29, 93, 226, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryA20, recompui::Color{29, 93, 226, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryA30, recompui::Color{29, 93, 226, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryA50, recompui::Color{29, 93, 226, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::PrimaryA80, recompui::Color{29, 93, 226, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Secondary, recompui::Color{247, 158, 8, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryL, recompui::Color{255, 215, 148, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryD, recompui::Color{224, 141, 0, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryA5, recompui::Color{247, 158, 8, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryA20, recompui::Color{247, 158, 8, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryA30, recompui::Color{247, 158, 8, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryA50, recompui::Color{247, 158, 8, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SecondaryA80, recompui::Color{247, 158, 8, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Warning, recompui::Color{255, 254, 0, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningL, recompui::Color{255, 254, 143, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningD, recompui::Color{197, 163, 2, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningA5, recompui::Color{255, 254, 0, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningA20, recompui::Color{255, 254, 0, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningA30, recompui::Color{255, 254, 0, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningA50, recompui::Color{255, 254, 0, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WarningA80, recompui::Color{255, 254, 0, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Danger, recompui::Color{255, 53, 31, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerL, recompui::Color{255, 149, 138, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerD, recompui::Color{163, 16, 0, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerA5, recompui::Color{255, 53, 31, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerA20, recompui::Color{255, 53, 31, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerA30, recompui::Color{255, 53, 31, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerA50, recompui::Color{255, 53, 31, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::DangerA80, recompui::Color{255, 53, 31, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Success, recompui::Color{40, 238, 32, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessL, recompui::Color{155, 247, 151, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessD, recompui::Color{18, 157, 12, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessA5, recompui::Color{40, 238, 32, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessA20, recompui::Color{40, 238, 32, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessA30, recompui::Color{40, 238, 32, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessA50, recompui::Color{40, 238, 32, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::SuccessA80, recompui::Color{40, 238, 32, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Border, recompui::Color{255, 255, 255, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BorderSoft, recompui::Color{255, 255, 255, 26});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BorderHard, recompui::Color{255, 255, 255, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BorderSolid, recompui::Color{255, 255, 255, 153});
|
||||
recompui::set_theme_color(recompui::ThemeColor::Transparent, recompui::Color{0, 0, 0, 0});
|
||||
recompui::set_theme_color(recompui::ThemeColor::A, recompui::Color{51, 51, 255, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AL, recompui::Color{178, 178, 255, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AD, recompui::Color{32, 32, 172, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AA5, recompui::Color{51, 51, 255, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AA20, recompui::Color{51, 51, 255, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AA30, recompui::Color{51, 51, 255, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AA50, recompui::Color{51, 51, 255, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::AA80, recompui::Color{51, 51, 255, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::White, recompui::Color{255, 255, 255, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WhiteA5, recompui::Color{255, 255, 255, 13});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WhiteA20, recompui::Color{255, 255, 255, 51});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WhiteA30, recompui::Color{255, 255, 255, 77});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WhiteA50, recompui::Color{255, 255, 255, 128});
|
||||
recompui::set_theme_color(recompui::ThemeColor::WhiteA80, recompui::Color{255, 255, 255, 204});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW05, recompui::Color{13, 13, 13, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW10, recompui::Color{26, 26, 26, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW25, recompui::Color{64, 64, 64, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW50, recompui::Color{128, 128, 128, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW75, recompui::Color{191, 191, 191, 255});
|
||||
recompui::set_theme_color(recompui::ThemeColor::BW90, recompui::Color{229, 229, 229, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Background1, recompui::Color{10, 10, 11, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Background2, recompui::Color{19, 20, 21, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Background3, recompui::Color{27, 27, 29, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BGOverlay, recompui::Color{199, 200, 204, 26});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::ModalOverlay, recompui::Color{10, 10, 11, 229});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BGShadow, recompui::Color{0, 0, 0, 89});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BGShadow2, recompui::Color{10, 10, 11, 184});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Text, recompui::Color{242, 242, 242, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextActive, recompui::Color{245, 245, 245, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextDim, recompui::Color{204, 204, 204, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextInactive, recompui::Color{255, 255, 255, 153});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextA5, recompui::Color{242, 242, 242, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextA20, recompui::Color{242, 242, 242, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextA30, recompui::Color{242, 242, 242, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextA50, recompui::Color{242, 242, 242, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::TextA80, recompui::Color{242, 242, 242, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Primary, recompui::Color{29, 93, 226, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryL, recompui::Color{167, 191, 241, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryD, recompui::Color{0, 38, 117, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryA5, recompui::Color{29, 93, 226, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryA20, recompui::Color{29, 93, 226, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryA30, recompui::Color{29, 93, 226, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryA50, recompui::Color{29, 93, 226, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::PrimaryA80, recompui::Color{29, 93, 226, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Secondary, recompui::Color{247, 158, 8, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryL, recompui::Color{255, 215, 148, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryD, recompui::Color{224, 141, 0, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryA5, recompui::Color{247, 158, 8, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryA20, recompui::Color{247, 158, 8, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryA30, recompui::Color{247, 158, 8, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryA50, recompui::Color{247, 158, 8, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SecondaryA80, recompui::Color{247, 158, 8, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Warning, recompui::Color{255, 254, 0, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningL, recompui::Color{255, 254, 143, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningD, recompui::Color{197, 163, 2, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningA5, recompui::Color{255, 254, 0, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningA20, recompui::Color{255, 254, 0, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningA30, recompui::Color{255, 254, 0, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningA50, recompui::Color{255, 254, 0, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WarningA80, recompui::Color{255, 254, 0, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Danger, recompui::Color{255, 53, 31, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerL, recompui::Color{255, 149, 138, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerD, recompui::Color{163, 16, 0, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerA5, recompui::Color{255, 53, 31, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerA20, recompui::Color{255, 53, 31, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerA30, recompui::Color{255, 53, 31, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerA50, recompui::Color{255, 53, 31, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::DangerA80, recompui::Color{255, 53, 31, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Success, recompui::Color{40, 238, 32, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessL, recompui::Color{155, 247, 151, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessD, recompui::Color{18, 157, 12, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessA5, recompui::Color{40, 238, 32, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessA20, recompui::Color{40, 238, 32, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessA30, recompui::Color{40, 238, 32, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessA50, recompui::Color{40, 238, 32, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::SuccessA80, recompui::Color{40, 238, 32, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Border, recompui::Color{255, 255, 255, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BorderSoft, recompui::Color{255, 255, 255, 26});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BorderHard, recompui::Color{255, 255, 255, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BorderSolid, recompui::Color{255, 255, 255, 153});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::Transparent, recompui::Color{0, 0, 0, 0});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::A, recompui::Color{51, 51, 255, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AL, recompui::Color{178, 178, 255, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AD, recompui::Color{32, 32, 172, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AA5, recompui::Color{51, 51, 255, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AA20, recompui::Color{51, 51, 255, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AA30, recompui::Color{51, 51, 255, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AA50, recompui::Color{51, 51, 255, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::AA80, recompui::Color{51, 51, 255, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::White, recompui::Color{255, 255, 255, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WhiteA5, recompui::Color{255, 255, 255, 13});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WhiteA20, recompui::Color{255, 255, 255, 51});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WhiteA30, recompui::Color{255, 255, 255, 77});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WhiteA50, recompui::Color{255, 255, 255, 128});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::WhiteA80, recompui::Color{255, 255, 255, 204});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW05, recompui::Color{13, 13, 13, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW10, recompui::Color{26, 26, 26, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW25, recompui::Color{64, 64, 64, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW50, recompui::Color{128, 128, 128, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW75, recompui::Color{191, 191, 191, 255});
|
||||
recompui::theme::set_theme_color(recompui::theme::color::BW90, recompui::Color{229, 229, 229, 255});
|
||||
|
||||
recompui::theme::border::radius_sm = 12.0f;
|
||||
recompui::theme::border::radius_md = 18.0f;
|
||||
recompui::theme::border::radius_lg = 24.0f;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "slot_map.h"
|
||||
#include "RmlUi/Core/StreamMemory.h"
|
||||
#include "RmlUi/../../Source/Core/DocumentHeader.h"
|
||||
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
#include "recomp_ui.h"
|
||||
@@ -238,9 +239,16 @@ recompui::ContextId recompui::create_context() {
|
||||
root->shim = false;
|
||||
|
||||
ret.open();
|
||||
|
||||
// TODO: Utilize existing headers (for full continuity between documents) or get absolute path of assets.
|
||||
Rml::DocumentHeader header = Rml::DocumentHeader();
|
||||
header.source = "assets/";
|
||||
doc->ProcessHeader(&header);
|
||||
|
||||
root->set_width(100.0f, Unit::Percent);
|
||||
root->set_height(100.0f, Unit::Percent);
|
||||
root->set_display(Display::Flex);
|
||||
|
||||
ret.close();
|
||||
|
||||
doc->Hide();
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "ui_binding_button.h"
|
||||
#include "ui_theme.h"
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
static const float padding = 8.0f;
|
||||
|
||||
BindingButton::BindingButton(Element *parent, const std::string &mapped_binding) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->mapped_binding = mapped_binding;
|
||||
|
||||
enable_focus();
|
||||
apply_sizing_styling(this);
|
||||
|
||||
set_border_color(theme::color::WhiteA5);
|
||||
set_background_color(theme::color::WhiteA5);
|
||||
set_color(theme::color::TextDim);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
focus_style.set_border_color(theme::color::White);
|
||||
focus_style.set_background_color(theme::color::WhiteA30);
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_border_color(theme::color::WhiteA80);
|
||||
hover_style.set_background_color(theme::color::WhiteA20);
|
||||
hover_style.set_color(theme::color::Text);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
bound_text_el = context.create_element<Element>(this, 0, "div", true);
|
||||
bound_text_el->set_text(mapped_binding);
|
||||
apply_binding_style();
|
||||
|
||||
recording_parent = context.create_element<Element>(this);
|
||||
recording_circle = context.create_element<Element>(recording_parent);
|
||||
recording_edge = context.create_element<Element>(recording_parent);
|
||||
recording_svg = context.create_element<Svg>(recording_edge, "icons/RecordBorder.svg");
|
||||
apply_recording_style();
|
||||
}
|
||||
|
||||
void BindingButton::apply_sizing_styling(Element *el) {
|
||||
const float height = 56.0f - (theme::border::width * 2.0f);
|
||||
el->set_display(Display::Flex);
|
||||
el->set_position(Position::Relative);
|
||||
|
||||
el->set_flex_grow(1.0f);
|
||||
el->set_flex_shrink(1.0f);
|
||||
el->set_flex_basis(100.0f, recompui::Unit::Percent);
|
||||
|
||||
el->set_align_items(AlignItems::Center);
|
||||
el->set_justify_content(JustifyContent::Center);
|
||||
|
||||
el->set_width(100.0f, recompui::Unit::Percent);
|
||||
el->set_height(height);
|
||||
el->set_padding(padding);
|
||||
|
||||
el->set_border_width(theme::border::width);
|
||||
el->set_border_radius(theme::border::radius_sm);
|
||||
el->set_border_color(theme::color::Transparent);
|
||||
el->set_background_color(theme::color::Transparent);
|
||||
}
|
||||
|
||||
void BindingButton::apply_recording_style() {
|
||||
recording_parent->set_display(Display::Flex);
|
||||
recording_parent->set_position(Position::Absolute);
|
||||
|
||||
recording_parent->set_top(0.0f);
|
||||
recording_parent->set_left(0.0f);
|
||||
recording_parent->set_right(0.0f);
|
||||
recording_parent->set_bottom(0.0f);
|
||||
|
||||
recording_parent->set_align_items(AlignItems::Center);
|
||||
recording_parent->set_justify_content(JustifyContent::Center);
|
||||
recording_parent->set_opacity(0);
|
||||
|
||||
const float circle_size = 24;
|
||||
recording_circle->set_width(circle_size);
|
||||
recording_circle->set_height(circle_size);
|
||||
recording_circle->set_border_radius(circle_size * 0.5f);
|
||||
recording_circle->set_background_color(theme::color::Danger);
|
||||
|
||||
const float edge_size = 36;
|
||||
recording_edge->set_position(Position::Absolute);
|
||||
recording_edge->set_top(50.0f, recompui::Unit::Percent);
|
||||
recording_edge->set_left(50.0f, recompui::Unit::Percent);
|
||||
recording_edge->set_width(edge_size);
|
||||
recording_edge->set_height(edge_size);
|
||||
recording_edge->set_translate_2D(-50.0f, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
recording_svg->set_width(edge_size);
|
||||
recording_svg->set_height(edge_size);
|
||||
recording_svg->set_image_color(theme::color::Danger);
|
||||
}
|
||||
|
||||
void BindingButton::apply_binding_style() {
|
||||
bound_text_el->set_font_family("promptfont");
|
||||
bound_text_el->set_font_size(40.0f);
|
||||
bound_text_el->set_font_style(FontStyle::Normal);
|
||||
bound_text_el->set_font_weight(400);
|
||||
bound_text_el->set_line_height(40.0f);
|
||||
bound_text_el->set_opacity(1);
|
||||
}
|
||||
|
||||
void BindingButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void BindingButton::set_binding(const std::string &binding) {
|
||||
this->mapped_binding = binding;
|
||||
bound_text_el->set_text(mapped_binding);
|
||||
}
|
||||
|
||||
void BindingButton::set_is_binding(bool is_binding) {
|
||||
this->is_binding = is_binding;
|
||||
|
||||
if (is_binding) {
|
||||
bound_text_el->set_opacity(0);
|
||||
recording_parent->set_opacity(1);
|
||||
queue_update();
|
||||
} else {
|
||||
bound_text_el->set_opacity(1);
|
||||
recording_parent->set_opacity(0);
|
||||
}
|
||||
}
|
||||
|
||||
void BindingButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
set_is_binding(!is_binding);
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
{
|
||||
if (is_binding == false) {
|
||||
break;
|
||||
}
|
||||
queue_update();
|
||||
std::chrono::high_resolution_clock::duration since_start = ultramodern::time_since_start();
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_start).count();
|
||||
const float loop_length_seconds = 1.5f;
|
||||
float t = static_cast<float>(millis) / (loop_length_seconds * 1000.0f);
|
||||
float sine_time = sinf(t * 2.0f * 3.14159f);
|
||||
float scale = 1.0f + ((sine_time * 0.15f / 2.0f) - 0.15f);
|
||||
recording_circle->set_scale_2D(scale, scale);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
class BindingButton : public Element {
|
||||
protected:
|
||||
bool is_binding = false;
|
||||
std::string mapped_binding; // promptfont representation of the binding.
|
||||
|
||||
Element *bound_text_el;
|
||||
Element *recording_parent;
|
||||
Element *recording_circle;
|
||||
Element *recording_edge;
|
||||
Svg *recording_svg;
|
||||
|
||||
Style binding_style;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "BindingButton"; }
|
||||
public:
|
||||
BindingButton(Element *parent, const std::string &mapped_binding);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
void set_binding(const std::string &binding);
|
||||
void set_is_binding(bool is_binding);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
// This is exposed for placeholder elements
|
||||
static void apply_sizing_styling(Element *el);
|
||||
private:
|
||||
void apply_recording_style();
|
||||
void apply_binding_style();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,31 +1,74 @@
|
||||
#include "ui_button.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", true) {
|
||||
Button::Button(Element *parent, const std::string &text, ButtonStyle style, ButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", false) {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
float base_padding = 24.0f;
|
||||
switch (size) {
|
||||
case ButtonSize::Small:
|
||||
base_padding = 12.0f;
|
||||
break;
|
||||
case ButtonSize::Medium:
|
||||
base_padding = 12.0f;
|
||||
break;
|
||||
case ButtonSize::Large:
|
||||
default:
|
||||
base_padding = 24.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
const float button_padding_internal = base_padding - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_text(text);
|
||||
set_display(Display::Block);
|
||||
set_padding(23.0f);
|
||||
set_border_width(1.1f);
|
||||
set_border_radius(12.0f);
|
||||
set_font_size(28.0f);
|
||||
set_letter_spacing(3.08f);
|
||||
set_line_height(28.0f);
|
||||
set_font_style(FontStyle::Normal);
|
||||
set_font_weight(700);
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
|
||||
set_padding_right(button_padding_internal);
|
||||
set_padding_left(button_padding_internal);
|
||||
|
||||
set_width_auto();
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(theme::border::radius_md);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
switch (size) {
|
||||
case ButtonSize::Small: {
|
||||
auto label = context.create_element<Label>(this, text, LabelStyle::Annotation);
|
||||
break;
|
||||
}
|
||||
case ButtonSize::Medium:
|
||||
context.create_element<Label>(this, text, LabelStyle::Small);
|
||||
break;
|
||||
case ButtonSize::Large:
|
||||
default:
|
||||
context.create_element<Label>(this, text, LabelStyle::Normal);
|
||||
break;
|
||||
}
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(ThemeColor::Text);
|
||||
set_color(theme::color::Text);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
hover_style.set_color(ThemeColor::Text);
|
||||
focus_style.set_color(ThemeColor::Text);
|
||||
disabled_style.set_color(ThemeColor::TextDim, 128);
|
||||
hover_disabled_style.set_color(ThemeColor::Text, 128);
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
hover_disabled_style.set_color(theme::color::Text, 128);
|
||||
|
||||
apply_button_style(style);
|
||||
|
||||
@@ -77,27 +120,31 @@ namespace recompui {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(ThemeColor::Primary);
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(ThemeColor::Secondary);
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(ThemeColor::Text);
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(ThemeColor::Success);
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(ThemeColor::Warning);
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(ThemeColor::Danger);
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -106,11 +153,11 @@ namespace recompui {
|
||||
}
|
||||
}
|
||||
|
||||
void Button::apply_theme_style(recompui::ThemeColor color) {
|
||||
const uint8_t border_opacity = 204;
|
||||
const uint8_t background_opacity = 13;
|
||||
const uint8_t border_hover_opacity = 255;
|
||||
void Button::apply_theme_style(recompui::theme::color color, bool is_basic) {
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
|
||||
@@ -11,11 +11,20 @@ namespace recompui {
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
Basic, // No border, only shows background on hover or focus.
|
||||
};
|
||||
|
||||
enum class ButtonSize {
|
||||
Small = 32,
|
||||
Medium = 48,
|
||||
Large = 72,
|
||||
Default = Large
|
||||
};
|
||||
|
||||
class Button : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
ButtonSize size = ButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
@@ -26,7 +35,7 @@ namespace recompui {
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Button"; }
|
||||
public:
|
||||
Button(Element *parent, const std::string &text, ButtonStyle style);
|
||||
Button(Element *parent, const std::string &text, ButtonStyle style, ButtonSize size = ButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
@@ -34,7 +43,7 @@ namespace recompui {
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
private:
|
||||
void apply_theme_style(recompui::ThemeColor color);
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "ui_config_page.h"
|
||||
#include "ui_theme.h"
|
||||
#include "ui_container.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
static const float headerFooterPaddingVert = 20.0f;
|
||||
static const float headerFooterPaddingHorz = 20.0f;
|
||||
|
||||
static void set_header_footer_side_styles(Element *el) {
|
||||
el->set_align_items(AlignItems::Center);
|
||||
// el->set_width(100.0f, Unit::Percent);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_flex_basis_auto();
|
||||
el->set_gap(8.0f);
|
||||
}
|
||||
|
||||
ConfigHeaderFooter::ConfigHeaderFooter(Element *parent, bool is_header) : Element(parent, 0, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height_auto();
|
||||
|
||||
set_padding_top(headerFooterPaddingVert);
|
||||
set_padding_bottom(headerFooterPaddingVert);
|
||||
set_padding_left(headerFooterPaddingHorz);
|
||||
set_padding_right(headerFooterPaddingHorz);
|
||||
|
||||
set_background_color(theme::color::BGShadow);
|
||||
|
||||
const float border_width = theme::border::width;
|
||||
const recompui::theme::color border_color = theme::color::BorderSoft;
|
||||
if (is_header) {
|
||||
set_border_bottom_width(border_width);
|
||||
set_border_bottom_color(border_color);
|
||||
} else {
|
||||
set_border_top_width(border_width);
|
||||
set_border_top_color(border_color);
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
}
|
||||
|
||||
ContextId context = get_current_context();
|
||||
left = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart, 0);
|
||||
set_header_footer_side_styles(left);
|
||||
|
||||
right = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
set_header_footer_side_styles(right);
|
||||
}
|
||||
|
||||
static void set_config_body_side_styles(Element *el) {
|
||||
el->set_flex_grow(1.0f);
|
||||
el->set_flex_shrink(1.0f);
|
||||
el->set_flex_basis(100.0f, Unit::Percent);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_padding(16);
|
||||
}
|
||||
|
||||
ConfigBody::ConfigBody(Element *parent) : Element(parent, 0, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_grow(1.0f);
|
||||
set_flex_shrink(1.0f);
|
||||
set_flex_basis_auto();
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
left = context.create_element<Element>(this, 0, "div", false);
|
||||
set_config_body_side_styles(left);
|
||||
|
||||
right = context.create_element<Element>(this, 0, "div", false);
|
||||
set_config_body_side_styles(right);
|
||||
}
|
||||
|
||||
ConfigPage::ConfigPage(Element *parent) : Element(parent, 0, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
|
||||
set_flex_grow(1.0f);
|
||||
set_flex_shrink(1.0f);
|
||||
set_flex_basis(100.0f, Unit::Percent);
|
||||
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
set_border_top_width(theme::border::width);
|
||||
set_border_top_color(theme::color::BorderSoft);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
header = context.create_element<ConfigHeaderFooter>(this, true);
|
||||
header->set_visibility(Visibility::Hidden);
|
||||
|
||||
body = context.create_element<ConfigBody>(this);
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
|
||||
footer = context.create_element<ConfigHeaderFooter>(this, false);
|
||||
footer->set_visibility(Visibility::Hidden);
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_header() {
|
||||
header->set_visibility(Visibility::Visible);
|
||||
return header;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_header() {
|
||||
header->set_visibility(Visibility::Hidden);
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_footer() {
|
||||
footer->set_visibility(Visibility::Visible);
|
||||
set_border_bottom_left_radius(0);
|
||||
set_border_bottom_right_radius(0);
|
||||
return footer;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_footer() {
|
||||
footer->set_visibility(Visibility::Hidden);
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
class ConfigHeaderFooter : public Element {
|
||||
protected:
|
||||
Element *left;
|
||||
Element *right;
|
||||
bool is_header;
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigHeaderFooter"; }
|
||||
public:
|
||||
ConfigHeaderFooter(Element *parent, bool is_header);
|
||||
Element *get_left() { return left; }
|
||||
Element *get_right() { return right; }
|
||||
};
|
||||
|
||||
class ConfigBody : public Element {
|
||||
protected:
|
||||
Element *left;
|
||||
Element *right;
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigBody"; }
|
||||
public:
|
||||
ConfigBody(Element *parent);
|
||||
Element *get_left() { return left; }
|
||||
Element *get_right() { return right; }
|
||||
};
|
||||
|
||||
|
||||
class ConfigPage : public Element {
|
||||
protected:
|
||||
ConfigHeaderFooter *header = nullptr;
|
||||
ConfigBody *body;
|
||||
ConfigHeaderFooter *footer = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigPage"; }
|
||||
public:
|
||||
ConfigPage(Element *parent);
|
||||
ConfigHeaderFooter *add_header();
|
||||
void hide_header();
|
||||
ConfigHeaderFooter *add_footer();
|
||||
void hide_footer();
|
||||
ConfigHeaderFooter *get_header() { return header; };
|
||||
ConfigBody *get_body() { return body; };
|
||||
ConfigHeaderFooter *get_footer() { return footer; };
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -24,6 +24,7 @@ Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_clas
|
||||
if (parent != nullptr) {
|
||||
base = parent->base->AppendChild(std::move(base_owning));
|
||||
parent->add_child(this);
|
||||
this->parent = parent;
|
||||
}
|
||||
else {
|
||||
base = base_owning.get();
|
||||
@@ -59,6 +60,19 @@ void Element::add_child(Element *child) {
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_parent(Element *new_parent) {
|
||||
if (parent != nullptr) {
|
||||
parent->remove_child(this, false);
|
||||
base_owning = parent->base->RemoveChild(base);
|
||||
|
||||
parent = new_parent;
|
||||
|
||||
base = parent->base->AppendChild(std::move(base_owning), true);
|
||||
parent->add_child(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Element::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||
assert(base != nullptr);
|
||||
|
||||
@@ -110,7 +124,7 @@ void Element::apply_style(Style *style) {
|
||||
// Skip redundant SetProperty calls to prevent dirtying unnecessary state.
|
||||
// This avoids expensive layout operations when a simple color-only style is applied.
|
||||
const Rml::Property* cur_value = base->GetLocalProperty(it.first);
|
||||
if (*cur_value != it.second) {
|
||||
if (cur_value == nullptr || *cur_value != it.second) {
|
||||
base->SetProperty(it.first, it.second);
|
||||
}
|
||||
}
|
||||
@@ -306,7 +320,7 @@ void Element::clear_children() {
|
||||
children.clear();
|
||||
}
|
||||
|
||||
bool Element::remove_child(ResourceId child) {
|
||||
bool Element::remove_child(ResourceId child, bool remove_from_context) {
|
||||
bool found = false;
|
||||
|
||||
ContextId context = get_current_context();
|
||||
@@ -315,7 +329,9 @@ bool Element::remove_child(ResourceId child) {
|
||||
Element* cur_child = *it;
|
||||
if (cur_child->get_resource_id() == child) {
|
||||
children.erase(it);
|
||||
context.destroy_resource(cur_child);
|
||||
if (remove_from_context) {
|
||||
context.destroy_resource(cur_child);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -513,4 +529,48 @@ void Element::register_callback(ContextId context, PTR(void) callback, PTR(void)
|
||||
callbacks.emplace_back(UICallback{.context = context, .callback = callback, .userdata = userdata});
|
||||
}
|
||||
|
||||
}
|
||||
Element *Element::select_add_option(std::string_view text, std::string_view value) {
|
||||
if (base->GetTagName() != "select") {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rml::ElementFormControlSelect* select = (Rml::ElementFormControlSelect *)(base);
|
||||
if (!select) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ContextId context = get_current_context();
|
||||
Element *option_element = context.create_element<Element>(this, 0, "option", true);
|
||||
option_element->set_text(text);
|
||||
option_element->set_input_text(value);
|
||||
|
||||
return option_element;
|
||||
}
|
||||
|
||||
void Element::select_set_selection(std::string_view option_value) {
|
||||
if (base->GetTagName() != "select") {
|
||||
return;
|
||||
}
|
||||
|
||||
Rml::ElementFormControlSelect* select = (Rml::ElementFormControlSelect *)(base);
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
select->SetValue(std::string(option_value));
|
||||
}
|
||||
|
||||
Element Element::get_element_with_tag_name(std::string_view tag_name) {
|
||||
for (int i = 0; i < base->GetNumChildren(true); i++) {
|
||||
Rml::Element* child = base->GetChild(i);
|
||||
if (child->GetTagName() == tag_name) {
|
||||
return Element(child);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Select element has no child with the specified tag name");
|
||||
}
|
||||
|
||||
bool Element::is_pseudo_class_set(Rml::String pseudo_class) {
|
||||
return base->IsPseudoClassSet(pseudo_class);
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
std::unordered_set<std::string_view> style_active_set;
|
||||
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
||||
std::vector<UICallback> callbacks;
|
||||
Element *parent = nullptr;
|
||||
std::vector<Element *> children;
|
||||
std::string id;
|
||||
bool shim = false;
|
||||
@@ -67,10 +68,12 @@ public:
|
||||
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div", bool can_set_text = false);
|
||||
virtual ~Element();
|
||||
void clear_children();
|
||||
bool remove_child(ResourceId child);
|
||||
bool remove_child(Element *child) { return remove_child(child->get_resource_id()); }
|
||||
bool remove_child(ResourceId child, bool remove_from_context = true);
|
||||
bool remove_child(Element *child, bool remove_from_context = true) { return remove_child(child->get_resource_id(), remove_from_context); }
|
||||
void set_parent(Element *new_parent);
|
||||
void add_style(Style *style, std::string_view style_name);
|
||||
void add_style(Style *style, const std::initializer_list<std::string_view> &style_names);
|
||||
Element get_element_with_tag_name(std::string_view tag_name);
|
||||
void set_enabled(bool enabled);
|
||||
bool is_enabled() const;
|
||||
void set_text(std::string_view text);
|
||||
@@ -99,8 +102,12 @@ public:
|
||||
void set_input_value_float(float val) { set_input_value(val); }
|
||||
void set_input_value_double(double val) { set_input_value(val); }
|
||||
const std::string& get_id() { return id; }
|
||||
bool is_pseudo_class_set(Rml::String pseudo_class);
|
||||
|
||||
Element *select_add_option(std::string_view text, std::string_view value);
|
||||
void select_set_selection(std::string_view option_value);
|
||||
};
|
||||
|
||||
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
|
||||
|
||||
} // namespace recompui
|
||||
} // namespace recompui
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "ui_icon_button.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
IconButton::IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
set_width(float_size_internal);
|
||||
set_min_width(float_size_internal);
|
||||
set_max_width(float_size_internal);
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(float_size_internal * 0.5f);
|
||||
set_border_color(theme::color::Transparent);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(theme::color::TextDim);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
set_opacity(1.0f);
|
||||
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
hover_disabled_style.set_color(theme::color::TextDim);
|
||||
|
||||
float icon_size = 0;
|
||||
switch (size) {
|
||||
case IconButtonSize::Mini:
|
||||
icon_size = 16.0f;
|
||||
break;
|
||||
case IconButtonSize::Small:
|
||||
icon_size = 24.0f;
|
||||
break;
|
||||
case IconButtonSize::Medium:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case IconButtonSize::Large:
|
||||
default:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case IconButtonSize::XLarge:
|
||||
icon_size = 40.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
svg = context.create_element<Svg>(this, svg_src);
|
||||
svg->set_width(icon_size);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
|
||||
apply_button_style(style);
|
||||
|
||||
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
|
||||
void IconButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
svg->set_color(hover_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
svg->set_color(focus_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IconButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void IconButton::apply_button_style(ButtonStyle new_style) {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IconButton::apply_theme_style(recompui::theme::color color, bool is_basic) {
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
hover_style.set_border_color(color, border_hover_opacity);
|
||||
hover_style.set_background_color(color, background_hover_opacity);
|
||||
focus_style.set_border_color(color, border_hover_opacity);
|
||||
focus_style.set_background_color(color, background_hover_opacity);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_button.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
enum class IconButtonSize {
|
||||
Mini = 20, // 20x20 (Inline with body text)
|
||||
Small = 32, // 32x32
|
||||
Medium = 48, // 48x48
|
||||
Large = 56, // 56x56
|
||||
Default = IconButtonSize::Large,
|
||||
XLarge = 72, // 72x72
|
||||
};
|
||||
|
||||
class IconButton : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
IconButtonSize size = IconButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
Svg *svg;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "IconButton"; }
|
||||
public:
|
||||
IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size = IconButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
private:
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -7,7 +7,6 @@ namespace recompui {
|
||||
Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) {
|
||||
switch (label_style) {
|
||||
case LabelStyle::Annotation:
|
||||
set_color(ThemeColor::Primary);
|
||||
set_font_size(18.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(18.0f);
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "ui_pill_button.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
static constexpr float pill_padding = 16.0f;
|
||||
|
||||
PillButton::PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
set_min_width(float_size_internal);
|
||||
set_width_auto();
|
||||
set_padding_right(pill_padding);
|
||||
set_padding_left(pill_padding);
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(float_size_internal * 0.5f);
|
||||
set_border_color(theme::color::Transparent);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(theme::color::TextDim);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
set_opacity(1.0f);
|
||||
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
hover_disabled_style.set_color(theme::color::TextDim);
|
||||
ContextId context = get_current_context();
|
||||
|
||||
bool has_svg = !svg_src.empty();
|
||||
bool has_text = !text.empty();
|
||||
|
||||
if (has_svg) {
|
||||
float icon_size = 0;
|
||||
switch (size) {
|
||||
case PillButtonSize::Mini:
|
||||
icon_size = 16.0f;
|
||||
break;
|
||||
case PillButtonSize::Small:
|
||||
icon_size = 24.0f;
|
||||
break;
|
||||
case PillButtonSize::Medium:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case PillButtonSize::Large:
|
||||
default:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case PillButtonSize::XLarge:
|
||||
icon_size = 40.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
svg = context.create_element<Svg>(this, svg_src);
|
||||
svg->set_width(icon_size);
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
|
||||
if (has_text) {
|
||||
auto label = context.create_element<Label>(this, text, LabelStyle::Normal);
|
||||
if (has_svg) {
|
||||
label->set_margin_left(8.0f);
|
||||
}
|
||||
}
|
||||
|
||||
apply_button_style(style);
|
||||
}
|
||||
|
||||
void PillButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(hover_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(focus_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PillButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void PillButton::apply_button_style(ButtonStyle new_style) {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PillButton::apply_theme_style(recompui::theme::color color, bool is_basic, bool with_text) {
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
has_override_text_color = with_text;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
hover_style.set_border_color(color, border_hover_opacity);
|
||||
hover_style.set_background_color(color, background_hover_opacity);
|
||||
focus_style.set_border_color(color, border_hover_opacity);
|
||||
focus_style.set_background_color(color, background_hover_opacity);
|
||||
|
||||
if (with_text) {
|
||||
set_color(color);
|
||||
if (svg != nullptr) {
|
||||
svg->set_image_color(color);
|
||||
}
|
||||
}
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_button.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
enum class PillButtonSize {
|
||||
Mini = 20, // 20x20 (Inline with body text)
|
||||
Small = 32, // 32x32
|
||||
Medium = 48, // 48x48
|
||||
Large = 56, // 56x56
|
||||
Default = PillButtonSize::Large,
|
||||
XLarge = 72, // 72x72
|
||||
};
|
||||
|
||||
class PillButton : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
PillButtonSize size = PillButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
Svg *svg = nullptr;
|
||||
bool has_override_text_color = false;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "PillButton"; }
|
||||
public:
|
||||
PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size = PillButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false, bool with_text = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -17,16 +17,16 @@ namespace recompui {
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_font_style(FontStyle::Normal);
|
||||
set_border_color(ThemeColor::Text, 0);
|
||||
set_border_color(theme::color::Text, 0);
|
||||
set_border_bottom_width(1.0f);
|
||||
set_color(ThemeColor::TextInactive);
|
||||
set_color(theme::color::TextInactive);
|
||||
set_padding_bottom(8.0f);
|
||||
set_text_transform(TextTransform::Uppercase);
|
||||
set_height_auto();
|
||||
hover_style.set_color(ThemeColor::WhiteA80);
|
||||
checked_style.set_color(ThemeColor::White);
|
||||
checked_style.set_border_color(ThemeColor::Text);
|
||||
pulsing_style.set_border_color(ThemeColor::SecondaryA80);
|
||||
hover_style.set_color(theme::color::WhiteA80);
|
||||
checked_style.set_color(theme::color::White);
|
||||
checked_style.set_border_color(theme::color::Text);
|
||||
pulsing_style.set_border_color(theme::color::SecondaryA80);
|
||||
|
||||
add_style(&hover_style, { hover_state });
|
||||
add_style(&checked_style, { checked_state });
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "ui_select.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
Option::Option(Element *parent, const SelectOption &option) :
|
||||
Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "option", true),
|
||||
option(option)
|
||||
{
|
||||
set_text(option.text);
|
||||
set_input_text(option.value);
|
||||
set_color(theme::color::Primary);
|
||||
|
||||
set_padding(12.0f);
|
||||
set_height_auto();
|
||||
set_width_auto();
|
||||
set_overflow(Overflow::Hidden);
|
||||
|
||||
set_min_width(128.0f);
|
||||
|
||||
set_font_size(18.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(18.0f);
|
||||
set_font_weight(400);
|
||||
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
hover_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_background_color(theme::color::White, 77);
|
||||
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
focus_style.set_background_color(theme::color::White, 77);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
disabled_style.set_background_color(theme::color::Transparent);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
}
|
||||
|
||||
void Option::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
break;
|
||||
case EventType::Hover:
|
||||
printf("Option hovered: %s\n", option.text.c_str());
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constexpr std::string_view select_element_selectbox = "selectbox";
|
||||
constexpr std::string_view select_element_selectarrow = "selectarrow";
|
||||
constexpr std::string_view select_element_selectvalue = "selectvalue";
|
||||
|
||||
constexpr float select_element_height = 48.0f;
|
||||
constexpr float select_element_min_width = 128.0f + 64.0f; // 128px for the select box, 64px for the arrow
|
||||
constexpr float select_element_padding = 16.0f;
|
||||
constexpr float select_element_caret_size = 24.0f;
|
||||
|
||||
Select::Select(
|
||||
Element *parent, std::vector<SelectOption> options, std::string selected_option_value
|
||||
) :
|
||||
Element(
|
||||
parent,
|
||||
Events(EventType::Text, EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus),
|
||||
"select",
|
||||
false),
|
||||
selected_option_value(selected_option_value)
|
||||
{
|
||||
this->options = options;
|
||||
|
||||
ContextId context = get_current_context();
|
||||
wrapper = context.create_element<Element>(parent, 0, "div", false);
|
||||
set_parent(wrapper);
|
||||
wrapper->set_height_auto();
|
||||
wrapper->set_width(100.0f, Unit::Percent);
|
||||
wrapper->set_position(Position::Relative);
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::FlexStart);
|
||||
set_gap(16.0f);
|
||||
set_text_align(TextAlign::Left);
|
||||
set_position(Position::Relative);
|
||||
set_padding_right(select_element_padding);
|
||||
set_padding_left(select_element_padding);
|
||||
set_height(select_element_height);
|
||||
set_width_auto();
|
||||
set_overflow(Overflow::Hidden);
|
||||
|
||||
set_min_width(select_element_min_width);
|
||||
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(theme::border::radius_md);
|
||||
set_border_color(theme::color::Border, 204);
|
||||
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(0.0f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_font_style(FontStyle::Normal);
|
||||
|
||||
set_focusable(true);
|
||||
set_color(theme::color::Text);
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Right);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
set_nav_auto(NavDirection::Left);
|
||||
|
||||
hover_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_background_color(theme::color::White, 77);
|
||||
hover_style.set_border_color(theme::color::Border, 255);
|
||||
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
focus_style.set_background_color(theme::color::White, 77);
|
||||
focus_style.set_border_color(theme::color::Border, 255);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
disabled_style.set_background_color(theme::color::Transparent);
|
||||
disabled_style.set_border_color(theme::color::Border, 77);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
hover_disabled_style.set_color(theme::color::TextDim, 128);
|
||||
hover_disabled_style.set_background_color(theme::color::Transparent);
|
||||
hover_disabled_style.set_border_color(theme::color::Border, 77);
|
||||
hover_disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
Element selectbox_element = get_element_with_tag_name(select_element_selectbox);
|
||||
selectbox_element.set_margin_top(4.0f);
|
||||
selectbox_element.set_width_auto();
|
||||
selectbox_element.set_padding_top(8.0f);
|
||||
selectbox_element.set_padding_bottom(8.0f);
|
||||
selectbox_element.set_background_color(theme::color::Background3);
|
||||
selectbox_element.set_border_width(theme::border::width);
|
||||
selectbox_element.set_border_radius(theme::border::radius_sm);
|
||||
selectbox_element.set_border_color(theme::color::BorderSoft);
|
||||
|
||||
Element selectvalue_element = get_element_with_tag_name(select_element_selectvalue);
|
||||
selectvalue_element.set_display(Display::Block);
|
||||
selectvalue_element.set_position(Position::Absolute);
|
||||
selectvalue_element.set_top(50.0f, recompui::Unit::Percent);
|
||||
selectvalue_element.set_left(select_element_padding);
|
||||
selectvalue_element.set_width_auto();
|
||||
selectvalue_element.set_height_auto();
|
||||
selectvalue_element.set_translate_2D(0.0f, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
Element selectarrow_element = get_element_with_tag_name(select_element_selectarrow);
|
||||
selectarrow_element.set_display(Display::None);
|
||||
|
||||
add_option_elements();
|
||||
|
||||
arrow = context.create_element<Svg>(
|
||||
wrapper,
|
||||
"icons/Caret.svg"
|
||||
);
|
||||
|
||||
arrow->set_display(Display::Block);
|
||||
|
||||
arrow->set_position(Position::Absolute);
|
||||
arrow->set_top(50.0f, recompui::Unit::Percent);
|
||||
arrow->set_right(select_element_padding);
|
||||
arrow->set_translate_2D(0, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
arrow->set_width(select_element_caret_size);
|
||||
arrow->set_height(select_element_caret_size);
|
||||
arrow->set_color(theme::color::TextDim);
|
||||
// makes clicking on the arrow pass click interactions through to whats beneath it
|
||||
arrow->set_pointer_events(PointerEvents::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
|
||||
void Select::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus: {
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Text:
|
||||
{
|
||||
const std::string& opt_value = std::get<EventText>(e.variant).text;
|
||||
|
||||
int index = -1;
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
if (options[i].value == opt_value) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
selected_option_index = index;
|
||||
for (const auto &callback : change_callbacks) {
|
||||
callback(options[index], selected_option_index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update: {
|
||||
Element selectvalue_element = get_element_with_tag_name(select_element_selectvalue);
|
||||
bool is_now_open = selectvalue_element.is_pseudo_class_set("checked");
|
||||
if (is_now_open != is_open) {
|
||||
is_open = is_now_open;
|
||||
if (is_open) {
|
||||
arrow->set_rotation(180.0f);
|
||||
} else {
|
||||
arrow->set_rotation(0.0f);
|
||||
}
|
||||
}
|
||||
queue_update();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Select::add_change_callback(std::function<void(SelectOption& option, int option_index)> callback) {
|
||||
change_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Select::add_option_elements() {
|
||||
clear_children();
|
||||
option_elements.clear();
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
auto &option = options[i];
|
||||
Option *option_element = context.create_element<Option>(this, option);
|
||||
option_elements.push_back(option_element);
|
||||
|
||||
if (!selected_option_value.empty() && option.value == selected_option_value) {
|
||||
set_selection(selected_option_value);
|
||||
selected_option_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
enum class SelectOptionStyle {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
Default = Tertiary,
|
||||
};
|
||||
|
||||
struct SelectOption {
|
||||
std::string text;
|
||||
std::string value;
|
||||
SelectOptionStyle style = SelectOptionStyle::Default;
|
||||
|
||||
SelectOption(const std::string &text, const std::string &value, SelectOptionStyle style = SelectOptionStyle::Default)
|
||||
: text(text), value(value), style(style) {}
|
||||
};
|
||||
|
||||
class Option : public Element {
|
||||
protected:
|
||||
SelectOption option;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Option"; }
|
||||
public:
|
||||
Option(Element *parent, const SelectOption &option);
|
||||
};
|
||||
|
||||
class Select : public Element {
|
||||
protected:
|
||||
int selected_option_index = -1;
|
||||
std::string selected_option_value;
|
||||
Element *wrapper = nullptr;
|
||||
Svg *arrow = nullptr;
|
||||
std::vector<SelectOption> options;
|
||||
std::vector<Option*> option_elements;
|
||||
bool is_open = false;
|
||||
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void(SelectOption& option, int option_index)>> change_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Select"; }
|
||||
public:
|
||||
Select(
|
||||
Element *parent,
|
||||
std::vector<SelectOption> options = {},
|
||||
std::string selected_option_value = ""
|
||||
);
|
||||
void add_change_callback(std::function<void(SelectOption& option, int option_index)> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void set_selection(std::string_view option_value) {
|
||||
select_set_selection(option_value);
|
||||
}
|
||||
private:
|
||||
void add_option_elements();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -93,11 +93,11 @@ namespace recompui {
|
||||
queue_update();
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(ThemeColor::TextDim);
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(ThemeColor::BW25);
|
||||
circle_element->set_background_color(theme::color::BW25);
|
||||
}
|
||||
break;
|
||||
case EventType::Navigate:
|
||||
@@ -118,12 +118,12 @@ namespace recompui {
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
circle_element->set_background_color(ThemeColor::TextDim);
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
circle_element->set_background_color(ThemeColor::BW25);
|
||||
circle_element->set_background_color(theme::color::BW25);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -162,7 +162,7 @@ namespace recompui {
|
||||
bar_element->set_width(100.0f, Unit::Percent);
|
||||
bar_element->set_height(2.0f);
|
||||
bar_element->set_margin_top(8.0f);
|
||||
bar_element->set_background_color(ThemeColor::WhiteA20);
|
||||
bar_element->set_background_color(theme::color::WhiteA20);
|
||||
bar_element->add_pressed_callback([this](float x, float y){ bar_pressed(x, y); focus(); });
|
||||
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||
|
||||
@@ -173,8 +173,8 @@ namespace recompui {
|
||||
circle_element->set_margin_top(-7.0f);
|
||||
circle_element->set_margin_right(-8.0f);
|
||||
circle_element->set_margin_left(-8.0f);
|
||||
circle_element->set_background_color(ThemeColor::TextDim);
|
||||
circle_element->set_border_radius(8.0f);
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
circle_element->set_border_radius(theme::border::radius_sm);
|
||||
circle_element->add_pressed_callback([this](float, float){ focus(); });
|
||||
circle_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ circle_dragged(x, y, phase); focus(); });
|
||||
circle_element->set_cursor(Cursor::Pointer);
|
||||
|
||||
@@ -388,7 +388,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderBottomRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
static Color get_theme_color_with_opacity(ThemeColor color, int opacity) {
|
||||
static Color get_theme_color_with_opacity(theme::color color, int opacity) {
|
||||
Color theme_color = get_theme_color(color);
|
||||
if (opacity == recompui::ThemeDefaultOpacity) {
|
||||
opacity = theme_color.a; // Use the existing opacity if not specified.
|
||||
@@ -402,7 +402,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BackgroundColor, property);
|
||||
}
|
||||
|
||||
void Style::set_background_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_background_color(recompui::theme::color color, int opacity) {
|
||||
set_background_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_border_color(recompui::theme::color color, int opacity) {
|
||||
set_border_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_left_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_border_left_color(recompui::theme::color color, int opacity) {
|
||||
set_border_left_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_top_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_border_top_color(recompui::theme::color color, int opacity) {
|
||||
set_border_top_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_right_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_border_right_color(recompui::theme::color color, int opacity) {
|
||||
set_border_right_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_border_bottom_color(recompui::theme::color color, int opacity) {
|
||||
set_border_bottom_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
@@ -459,10 +459,19 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::Color, property);
|
||||
}
|
||||
|
||||
void Style::set_color(recompui::ThemeColor color, int opacity) {
|
||||
void Style::set_color(recompui::theme::color color, int opacity) {
|
||||
set_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_image_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::ImageColor, property);
|
||||
}
|
||||
|
||||
void Style::set_image_color(recompui::theme::color color, int opacity) {
|
||||
set_image_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_cursor(Cursor cursor) {
|
||||
switch (cursor) {
|
||||
case Cursor::None:
|
||||
@@ -537,6 +546,23 @@ namespace recompui {
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_flex_wrap(FlexWrap flex_wrap) {
|
||||
switch (flex_wrap) {
|
||||
case FlexWrap::NoWrap:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::Nowrap);
|
||||
break;
|
||||
case FlexWrap::Wrap:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::Wrap);
|
||||
break;
|
||||
case FlexWrap::WrapReverse:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::WrapReverse);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown flex wrap.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_align_items(AlignItems align_items) {
|
||||
set_property(Rml::PropertyId::AlignItems, to_rml(align_items));
|
||||
}
|
||||
@@ -645,5 +671,61 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
|
||||
}
|
||||
|
||||
Rml::TransformPtr Style::get_existing_transform() {
|
||||
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
|
||||
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
|
||||
if (curTransform != nullptr) {
|
||||
return curTransform;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Style::set_or_add_transformation(const Rml::TransformPrimitive& primitive) {
|
||||
Rml::TransformPtr transform = Rml::MakeShared<Rml::Transform>();
|
||||
Rml::TransformPtr existing_transform = get_existing_transform();
|
||||
bool added_new = false;
|
||||
if (existing_transform != nullptr) {
|
||||
auto& primitives = existing_transform->GetPrimitives();
|
||||
for (int i = 0; i < primitives.size(); i++) {
|
||||
if (primitives[i].type == primitive.type) {
|
||||
transform->AddPrimitive(primitive);
|
||||
added_new = true;
|
||||
} else {
|
||||
transform->AddPrimitive(primitives[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!added_new) {
|
||||
transform->AddPrimitive(primitive);
|
||||
}
|
||||
|
||||
set_property(Rml::PropertyId::Transform, Rml::Property(Rml::Variant(std::move(transform)), Rml::Unit::TRANSFORM));
|
||||
}
|
||||
|
||||
void Style::set_translate_2D(float x, float y, Unit unit) {
|
||||
set_or_add_transformation(Rml::Transforms::Translate2D(x, y, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_scale_2D(float scale_x, float scale_y) {
|
||||
set_or_add_transformation(Rml::Transforms::Scale2D(scale_x, scale_y));
|
||||
}
|
||||
|
||||
void Style::set_rotation(float degrees) {
|
||||
set_or_add_transformation(Rml::Transforms::Rotate2D(degrees));
|
||||
}
|
||||
|
||||
void Style::set_pointer_events(PointerEvents pointer_events) {
|
||||
switch (pointer_events) {
|
||||
case PointerEvents::None:
|
||||
set_property(Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None);
|
||||
break;
|
||||
case PointerEvents::Auto:
|
||||
set_property(Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::Auto);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace recompui {
|
||||
friend class ContextId;
|
||||
private:
|
||||
std::map<Rml::PropertyId, Rml::Property> property_map;
|
||||
Rml::TransformPtr get_existing_transform();
|
||||
void set_or_add_transformation(const Rml::TransformPrimitive& primitive);
|
||||
protected:
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property);
|
||||
ResourceId resource_id = ResourceId::null();
|
||||
@@ -69,13 +71,15 @@ namespace recompui {
|
||||
void set_border_right_color(const Color &color);
|
||||
void set_border_bottom_color(const Color &color);
|
||||
void set_color(const Color &color);
|
||||
void set_background_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_left_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_top_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_right_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_bottom_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_color(recompui::ThemeColor color, int opacity = ThemeDefaultOpacity);
|
||||
void set_image_color(const Color &color);
|
||||
void set_background_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_left_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_top_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_right_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_bottom_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_image_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_cursor(Cursor cursor);
|
||||
void set_opacity(float opacity);
|
||||
void set_display(Display display);
|
||||
@@ -87,6 +91,7 @@ namespace recompui {
|
||||
void set_flex(float grow, float shrink);
|
||||
void set_flex(float grow, float shrink, float basis, Unit basis_unit = Unit::Percent);
|
||||
void set_flex_direction(FlexDirection flex_direction);
|
||||
void set_flex_wrap(FlexWrap flex_wrap);
|
||||
void set_align_items(AlignItems align_items);
|
||||
void set_overflow(Overflow overflow);
|
||||
void set_overflow_x(Overflow overflow);
|
||||
@@ -104,6 +109,9 @@ namespace recompui {
|
||||
void set_drag(Drag drag);
|
||||
void set_tab_index(TabIndex focus);
|
||||
void set_font_family(std::string_view family);
|
||||
void set_translate_2D(float x, float y, Unit unit = Unit::Dp);
|
||||
void set_scale_2D(float scale_x, float scale_y);
|
||||
void set_rotation(float degrees);
|
||||
virtual void set_nav_auto(NavDirection dir);
|
||||
virtual void set_nav_none(NavDirection dir);
|
||||
virtual void set_nav(NavDirection dir, Element* element);
|
||||
@@ -111,6 +119,7 @@ namespace recompui {
|
||||
void set_tab_index_auto();
|
||||
void set_tab_index_none();
|
||||
void set_focusable(bool focusable);
|
||||
void set_pointer_events(PointerEvents pointer_events);
|
||||
virtual bool is_element() { return false; }
|
||||
ResourceId get_resource_id() { return resource_id; }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "ui_svg.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Svg::Svg(Element *parent, std::string_view src) : Element(parent, 0, "svg") {
|
||||
set_src(src);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Svg : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Svg"; }
|
||||
public:
|
||||
Svg(Element *parent, std::string_view src);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -33,7 +33,7 @@ namespace recompui {
|
||||
set_attribute("type", "password");
|
||||
}
|
||||
set_min_width(60.0f);
|
||||
set_border_color(ThemeColor::Text);
|
||||
set_border_color(theme::color::Text);
|
||||
set_border_bottom_width(1.0f);
|
||||
set_padding_bottom(6.0f);
|
||||
set_focusable(true);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#include <array>
|
||||
#include "ui_theme.h"
|
||||
|
||||
using ThemeColor = recompui::ThemeColor;
|
||||
using ThemeColor = recompui::theme::color;
|
||||
using ThemeColorArray = std::array<recompui::Color, (std::size_t)(ThemeColor::size)>;
|
||||
using ThemeColorNameArray = std::array<const char *, (std::size_t)(ThemeColor::size)>;
|
||||
|
||||
float recompui::theme::border::radius_sm = 8.0f;
|
||||
float recompui::theme::border::radius_md = 12.0f;
|
||||
float recompui::theme::border::radius_lg = 16.0f;
|
||||
float recompui::theme::border::width = 1.1f;
|
||||
|
||||
constexpr ThemeColorArray get_default_theme_color_array() {
|
||||
ThemeColorArray colors;
|
||||
|
||||
@@ -102,6 +107,15 @@ constexpr ThemeColorArray get_default_theme_color_array() {
|
||||
colors[(std::size_t)ThemeColor::BW75] = recompui::Color{191, 191, 191, 255};
|
||||
colors[(std::size_t)ThemeColor::BW90] = recompui::Color{229, 229, 229, 255};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Player1] = recompui::Color{0, 185, 253, 255};
|
||||
colors[(std::size_t)ThemeColor::Player2] = recompui::Color{253, 76, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player3] = recompui::Color{253, 194, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player4] = recompui::Color{147, 253, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player5] = recompui::Color{0, 253, 164, 255};
|
||||
colors[(std::size_t)ThemeColor::Player6] = recompui::Color{253, 0, 189, 255};
|
||||
colors[(std::size_t)ThemeColor::Player7] = recompui::Color{140, 74, 255, 255};
|
||||
colors[(std::size_t)ThemeColor::Player8] = recompui::Color{255, 186, 118, 255};
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
@@ -188,20 +202,28 @@ constexpr ThemeColorNameArray get_default_theme_color_names() {
|
||||
names[(std::size_t)ThemeColor::BW50] = "BW50";
|
||||
names[(std::size_t)ThemeColor::BW75] = "BW75";
|
||||
names[(std::size_t)ThemeColor::BW90] = "BW90";
|
||||
names[(std::size_t)ThemeColor::Player1] = "Player1";
|
||||
names[(std::size_t)ThemeColor::Player2] = "Player2";
|
||||
names[(std::size_t)ThemeColor::Player3] = "Player3";
|
||||
names[(std::size_t)ThemeColor::Player4] = "Player4";
|
||||
names[(std::size_t)ThemeColor::Player5] = "Player5";
|
||||
names[(std::size_t)ThemeColor::Player6] = "Player6";
|
||||
names[(std::size_t)ThemeColor::Player7] = "Player7";
|
||||
names[(std::size_t)ThemeColor::Player8] = "Player8";
|
||||
return names;
|
||||
}
|
||||
|
||||
static ThemeColorArray theme_colors = get_default_theme_color_array();
|
||||
static ThemeColorNameArray theme_color_names = get_default_theme_color_names();
|
||||
|
||||
void recompui::set_theme_color(ThemeColor color, const recompui::Color &value) {
|
||||
void recompui::theme::set_theme_color(recompui::theme::color color, const recompui::Color &value) {
|
||||
theme_colors[(std::size_t)color] = value;
|
||||
}
|
||||
|
||||
const recompui::Color &recompui::get_theme_color(recompui::ThemeColor color) {
|
||||
const recompui::Color &recompui::theme::get_theme_color(recompui::theme::color color) {
|
||||
return theme_colors[(std::size_t)color];
|
||||
}
|
||||
|
||||
const char *recompui::get_theme_color_name(recompui::ThemeColor color) {
|
||||
const char *recompui::theme::get_theme_color_name(recompui::theme::color color) {
|
||||
return theme_color_names[(std::size_t)color];
|
||||
}
|
||||
|
||||
+105
-87
@@ -3,93 +3,111 @@
|
||||
#include "ui_types.h"
|
||||
|
||||
namespace recompui {
|
||||
enum class ThemeColor {
|
||||
Background1,
|
||||
Background2,
|
||||
Background3,
|
||||
BGOverlay,
|
||||
ModalOverlay,
|
||||
BGShadow,
|
||||
BGShadow2,
|
||||
Text,
|
||||
TextActive,
|
||||
TextDim,
|
||||
TextInactive,
|
||||
TextA5,
|
||||
TextA20,
|
||||
TextA30,
|
||||
TextA50,
|
||||
TextA80,
|
||||
Primary,
|
||||
PrimaryL,
|
||||
PrimaryD,
|
||||
PrimaryA5,
|
||||
PrimaryA20,
|
||||
PrimaryA30,
|
||||
PrimaryA50,
|
||||
PrimaryA80,
|
||||
Secondary,
|
||||
SecondaryL,
|
||||
SecondaryD,
|
||||
SecondaryA5,
|
||||
SecondaryA20,
|
||||
SecondaryA30,
|
||||
SecondaryA50,
|
||||
SecondaryA80,
|
||||
Warning,
|
||||
WarningL,
|
||||
WarningD,
|
||||
WarningA5,
|
||||
WarningA20,
|
||||
WarningA30,
|
||||
WarningA50,
|
||||
WarningA80,
|
||||
Danger,
|
||||
DangerL,
|
||||
DangerD,
|
||||
DangerA5,
|
||||
DangerA20,
|
||||
DangerA30,
|
||||
DangerA50,
|
||||
DangerA80,
|
||||
Success,
|
||||
SuccessL,
|
||||
SuccessD,
|
||||
SuccessA5,
|
||||
SuccessA20,
|
||||
SuccessA30,
|
||||
SuccessA50,
|
||||
SuccessA80,
|
||||
Border,
|
||||
BorderSoft,
|
||||
BorderHard,
|
||||
BorderSolid,
|
||||
Transparent,
|
||||
A,
|
||||
AL,
|
||||
AD,
|
||||
AA5,
|
||||
AA20,
|
||||
AA30,
|
||||
AA50,
|
||||
AA80,
|
||||
White,
|
||||
WhiteA5,
|
||||
WhiteA20,
|
||||
WhiteA30,
|
||||
WhiteA50,
|
||||
WhiteA80,
|
||||
BW05,
|
||||
BW10,
|
||||
BW25,
|
||||
BW50,
|
||||
BW75,
|
||||
BW90,
|
||||
namespace theme {
|
||||
enum class color {
|
||||
Background1,
|
||||
Background2,
|
||||
Background3,
|
||||
BGOverlay,
|
||||
ModalOverlay,
|
||||
BGShadow,
|
||||
BGShadow2,
|
||||
Text,
|
||||
TextActive,
|
||||
TextDim,
|
||||
TextInactive,
|
||||
TextA5,
|
||||
TextA20,
|
||||
TextA30,
|
||||
TextA50,
|
||||
TextA80,
|
||||
Primary,
|
||||
PrimaryL,
|
||||
PrimaryD,
|
||||
PrimaryA5,
|
||||
PrimaryA20,
|
||||
PrimaryA30,
|
||||
PrimaryA50,
|
||||
PrimaryA80,
|
||||
Secondary,
|
||||
SecondaryL,
|
||||
SecondaryD,
|
||||
SecondaryA5,
|
||||
SecondaryA20,
|
||||
SecondaryA30,
|
||||
SecondaryA50,
|
||||
SecondaryA80,
|
||||
Warning,
|
||||
WarningL,
|
||||
WarningD,
|
||||
WarningA5,
|
||||
WarningA20,
|
||||
WarningA30,
|
||||
WarningA50,
|
||||
WarningA80,
|
||||
Danger,
|
||||
DangerL,
|
||||
DangerD,
|
||||
DangerA5,
|
||||
DangerA20,
|
||||
DangerA30,
|
||||
DangerA50,
|
||||
DangerA80,
|
||||
Success,
|
||||
SuccessL,
|
||||
SuccessD,
|
||||
SuccessA5,
|
||||
SuccessA20,
|
||||
SuccessA30,
|
||||
SuccessA50,
|
||||
SuccessA80,
|
||||
Border,
|
||||
BorderSoft,
|
||||
BorderHard,
|
||||
BorderSolid,
|
||||
Transparent,
|
||||
A,
|
||||
AL,
|
||||
AD,
|
||||
AA5,
|
||||
AA20,
|
||||
AA30,
|
||||
AA50,
|
||||
AA80,
|
||||
White,
|
||||
WhiteA5,
|
||||
WhiteA20,
|
||||
WhiteA30,
|
||||
WhiteA50,
|
||||
WhiteA80,
|
||||
BW05,
|
||||
BW10,
|
||||
BW25,
|
||||
BW50,
|
||||
BW75,
|
||||
BW90,
|
||||
Player1,
|
||||
Player2,
|
||||
Player3,
|
||||
Player4,
|
||||
Player5,
|
||||
Player6,
|
||||
Player7,
|
||||
Player8,
|
||||
|
||||
size,
|
||||
};
|
||||
size,
|
||||
};
|
||||
|
||||
const char *get_theme_color_name(recompui::ThemeColor color);
|
||||
void set_theme_color(ThemeColor color, const recompui::Color &value);
|
||||
const recompui::Color &get_theme_color(recompui::ThemeColor color);
|
||||
namespace border {
|
||||
extern float radius_sm;
|
||||
extern float radius_md;
|
||||
extern float radius_lg;
|
||||
extern float width;
|
||||
}
|
||||
|
||||
const char *get_theme_color_name(theme::color color);
|
||||
void set_theme_color(theme::color color, const recompui::Color &value);
|
||||
const recompui::Color &get_theme_color(theme::color color);
|
||||
|
||||
} // namespace theme
|
||||
} // namespace recompui
|
||||
|
||||
@@ -5,29 +5,34 @@
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
static constexpr float toggle_height = 72.0f;
|
||||
static constexpr float toggle_width = 162.0f;
|
||||
|
||||
static constexpr float toggle_floater_height = 64.0f;
|
||||
static constexpr float toggle_floater_width = 80.0f;
|
||||
|
||||
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "button") {
|
||||
enable_focus();
|
||||
|
||||
set_width(162.0f);
|
||||
set_height(72.0f);
|
||||
set_border_radius(36.0f);
|
||||
set_width(toggle_width);
|
||||
set_height(toggle_height);
|
||||
set_border_radius(toggle_height * 0.5f);
|
||||
set_opacity(0.9f);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_border_width(2.0f);
|
||||
set_border_color(ThemeColor::DangerD);
|
||||
set_background_color(ThemeColor::Transparent);
|
||||
checked_style.set_border_color(ThemeColor::Success);
|
||||
hover_style.set_border_color(ThemeColor::DangerD);
|
||||
hover_style.set_background_color(ThemeColor::DangerA30);
|
||||
focus_style.set_border_color(ThemeColor::DangerD);
|
||||
focus_style.set_background_color(ThemeColor::DangerA30);
|
||||
checked_hover_style.set_border_color(ThemeColor::Success);
|
||||
checked_hover_style.set_background_color(ThemeColor::SuccessA30);
|
||||
checked_focus_style.set_border_color(ThemeColor::Success);
|
||||
checked_focus_style.set_background_color(ThemeColor::SuccessA30);
|
||||
disabled_style.set_border_color(ThemeColor::DangerD, 128);
|
||||
checked_disabled_style.set_border_color(ThemeColor::SuccessD, 128);
|
||||
set_border_width(theme::border::width);
|
||||
set_border_color(theme::color::BW50);
|
||||
set_background_color(theme::color::Transparent);
|
||||
checked_style.set_border_color(theme::color::Primary);
|
||||
hover_style.set_border_color(theme::color::BW50);
|
||||
hover_style.set_background_color(theme::color::WhiteA5);
|
||||
focus_style.set_border_color(theme::color::BW50);
|
||||
focus_style.set_background_color(theme::color::WhiteA5);
|
||||
checked_hover_style.set_border_color(theme::color::Primary);
|
||||
checked_hover_style.set_background_color(theme::color::PrimaryA30);
|
||||
checked_focus_style.set_border_color(theme::color::Primary);
|
||||
checked_focus_style.set_background_color(theme::color::PrimaryA30);
|
||||
disabled_style.set_border_color(theme::color::BW50, 128);
|
||||
checked_disabled_style.set_border_color(theme::color::PrimaryD, 128);
|
||||
add_style(&checked_style, checked_state);
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
@@ -41,13 +46,13 @@ namespace recompui {
|
||||
floater = context.create_element<Element>(this);
|
||||
floater->set_position(Position::Relative);
|
||||
floater->set_top(2.0f);
|
||||
floater->set_width(80.0f);
|
||||
floater->set_height(64.0f);
|
||||
floater->set_border_radius(32.0f);
|
||||
floater->set_background_color(ThemeColor::DangerD);
|
||||
floater_checked_style.set_background_color(ThemeColor::Success);
|
||||
floater_disabled_style.set_background_color(ThemeColor::DangerD, 128);
|
||||
floater_disabled_checked_style.set_background_color(ThemeColor::SuccessD, 128);
|
||||
floater->set_width(toggle_floater_width);
|
||||
floater->set_height(toggle_floater_height);
|
||||
floater->set_border_radius(toggle_height * 0.5f);
|
||||
floater->set_background_color(theme::color::TextDim);
|
||||
floater_checked_style.set_background_color(theme::color::Primary);
|
||||
floater_disabled_style.set_background_color(theme::color::TextDim, 128);
|
||||
floater_disabled_checked_style.set_background_color(theme::color::PrimaryD, 128);
|
||||
floater->add_style(&floater_checked_style, checked_state);
|
||||
floater->add_style(&floater_disabled_style, disabled_state);
|
||||
floater->add_style(&floater_disabled_checked_style, { checked_state, disabled_state });
|
||||
|
||||
@@ -60,6 +60,11 @@ namespace recompui {
|
||||
Count
|
||||
};
|
||||
|
||||
enum class PointerEvents {
|
||||
None,
|
||||
Auto
|
||||
};
|
||||
|
||||
template <typename Enum, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||
constexpr uint32_t Events(Enum first) {
|
||||
return 1u << static_cast<uint32_t>(first);
|
||||
@@ -222,6 +227,12 @@ namespace recompui {
|
||||
ColumnReverse
|
||||
};
|
||||
|
||||
enum class FlexWrap {
|
||||
NoWrap,
|
||||
Wrap,
|
||||
WrapReverse
|
||||
};
|
||||
|
||||
enum class AlignItems {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "ui_assign_players_modal.h"
|
||||
#include "elements/ui_label.h"
|
||||
#include "elements/ui_container.h"
|
||||
#include "recomp_ui.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
recompui::ContextId assign_players_modal_context;
|
||||
|
||||
static const float assignPlayersHFPaddingVert = 20.0f;
|
||||
static const float assignPlayersHFPaddingHorz = 20.0f;
|
||||
|
||||
static void set_button_side_styles(Element *el) {
|
||||
el->set_align_items(AlignItems::Center);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_flex_basis_auto();
|
||||
el->set_gap(8.0f);
|
||||
}
|
||||
|
||||
AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "div", false) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_display(Display::None);
|
||||
|
||||
Element* modal_overlay = context.create_element<Element>(this);
|
||||
modal_overlay->set_background_color(theme::color::BGOverlay);
|
||||
modal_overlay->set_position(Position::Absolute);
|
||||
modal_overlay->set_top(0);
|
||||
modal_overlay->set_right(0);
|
||||
modal_overlay->set_bottom(0);
|
||||
modal_overlay->set_left(0);
|
||||
|
||||
Element* modal_whole_page_wrapper = context.create_element<Element>(this);
|
||||
modal_whole_page_wrapper->set_display(Display::Flex);
|
||||
modal_whole_page_wrapper->set_position(Position::Absolute);
|
||||
modal_whole_page_wrapper->set_top(0);
|
||||
modal_whole_page_wrapper->set_right(0);
|
||||
modal_whole_page_wrapper->set_bottom(0);
|
||||
modal_whole_page_wrapper->set_left(0);
|
||||
modal_whole_page_wrapper->set_align_items(AlignItems::Center);
|
||||
modal_whole_page_wrapper->set_justify_content(JustifyContent::Center);
|
||||
|
||||
Element* modal = context.create_element<Element>(modal_whole_page_wrapper);
|
||||
modal->set_display(Display::Flex);
|
||||
modal->set_position(Position::Relative);
|
||||
modal->set_flex(1.0f, 1.0f);
|
||||
modal->set_flex_basis(100, Unit::Percent);
|
||||
modal->set_flex_direction(FlexDirection::Column);
|
||||
modal->set_width(100, Unit::Percent);
|
||||
modal->set_max_width(700, Unit::Dp);
|
||||
modal->set_height_auto();
|
||||
modal->set_margin_auto();
|
||||
modal->set_border_width(theme::border::width, Unit::Dp);
|
||||
modal->set_border_radius(theme::border::radius_lg, Unit::Dp);
|
||||
modal->set_border_color(theme::color::WhiteA20);
|
||||
modal->set_background_color(theme::color::ModalOverlay);
|
||||
|
||||
fake_focus_button = context.create_element<Element>(modal, 0, "button", false);
|
||||
fake_focus_button->set_position(Position::Absolute);
|
||||
fake_focus_button->set_width(0, Unit::Dp);
|
||||
fake_focus_button->set_height(0, Unit::Dp);
|
||||
fake_focus_button->set_opacity(0);
|
||||
|
||||
context.create_element<Label>(modal, "Assign Players", LabelStyle::Large);
|
||||
|
||||
player_elements_wrapper = context.create_element<Element>(modal, 0, "div", false);
|
||||
player_elements_wrapper->set_display(Display::Flex);
|
||||
player_elements_wrapper->set_flex_direction(FlexDirection::Row);
|
||||
player_elements_wrapper->set_justify_content(JustifyContent::SpaceBetween);
|
||||
player_elements_wrapper->set_align_items(AlignItems::Center);
|
||||
player_elements_wrapper->set_width(100, Unit::Percent);
|
||||
player_elements_wrapper->set_padding(24, Unit::Dp);
|
||||
|
||||
Element* footer = context.create_element<Element>(modal, 0, "div", false);
|
||||
footer->set_display(Display::Flex);
|
||||
footer->set_position(Position::Relative);
|
||||
footer->set_flex_direction(FlexDirection::Row);
|
||||
footer->set_align_items(AlignItems::Center);
|
||||
footer->set_justify_content(JustifyContent::SpaceBetween);
|
||||
footer->set_width(100.0f, Unit::Percent);
|
||||
footer->set_height_auto();
|
||||
|
||||
footer->set_padding_top(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_bottom(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_left(assignPlayersHFPaddingHorz);
|
||||
footer->set_padding_right(assignPlayersHFPaddingHorz);
|
||||
|
||||
auto left = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexStart, 0);
|
||||
set_button_side_styles(left);
|
||||
close_button = context.create_element<Button>(left, "Cancel", ButtonStyle::Tertiary);
|
||||
close_button->add_pressed_callback(recompinput::stop_player_assignment_and_close_modal);
|
||||
|
||||
auto right = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
retry_button = context.create_element<Button>(right, "Retry", ButtonStyle::Warning);
|
||||
retry_button->set_enabled(false);
|
||||
retry_button->add_pressed_callback(recompinput::start_player_assignment);
|
||||
|
||||
confirm_button = context.create_element<Button>(right, "Confirm", ButtonStyle::Primary);
|
||||
confirm_button->set_enabled(false);
|
||||
confirm_button->add_pressed_callback(recompinput::commit_player_assignment);
|
||||
set_button_side_styles(right);
|
||||
}
|
||||
|
||||
AssignPlayersModal::~AssignPlayersModal() {
|
||||
}
|
||||
|
||||
void AssignPlayersModal::process_event(const Event &e) {
|
||||
if (!is_open) {
|
||||
return;
|
||||
}
|
||||
if (e.type == EventType::Update) {
|
||||
if (player_elements.empty() || player_elements.size() != recompinput::get_num_players()) {
|
||||
create_player_elements();
|
||||
}
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
player_elements[i]->update_assignment_player_card();
|
||||
}
|
||||
|
||||
if (!recompinput::is_player_assignment_active()) {
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
if (was_assigning) {
|
||||
confirm_button->focus();
|
||||
}
|
||||
}
|
||||
|
||||
was_assigning = false;
|
||||
} else {
|
||||
fake_focus_button->focus();
|
||||
was_assigning = true;
|
||||
}
|
||||
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
} else {
|
||||
confirm_button->set_enabled(false);
|
||||
retry_button->set_enabled(false);
|
||||
}
|
||||
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
|
||||
void AssignPlayersModal::create_player_elements() {
|
||||
player_elements_wrapper->clear_children();
|
||||
player_elements.clear();
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
PlayerCard* player_element = context.create_element<PlayerCard>(player_elements_wrapper, i, true);
|
||||
player_elements.push_back(player_element);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignPlayersModal::open() {
|
||||
if (!recompui::is_context_shown(assign_players_modal_context)) {
|
||||
recompui::show_context(assign_players_modal_context, "");
|
||||
}
|
||||
|
||||
is_open = true;
|
||||
set_display(Display::Block);
|
||||
create_player_elements();
|
||||
queue_update();
|
||||
}
|
||||
void AssignPlayersModal::close() {
|
||||
if (recompui::is_context_shown(assign_players_modal_context)) {
|
||||
set_display(Display::None);
|
||||
recompui::hide_context(assign_players_modal_context);
|
||||
}
|
||||
|
||||
is_open = false;
|
||||
}
|
||||
|
||||
recompui::AssignPlayersModal *assign_players_modal = nullptr;
|
||||
|
||||
void init_assign_players_modal() {
|
||||
assign_players_modal_context = recompui::create_context();
|
||||
assign_players_modal_context.open();
|
||||
assign_players_modal = assign_players_modal_context.create_element<AssignPlayersModal>(assign_players_modal_context.get_root_element());
|
||||
assign_players_modal_context.close();
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_svg.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "ui_player_card.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class AssignPlayersModal : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
bool was_assigning = false;
|
||||
Element* player_elements_wrapper = nullptr;
|
||||
Element* fake_focus_button = nullptr;
|
||||
std::vector<PlayerCard*> player_elements = {};
|
||||
|
||||
Button* close_button = nullptr;
|
||||
Button* retry_button = nullptr;
|
||||
Button* confirm_button = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "AssignPlayersModal"; }
|
||||
private:
|
||||
void create_player_elements();
|
||||
public:
|
||||
AssignPlayersModal(Element *parent);
|
||||
virtual ~AssignPlayersModal();
|
||||
void open();
|
||||
void close();
|
||||
};
|
||||
|
||||
extern AssignPlayersModal *assign_players_modal;
|
||||
|
||||
void init_assign_players_modal();
|
||||
|
||||
} // namespace recompui
|
||||
@@ -38,9 +38,9 @@ namespace recompui {
|
||||
html_colours["transparent"] = Rml::Colourb(0, 0, 0, 0);
|
||||
html_colours["whitesmoke"] = Rml::Colourb(245, 245, 245);
|
||||
|
||||
for (std::size_t i = 0; i < (std::size_t)recompui::ThemeColor::size; i++) {
|
||||
const char *color_name = recompui::get_theme_color_name((recompui::ThemeColor)i);
|
||||
const recompui::Color color_value = recompui::get_theme_color((recompui::ThemeColor)i);
|
||||
for (std::size_t i = 0; i < (std::size_t)recompui::theme::color::size; i++) {
|
||||
const char *color_name = recompui::theme::get_theme_color_name((recompui::theme::color)i);
|
||||
const recompui::Color color_value = recompui::theme::get_theme_color((recompui::theme::color)i);
|
||||
Rml::String color_name_lower = Rml::StringUtilities::ToLower(color_name);
|
||||
html_colours[color_name_lower] = Rml::Colourb(color_value.r, color_value.g, color_value.b, color_value.a);
|
||||
}
|
||||
|
||||
+11
-217
@@ -15,13 +15,9 @@
|
||||
ultramodern::renderer::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle nav_help_model_handle;
|
||||
Rml::DataModelHandle general_model_handle;
|
||||
Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
|
||||
bool configuring_controller = false;
|
||||
|
||||
int recompui::config_tab_to_index(recompui::ConfigTab tab) {
|
||||
switch (tab) {
|
||||
case recompui::ConfigTab::General:
|
||||
@@ -91,9 +87,6 @@ void bind_atomic(Rml::DataModelConstructor& constructor, Rml::DataModelHandle ha
|
||||
);
|
||||
}
|
||||
|
||||
static int scanned_binding_index = -1;
|
||||
static int scanned_input_index = -1;
|
||||
static int focused_input_index = -1;
|
||||
static int focused_config_option_index = -1;
|
||||
|
||||
static bool msaa2x_supported = false;
|
||||
@@ -105,34 +98,6 @@ static bool cont_active = true;
|
||||
|
||||
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
||||
|
||||
int recomp::get_scanned_input_index() {
|
||||
return scanned_input_index;
|
||||
}
|
||||
|
||||
void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||
recomp::set_input_binding(static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
controls_model_handle.DirtyVariable("active_binding_input");
|
||||
controls_model_handle.DirtyVariable("active_binding_slot");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__accept");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__exit");
|
||||
graphics_model_handle.DirtyVariable("gfx_help__apply");
|
||||
}
|
||||
|
||||
void recomp::cancel_scanning_input() {
|
||||
recomp::stop_scanning_input();
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
controls_model_handle.DirtyVariable("active_binding_input");
|
||||
controls_model_handle.DirtyVariable("active_binding_slot");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__accept");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__exit");
|
||||
graphics_model_handle.DirtyVariable("gfx_help__apply");
|
||||
}
|
||||
|
||||
void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
if (cont_active != cont_interacted) {
|
||||
cont_active = cont_interacted;
|
||||
@@ -485,15 +450,6 @@ public:
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
banjo::open_quit_game_prompt();
|
||||
});
|
||||
|
||||
recompui::register_event(listener, "toggle_input_device",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
cur_device = cur_device == recomp::InputDevice::Controller
|
||||
? recomp::InputDevice::Keyboard
|
||||
: recomp::InputDevice::Controller;
|
||||
controls_model_handle.DirtyVariable("input_device_is_keyboard");
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
}
|
||||
|
||||
void bind_config_list_events(Rml::DataModelConstructor &constructor) {
|
||||
@@ -593,13 +549,14 @@ public:
|
||||
|
||||
constructor.BindFunc("gfx_help__apply", [](Rml::Variant& out) {
|
||||
if (cont_active) {
|
||||
// TODO: Needs the profile index.
|
||||
out = \
|
||||
(recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller).to_string() :
|
||||
(recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() :
|
||||
""
|
||||
) + \
|
||||
(recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller).to_string() :
|
||||
(recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() :
|
||||
""
|
||||
);
|
||||
} else {
|
||||
@@ -615,170 +572,6 @@ public:
|
||||
graphics_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_controls_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("controls_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the controls config menu");
|
||||
}
|
||||
|
||||
constructor.BindFunc("input_count", [](Rml::Variant& out) { out = static_cast<uint64_t>(recomp::get_num_inputs()); } );
|
||||
constructor.BindFunc("input_device_is_keyboard", [](Rml::Variant& out) { out = cur_device == recomp::InputDevice::Keyboard; } );
|
||||
|
||||
constructor.RegisterTransformFunc("get_input_name", [](const Rml::VariantList& inputs) {
|
||||
return Rml::Variant{recomp::get_input_name(static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>()))};
|
||||
});
|
||||
|
||||
constructor.RegisterTransformFunc("get_input_enum_name", [](const Rml::VariantList& inputs) {
|
||||
return Rml::Variant{recomp::get_input_enum_name(static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>()))};
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("set_input_binding",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
scanned_input_index = inputs.at(0).Get<size_t>();
|
||||
scanned_binding_index = inputs.at(1).Get<size_t>();
|
||||
recomp::start_scanning_input(cur_device);
|
||||
model_handle.DirtyVariable("active_binding_input");
|
||||
model_handle.DirtyVariable("active_binding_slot");
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
if (cur_device == recomp::InputDevice::Controller) {
|
||||
banjo::reset_cont_input_bindings();
|
||||
} else {
|
||||
banjo::reset_kb_input_bindings();
|
||||
}
|
||||
model_handle.DirtyAllVariables();
|
||||
nav_help_model_handle.DirtyVariable("nav_help__accept");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__exit");
|
||||
graphics_model_handle.DirtyVariable("gfx_help__apply");
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("clear_input_bindings",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(input, binding_index, cur_device, recomp::InputField{});
|
||||
}
|
||||
model_handle.DirtyVariable("inputs");
|
||||
graphics_model_handle.DirtyVariable("gfx_help__apply");
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("reset_single_input_binding_to_default",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
banjo::reset_single_input_binding(cur_device, input);
|
||||
model_handle.DirtyVariable("inputs");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__accept");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__exit");
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("set_input_row_focus",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
int input_index = inputs.at(0).Get<size_t>();
|
||||
// watch for mouseout being overzealous during event bubbling, only clear if the event's attached element matches the current
|
||||
if (input_index == -1 && event.GetType() == "mouseout" && event.GetCurrentElement() != event.GetTargetElement()) {
|
||||
return;
|
||||
}
|
||||
focused_input_index = input_index;
|
||||
model_handle.DirtyVariable("cur_input_row");
|
||||
});
|
||||
|
||||
// Rml variable definition for an individual InputField.
|
||||
struct InputFieldVariableDefinition : public Rml::VariableDefinition {
|
||||
InputFieldVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Scalar) {}
|
||||
|
||||
virtual bool Get(void* ptr, Rml::Variant& variant) override { variant = reinterpret_cast<recomp::InputField*>(ptr)->to_string(); return true; }
|
||||
virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; }
|
||||
};
|
||||
// Static instance of the InputField variable definition to have a pointer to return to RmlUi.
|
||||
static InputFieldVariableDefinition input_field_definition_instance{};
|
||||
|
||||
// Rml variable definition for an array of InputField values (e.g. all the bindings for a single input).
|
||||
struct BindingContainerVariableDefinition : public Rml::VariableDefinition {
|
||||
BindingContainerVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Array) {}
|
||||
|
||||
virtual bool Get(void* ptr, Rml::Variant& variant) override { return false; }
|
||||
virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; }
|
||||
|
||||
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
|
||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
|
||||
return Rml::DataVariable{&input_field_definition_instance, &recomp::get_input_binding(input, address.index, cur_device)};
|
||||
}
|
||||
};
|
||||
// Static instance of the InputField array variable definition to have a fixed pointer to return to RmlUi.
|
||||
static BindingContainerVariableDefinition binding_container_var_instance{};
|
||||
|
||||
// Rml variable definition for an array of an array of InputField values (e.g. all the bindings for all inputs).
|
||||
struct BindingArrayContainerVariableDefinition : public Rml::VariableDefinition {
|
||||
BindingArrayContainerVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Array) {}
|
||||
|
||||
virtual bool Get(void* ptr, Rml::Variant& variant) override { return false; }
|
||||
virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; }
|
||||
|
||||
virtual int Size(void* ptr) override { return recomp::get_num_inputs(); }
|
||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
||||
// Encode the input index as the pointer to avoid needing to do any allocations.
|
||||
return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)address.index);
|
||||
}
|
||||
};
|
||||
|
||||
// Static instance of the BindingArrayContainerVariableDefinition variable definition to have a fixed pointer to return to RmlUi.
|
||||
static BindingArrayContainerVariableDefinition binding_array_var_instance{};
|
||||
|
||||
struct InputContainerVariableDefinition : public Rml::VariableDefinition {
|
||||
InputContainerVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Struct) {}
|
||||
|
||||
virtual bool Get(void* ptr, Rml::Variant& variant) override { return true; }
|
||||
virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; }
|
||||
|
||||
virtual int Size(void* ptr) override { return recomp::get_num_inputs(); }
|
||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
||||
if (address.name == "array") {
|
||||
return Rml::DataVariable(&binding_array_var_instance, nullptr);
|
||||
}
|
||||
else {
|
||||
recomp::GameInput input = recomp::get_input_from_enum_name(address.name);
|
||||
if (input != recomp::GameInput::COUNT) {
|
||||
return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)input);
|
||||
}
|
||||
}
|
||||
return Rml::DataVariable{};
|
||||
}
|
||||
};
|
||||
|
||||
// Dummy type to associate with the variable definition.
|
||||
struct InputContainer {};
|
||||
constructor.RegisterCustomDataVariableDefinition<InputContainer>(Rml::MakeUnique<InputContainerVariableDefinition>());
|
||||
|
||||
// Dummy instance of the dummy type to bind to the variable.
|
||||
static InputContainer dummy_container;
|
||||
constructor.Bind("inputs", &dummy_container);
|
||||
|
||||
constructor.BindFunc("cur_input_row", [](Rml::Variant& out) {
|
||||
if (focused_input_index == -1) {
|
||||
out = "NONE";
|
||||
}
|
||||
else {
|
||||
out = recomp::get_input_enum_name(static_cast<recomp::GameInput>(focused_input_index));
|
||||
}
|
||||
});
|
||||
|
||||
constructor.BindFunc("active_binding_input", [](Rml::Variant& out) {
|
||||
if (scanned_input_index == -1) {
|
||||
out = "NONE";
|
||||
}
|
||||
else {
|
||||
out = recomp::get_input_enum_name(static_cast<recomp::GameInput>(scanned_input_index));
|
||||
}
|
||||
});
|
||||
|
||||
constructor.Bind<int>("active_binding_slot", &scanned_binding_index);
|
||||
|
||||
controls_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_nav_help_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("nav_help_model");
|
||||
if (!constructor) {
|
||||
@@ -794,20 +587,22 @@ public:
|
||||
});
|
||||
|
||||
constructor.BindFunc("nav_help__accept", [](Rml::Variant& out) {
|
||||
// TODO: Needs the profile index.
|
||||
if (cont_active) {
|
||||
out = \
|
||||
recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller).to_string() + \
|
||||
recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller).to_string();
|
||||
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0).to_string() + \
|
||||
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1).to_string();
|
||||
} else {
|
||||
out = PF_KEYBOARD_ENTER;
|
||||
}
|
||||
});
|
||||
|
||||
constructor.BindFunc("nav_help__exit", [](Rml::Variant& out) {
|
||||
// TODO: Needs the profile index.
|
||||
if (cont_active) {
|
||||
out = \
|
||||
recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller).to_string() + \
|
||||
recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller).to_string();
|
||||
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0).to_string() + \
|
||||
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1).to_string();
|
||||
} else {
|
||||
out = PF_KEYBOARD_ESCAPE;
|
||||
}
|
||||
@@ -870,7 +665,6 @@ public:
|
||||
//recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
make_graphics_bindings(context);
|
||||
make_sound_options_bindings(context);
|
||||
make_debug_bindings(context);
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
#include "ui_config_page_controls.h"
|
||||
#include "ui_assign_players_modal.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_label.h"
|
||||
#include "elements/ui_toggle.h"
|
||||
#include "elements/ui_container.h"
|
||||
#include "elements/ui_binding_button.h"
|
||||
#include "elements/ui_select.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
const std::string_view active_state_style_name = "cont_opt_active";
|
||||
|
||||
GameInputRow::GameInputRow(
|
||||
Element *parent,
|
||||
GameInputContext *input_ctx,
|
||||
std::function<void()> on_hover_callback,
|
||||
on_bind_click_callback on_bind_click,
|
||||
on_clear_or_reset_callback on_clear_or_reset
|
||||
) : Element(parent, Events(EventType::Hover), "div", false) {
|
||||
this->input_id = input_ctx->input_id;
|
||||
this->on_hover_callback = on_hover_callback;
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height_auto();
|
||||
|
||||
set_padding_top(4.0f);
|
||||
set_padding_right(16.0f);
|
||||
set_padding_bottom(4.0f);
|
||||
set_padding_left(20.0f);
|
||||
set_border_radius(theme::border::radius_sm);
|
||||
set_background_color(theme::color::Transparent);
|
||||
|
||||
active_style.set_background_color(theme::color::BGOverlay);
|
||||
add_style(&active_style, active_state_style_name);
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto label = context.create_element<Label>(this, input_ctx->name, LabelStyle::Normal);
|
||||
label->set_flex_grow(2.0f);
|
||||
label->set_flex_shrink(1.0f);
|
||||
label->set_flex_basis(300.0f);
|
||||
label->set_height_auto();
|
||||
// TODO: whitespace nowrap impl
|
||||
|
||||
auto bindings_container = context.create_element<Element>(this, 0, "div", false);
|
||||
{
|
||||
bindings_container->set_display(Display::Flex);
|
||||
bindings_container->set_position(Position::Relative);
|
||||
bindings_container->set_flex_grow(2.0f);
|
||||
bindings_container->set_flex_shrink(1.0f);
|
||||
bindings_container->set_flex_basis(400.0f);
|
||||
bindings_container->set_flex_direction(FlexDirection::Row);
|
||||
bindings_container->set_align_items(AlignItems::Center);
|
||||
bindings_container->set_justify_content(JustifyContent::SpaceBetween);
|
||||
bindings_container->set_width(100.0f, Unit::Percent);
|
||||
bindings_container->set_height(56.0f);
|
||||
bindings_container->set_padding_right(12.0f);
|
||||
bindings_container->set_padding_left(4.0f);
|
||||
bindings_container->set_gap(4.0f);
|
||||
|
||||
for (size_t i = 0; i < recomp::bindings_per_input; i++) {
|
||||
BindingButton *binding_button = context.create_element<BindingButton>(bindings_container, "");
|
||||
binding_button->add_pressed_callback([this, i, on_bind_click]() {
|
||||
on_bind_click(this->input_id, i);
|
||||
});
|
||||
binding_buttons.push_back(binding_button);
|
||||
}
|
||||
}
|
||||
|
||||
if (input_ctx->clearable) {
|
||||
auto clear_button = context.create_element<IconButton>(this, "icons/Trash.svg", ButtonStyle::Danger, IconButtonSize::Large);
|
||||
clear_button->add_pressed_callback([this, on_clear_or_reset]() {
|
||||
on_clear_or_reset(this->input_id, false);
|
||||
});
|
||||
} else {
|
||||
auto reset_button = context.create_element<IconButton>(this, "icons/Reset.svg", ButtonStyle::Warning, IconButtonSize::Large);
|
||||
reset_button->add_pressed_callback([this, on_clear_or_reset]() {
|
||||
on_clear_or_reset(this->input_id, true);
|
||||
});
|
||||
}
|
||||
|
||||
bindings.resize(recomp::bindings_per_input);
|
||||
for (size_t i = 0; i < recomp::bindings_per_input; i++) {
|
||||
bindings[i] = recompinput::InputField();
|
||||
}
|
||||
}
|
||||
|
||||
GameInputRow::~GameInputRow() {
|
||||
}
|
||||
|
||||
void GameInputRow::update_bindings(BindingList &new_bindings) {
|
||||
for (size_t i = 0; i < new_bindings.size(); i++) {
|
||||
binding_buttons[i]->set_is_binding(false);
|
||||
|
||||
// skip update if no changes
|
||||
if (
|
||||
new_bindings[i].input_id == bindings[i].input_id &&
|
||||
new_bindings[i].input_type == bindings[i].input_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
binding_buttons[i]->set_binding(new_bindings[i].to_string());
|
||||
bindings[i] = new_bindings[i];
|
||||
}
|
||||
}
|
||||
|
||||
void GameInputRow::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active;
|
||||
set_style_enabled(active_state_style_name, hover_active);
|
||||
if (hover_active && on_hover_callback) {
|
||||
on_hover_callback();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPageControls::ConfigPageControls(
|
||||
Element *parent,
|
||||
int num_players,
|
||||
std::vector<GameInputContext> game_input_contexts
|
||||
) : ConfigPage(parent) {
|
||||
this->game_input_contexts = game_input_contexts;
|
||||
this->num_players = num_players;
|
||||
this->multiplayer_enabled = num_players > 1;
|
||||
|
||||
multiplayer_view_mappings = !multiplayer_enabled;
|
||||
|
||||
set_selected_player(selected_player);
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
render_all();
|
||||
}
|
||||
|
||||
void ConfigPageControls::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Update:
|
||||
if (awaiting_binding && !recompinput::is_binding()) {
|
||||
awaiting_binding = false;
|
||||
update_control_mappings();
|
||||
}
|
||||
if (last_update_index != update_index) {
|
||||
last_update_index = update_index;
|
||||
render_all();
|
||||
}
|
||||
queue_update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::force_update() {
|
||||
update_index++;
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_all() {
|
||||
render_header();
|
||||
render_body();
|
||||
render_footer();
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_header() {
|
||||
if (!multiplayer_enabled) {
|
||||
hide_header();
|
||||
return;
|
||||
}
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
add_header();
|
||||
|
||||
// header left
|
||||
{
|
||||
auto header_left = header->get_left();
|
||||
header_left->clear_children();
|
||||
if (multiplayer_view_mappings) {
|
||||
auto profile_name = context.create_element<Label>(
|
||||
header_left,
|
||||
"Editing: " + recomp::get_input_profile_name(selected_profile_index),
|
||||
LabelStyle::Normal
|
||||
);
|
||||
} else {
|
||||
// Nothing rendered here as of now.. maybe single player toggle
|
||||
}
|
||||
}
|
||||
|
||||
// header right
|
||||
{
|
||||
auto header_right = header->get_right();
|
||||
header_right->clear_children();
|
||||
|
||||
if (multiplayer_view_mappings) {
|
||||
Button* go_back_button = context.create_element<Button>(header_right, "Go back", ButtonStyle::Tertiary);
|
||||
go_back_button->add_pressed_callback([this]() {
|
||||
this->multiplayer_view_mappings = false;
|
||||
this->force_update();
|
||||
});
|
||||
} else {
|
||||
Button* assign_players_button = context.create_element<Button>(header_right, "Assign players", ButtonStyle::Primary);
|
||||
assign_players_button->add_pressed_callback([]() {
|
||||
recompui::assign_players_modal->open();
|
||||
recompinput::start_player_assignment();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body() {
|
||||
bool show_mappings = (multiplayer_enabled && multiplayer_view_mappings) || !multiplayer_enabled;
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
if (show_mappings) {
|
||||
body->get_right()->set_display(Display::Flex);
|
||||
render_body_mappings();
|
||||
} else {
|
||||
body->get_right()->set_display(Display::None);
|
||||
render_body_players();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body_mappings() {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
// left side
|
||||
{
|
||||
render_control_mappings();
|
||||
}
|
||||
|
||||
// right side
|
||||
{
|
||||
body->get_right()->clear_children();
|
||||
description_container = context.create_element<Element>(body->get_right(), 0, "p", true);
|
||||
description_container->set_text(
|
||||
"Sometimes, the windows combine with the seams in a way\n"
|
||||
"That twitches on a peak at the place where the spirit was slain\n"
|
||||
"Hey, one foot leads to another\n"
|
||||
"Night's for sleep, blue curtains, covers, sequins in the eyes\n"
|
||||
"That's a fine time to dine\n"
|
||||
"Divine who's circling, feeding the cards to the midwives"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body_players() {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto body_left = body->get_left();
|
||||
body_left->clear_children();
|
||||
|
||||
auto player_grid = context.create_element<Element>(body_left, 0, "div", false);
|
||||
player_grid->set_display(Display::Flex);
|
||||
player_grid->set_flex_direction(FlexDirection::Row);
|
||||
player_grid->set_flex_wrap(FlexWrap::Wrap);
|
||||
player_grid->set_justify_content(JustifyContent::SpaceBetween);
|
||||
player_grid->set_align_items(AlignItems::Center);
|
||||
player_grid->set_width(100.0f, Unit::Percent);
|
||||
player_grid->set_height_auto();
|
||||
player_grid->set_gap(64.0f);
|
||||
|
||||
for (int i = 0; i < num_players; i++) {
|
||||
auto player_card = context.create_element<PlayerCard>(
|
||||
player_grid,
|
||||
i,
|
||||
false
|
||||
);
|
||||
player_card->set_on_select_profile_callback([this](int player_index, int profile_index) {
|
||||
this->on_select_player_profile(player_index, profile_index);
|
||||
});
|
||||
player_card->set_on_edit_profile_callback([this](int player_index) {
|
||||
this->on_edit_player_profile(player_index);
|
||||
});
|
||||
player_cards.push_back(player_card);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_select_player_profile(int player_index, int profile_index) {
|
||||
auto& assigned_player = recompinput::get_assigned_player(player_index);
|
||||
recomp::InputDevice device = recompinput::get_assigned_player_input_device(player_index);
|
||||
if (device != recomp::InputDevice::COUNT) {
|
||||
recomp::set_input_profile_for_player(player_index, profile_index, device);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_edit_player_profile(int player_index) {
|
||||
selected_player = player_index;
|
||||
recomp::InputDevice device = recompinput::get_assigned_player_input_device(player_index);
|
||||
if (device != recomp::InputDevice::COUNT) {
|
||||
selected_profile_index = recomp::get_input_profile_for_player(player_index, device);
|
||||
multiplayer_view_mappings = true;
|
||||
force_update();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_footer() {
|
||||
if (multiplayer_enabled && !multiplayer_view_mappings) {
|
||||
hide_footer();
|
||||
return;
|
||||
}
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
add_footer();
|
||||
{
|
||||
auto footer_left = footer->get_left();
|
||||
footer_left->clear_children();
|
||||
if (!multiplayer_enabled) {
|
||||
keyboard_toggle = context.create_element<Toggle>(footer_left);
|
||||
keyboard_toggle->set_checked(single_player_show_keyboard_mappings);
|
||||
keyboard_toggle->add_checked_callback([this](bool checked) {
|
||||
this->single_player_show_keyboard_mappings = checked;
|
||||
this->update_control_mappings();
|
||||
});
|
||||
Label *kb_label = context.create_element<Label>(footer_left, "Enable keyboard", LabelStyle::Normal);
|
||||
kb_label->set_margin_left(12.0f);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto footer_right = footer->get_right();
|
||||
footer_right->clear_children();
|
||||
auto reset_to_defaults_button = context.create_element<Button>(footer_right, "Reset to defaults", ButtonStyle::Warning);
|
||||
reset_to_defaults_button->add_pressed_callback([this]() {
|
||||
recomp::reset_profile_bindings(this->selected_profile_index, this->get_player_input_device());
|
||||
this->update_control_mappings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_control_mappings() {
|
||||
recompui::ContextId context = get_current_context();
|
||||
auto body_left = body->get_left();
|
||||
body_left->clear_children();
|
||||
|
||||
body_left->set_display(Display::Block);
|
||||
body_left->set_position(Position::Relative);
|
||||
body_left->set_height(100.0f, Unit::Percent);
|
||||
|
||||
{
|
||||
auto body_left_scroll = context.create_element<Element>(body_left, 0, "div", false);
|
||||
body_left_scroll->set_display(Display::Block);
|
||||
body_left_scroll->set_width(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_max_height(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_overflow_y(Overflow::Scroll);
|
||||
|
||||
game_input_rows.clear();
|
||||
for (int i = 0; i < game_input_contexts.size(); i++) {
|
||||
auto &ctx = game_input_contexts[i];
|
||||
GameInputRow *row = context.create_element<GameInputRow>(
|
||||
body_left_scroll,
|
||||
&ctx,
|
||||
[this, i]() {
|
||||
this->on_option_hover(i);
|
||||
},
|
||||
[this](recompinput::GameInput game_input, int input_index) {
|
||||
this->on_bind_click(game_input, input_index);
|
||||
},
|
||||
[this](recompinput::GameInput game_input, bool reset) {
|
||||
this->on_clear_or_reset_game_input(game_input, reset);
|
||||
}
|
||||
);
|
||||
game_input_rows.push_back(row);
|
||||
}
|
||||
}
|
||||
update_control_mappings();
|
||||
}
|
||||
|
||||
void ConfigPageControls::update_control_mappings() {
|
||||
if (!multiplayer_enabled) {
|
||||
selected_player = 0;
|
||||
selected_profile_index = single_player_show_keyboard_mappings
|
||||
? recomp::get_sp_keyboard_profile_index()
|
||||
: recomp::get_sp_controller_profile_index();
|
||||
} else if (!multiplayer_view_mappings) {
|
||||
return;
|
||||
}
|
||||
|
||||
game_input_bindings.clear();
|
||||
for (int i = 0; i < game_input_contexts.size(); i++) {
|
||||
GameInputContext &ctx = game_input_contexts[i];
|
||||
game_input_bindings[ctx.input_id] = {};
|
||||
|
||||
for (int j = 0; j < recomp::bindings_per_input; j++) {
|
||||
game_input_bindings[ctx.input_id].push_back(recomp::get_input_binding(selected_profile_index, ctx.input_id, j));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < game_input_rows.size(); i++) {
|
||||
game_input_rows[i]->update_bindings(
|
||||
game_input_bindings.at(game_input_rows[i]->get_input_id())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPageControls::~ConfigPageControls() {
|
||||
}
|
||||
|
||||
recompinput::InputDevice ConfigPageControls::get_player_input_device() {
|
||||
if (multiplayer_enabled) {
|
||||
return recompinput::get_assigned_player_input_device(this->selected_player);
|
||||
}
|
||||
|
||||
return single_player_show_keyboard_mappings
|
||||
? recomp::InputDevice::Keyboard
|
||||
: recomp::InputDevice::Controller;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_bind_click(recompinput::GameInput game_input, int input_index) {
|
||||
recompinput::InputDevice device = get_player_input_device();
|
||||
|
||||
recompinput::start_scanning_for_binding(this->selected_player, game_input, input_index, device);
|
||||
awaiting_binding = true;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_clear_or_reset_game_input(recompinput::GameInput game_input, bool reset) {
|
||||
if (!reset) {
|
||||
recomp::clear_input_binding(selected_profile_index, game_input);
|
||||
} else {
|
||||
recompinput::InputDevice device = get_player_input_device();
|
||||
recomp::reset_input_binding(selected_profile_index, device, game_input);
|
||||
}
|
||||
update_control_mappings();
|
||||
}
|
||||
|
||||
void ConfigPageControls::set_selected_player(int player) {
|
||||
selected_player = player;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_option_hover(uint8_t index) {
|
||||
if (description_container) {
|
||||
description_container->set_text(game_input_contexts[index].description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include "elements/ui_config_page.h"
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_icon_button.h"
|
||||
#include "elements/ui_binding_button.h"
|
||||
#include "elements/ui_pill_button.h"
|
||||
#include "elements/ui_toggle.h"
|
||||
#include "ui_player_card.h"
|
||||
|
||||
// TODO: remove after moving to recompinput
|
||||
namespace recompinput {
|
||||
using GameInput = recomp::GameInput;
|
||||
using InputField = recomp::InputField;
|
||||
using InputDevice = recomp::InputDevice;
|
||||
|
||||
constexpr int num_binding_slots = 4;
|
||||
} // recompinput
|
||||
|
||||
namespace recompui {
|
||||
|
||||
struct GameInputContext {
|
||||
std::string name;
|
||||
std::string description;
|
||||
recompinput::GameInput input_id;
|
||||
bool clearable;
|
||||
};
|
||||
|
||||
using BindingList = std::vector<recompinput::InputField>;
|
||||
// Receives which GameInput to be bound to, and the index of the binding that was clicked
|
||||
using on_bind_click_callback = std::function<void(recompinput::GameInput, int)>;
|
||||
// Player index, GameInput to be bound to, and the index of the binding that is being assigned
|
||||
using on_player_bind_callback = std::function<void(int, recompinput::GameInput, int)>;
|
||||
|
||||
using on_clear_or_reset_callback = std::function<void(recompinput::GameInput, bool)>;
|
||||
|
||||
// One single row of a game input mapping
|
||||
class GameInputRow : public Element {
|
||||
protected:
|
||||
recompinput::GameInput input_id;
|
||||
BindingList bindings;
|
||||
|
||||
int active_binding_index = -1;
|
||||
bool is_binding = false;
|
||||
|
||||
std::vector<BindingButton*> binding_buttons = {};
|
||||
|
||||
Style active_style;
|
||||
std::function<void()> on_hover_callback;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "GameInputRow"; }
|
||||
public:
|
||||
GameInputRow(
|
||||
Element *parent,
|
||||
GameInputContext *input_ctx,
|
||||
std::function<void()> on_hover_callback,
|
||||
on_bind_click_callback on_bind_click,
|
||||
on_clear_or_reset_callback on_clear_or_reset
|
||||
);
|
||||
virtual ~GameInputRow();
|
||||
void update_bindings(BindingList &new_bindings);
|
||||
recompinput::GameInput get_input_id() const { return input_id; }
|
||||
};
|
||||
|
||||
using PlayerBindings = std::map<recompinput::GameInput, BindingList>;
|
||||
|
||||
class ConfigPageControls : public ConfigPage {
|
||||
protected:
|
||||
// for tracking forced updates to entire page (major changes like player reassignment or singleplayer mode)
|
||||
int last_update_index = 0;
|
||||
int update_index = 0;
|
||||
|
||||
int selected_player = 0;
|
||||
int selected_profile_index = -1;
|
||||
int num_players;
|
||||
|
||||
bool multiplayer_enabled;
|
||||
bool multiplayer_view_mappings;
|
||||
|
||||
bool single_player_show_keyboard_mappings = false;
|
||||
|
||||
bool awaiting_binding = false;
|
||||
|
||||
std::vector<GameInputContext> game_input_contexts;
|
||||
PlayerBindings game_input_bindings;
|
||||
|
||||
std::vector<PlayerCard*> player_cards;
|
||||
std::vector<GameInputRow*> game_input_rows;
|
||||
Toggle *keyboard_toggle;
|
||||
Element *description_container = nullptr;
|
||||
on_player_bind_callback on_player_bind;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "ConfigPageControls"; }
|
||||
private:
|
||||
void on_option_hover(uint8_t index);
|
||||
void on_bind_click(recompinput::GameInput game_input, int input_index);
|
||||
void on_clear_or_reset_game_input(
|
||||
recompinput::GameInput game_input,
|
||||
bool reset = false
|
||||
);
|
||||
|
||||
void on_select_player_profile(int player_index, int profile_index);
|
||||
void on_edit_player_profile(int player_index);
|
||||
|
||||
void render_all();
|
||||
void render_header();
|
||||
void render_body();
|
||||
void render_body_players();
|
||||
void render_body_mappings();
|
||||
void render_footer();
|
||||
|
||||
recompinput::InputDevice get_player_input_device();
|
||||
public:
|
||||
ConfigPageControls(
|
||||
Element *parent,
|
||||
int num_players,
|
||||
std::vector<GameInputContext> game_input_contexts
|
||||
);
|
||||
virtual ~ConfigPageControls();
|
||||
|
||||
void force_update();
|
||||
void render_control_mappings();
|
||||
void update_control_mappings();
|
||||
void set_selected_player(int player);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "ui_config_page_controls_element.h"
|
||||
|
||||
// TODO: remove hardcoded recompinput funcs and data
|
||||
namespace recompinput {
|
||||
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigPageControls *controls_page = nullptr;
|
||||
|
||||
static bool is_multiplayer_enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#define DEFINE_INPUT(name, value, readable) GameInputContext{ readable, "Description for " #readable " input.", recompinput::GameInput::##name, !(recompinput::GameInput::##name == recompinput::GameInput::TOGGLE_MENU || recompinput::GameInput::##name == recompinput::GameInput::ACCEPT_MENU) },
|
||||
static std::vector<struct GameInputContext> temp_game_input_contexts = {
|
||||
DEFINE_ALL_INPUTS()
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
ElementConfigPageControls::ElementConfigPageControls(const Rml::String& tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
controls_page = context.create_element<ConfigPageControls>(
|
||||
&this_compat,
|
||||
recompinput::get_num_players(),
|
||||
temp_game_input_contexts
|
||||
);
|
||||
}
|
||||
|
||||
ElementConfigPageControls::~ElementConfigPageControls() {
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_config_page.h"
|
||||
#include "ui_config_page_controls.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
extern ConfigPageControls *controls_page;
|
||||
|
||||
class ElementConfigPageControls : public Rml::Element {
|
||||
public:
|
||||
ElementConfigPageControls(const Rml::String& tag);
|
||||
virtual ~ElementConfigPageControls();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "ui_config_page_example.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_icon_button.h"
|
||||
#include "elements/ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ElementConfigPageExample::ElementConfigPageExample(const Rml::String& tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto config_page = context.create_element<ConfigPage>(&this_compat);
|
||||
|
||||
auto body = config_page->get_body();
|
||||
{
|
||||
auto body_left = body->get_left();
|
||||
context.create_element<Label>(body_left, "First", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Second", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Third", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Fourth", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Fifth", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Sixth", LabelStyle::Normal);
|
||||
}
|
||||
{
|
||||
auto body_right = body->get_right();
|
||||
auto right_p = context.create_element<recompui::Element>(body_right, 0, "p", true);
|
||||
right_p->set_text("Testing a really long string here that should probably wrap if its actually cool... but it might not be cool? im not sure. but we will need some extra formatting which im forgetting about right now so i guess it'll have to use set inner html or something like that idk.");
|
||||
}
|
||||
|
||||
auto header = config_page->add_header();
|
||||
{
|
||||
auto header_left = header->get_left();
|
||||
context.create_element<Button>(header_left, "Primary", ButtonStyle::Primary);
|
||||
context.create_element<Button>(header_left, "Secondary", ButtonStyle::Secondary);
|
||||
context.create_element<Button>(header_left, "Large", ButtonStyle::Primary, ButtonSize::Large);
|
||||
context.create_element<Button>(header_left, "Medium", ButtonStyle::Primary, ButtonSize::Medium);
|
||||
context.create_element<Button>(header_left, "Small", ButtonStyle::Primary, ButtonSize::Small);
|
||||
|
||||
}
|
||||
{
|
||||
auto header_right = header->get_right();
|
||||
context.create_element<IconButton>(header_right, "icons/Arrow.svg", ButtonStyle::Tertiary, IconButtonSize::Small);
|
||||
context.create_element<IconButton>(header_right, "icons/Reset.svg", ButtonStyle::Danger, IconButtonSize::XLarge);
|
||||
}
|
||||
|
||||
auto footer = config_page->add_footer();
|
||||
{
|
||||
auto footer_left = footer->get_left();
|
||||
context.create_element<Button>(footer_left, "Goodbye", ButtonStyle::Warning);
|
||||
}
|
||||
{
|
||||
auto footer_right = footer->get_right();
|
||||
context.create_element<Button>(footer_right, "Last button", ButtonStyle::Success);
|
||||
context.create_element<IconButton>(footer_right, "icons/X.svg", ButtonStyle::Basic, IconButtonSize::Medium);
|
||||
}
|
||||
}
|
||||
|
||||
ElementConfigPageExample::~ElementConfigPageExample() {
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "elements/ui_config_page.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ElementConfigPageExample : public Rml::Element {
|
||||
public:
|
||||
ElementConfigPageExample(const Rml::String& tag);
|
||||
virtual ~ElementConfigPageExample();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -10,6 +10,8 @@ struct RecompCustomElement {
|
||||
static RecompCustomElement custom_elements[] = {
|
||||
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
|
||||
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
|
||||
CUSTOM_ELEMENT("recomp-config-page-example", recompui::ElementConfigPageExample),
|
||||
CUSTOM_ELEMENT("recomp-config-page-controls", recompui::ElementConfigPageControls),
|
||||
};
|
||||
|
||||
void recompui::register_custom_elements() {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include "ui_mod_menu.h"
|
||||
#include "ui_config_sub_menu.h"
|
||||
#include "ui_config_page_example.h"
|
||||
#include "ui_config_page_controls_element.h"
|
||||
|
||||
namespace recompui {
|
||||
void register_custom_elements();
|
||||
|
||||
@@ -11,7 +11,7 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
set_height(100.0f, Unit::Percent);
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_background_color(ThemeColor::BGOverlay);
|
||||
set_background_color(theme::color::BGOverlay);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
@@ -19,9 +19,9 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
header_container->set_flex(0.0f, 0.0f);
|
||||
header_container->set_padding(16.0f);
|
||||
header_container->set_gap(16.0f);
|
||||
header_container->set_background_color(ThemeColor::BGShadow);
|
||||
header_container->set_border_bottom_width(1.1f);
|
||||
header_container->set_border_bottom_color(ThemeColor::BorderSoft);
|
||||
header_container->set_background_color(theme::color::BGShadow);
|
||||
header_container->set_border_bottom_width(theme::border::width);
|
||||
header_container->set_border_bottom_color(theme::color::BorderSoft);
|
||||
{
|
||||
thumbnail_container = context.create_element<Container>(header_container, FlexDirection::Column, JustifyContent::SpaceEvenly);
|
||||
thumbnail_container->set_flex(0.0f, 0.0f);
|
||||
@@ -29,7 +29,7 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
thumbnail_image = context.create_element<Image>(thumbnail_container, "");
|
||||
thumbnail_image->set_width(100.0f);
|
||||
thumbnail_image->set_height(100.0f);
|
||||
thumbnail_image->set_background_color(ThemeColor::BGOverlay);
|
||||
thumbnail_image->set_background_color(theme::color::BGOverlay);
|
||||
}
|
||||
|
||||
header_details_container = context.create_element<Container>(header_container, FlexDirection::Column, JustifyContent::SpaceEvenly);
|
||||
@@ -55,9 +55,9 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
buttons_container->set_flex(0.0f, 0.0f);
|
||||
buttons_container->set_padding(16.0f);
|
||||
buttons_container->set_justify_content(JustifyContent::SpaceBetween);
|
||||
buttons_container->set_border_top_width(1.1f);
|
||||
buttons_container->set_border_top_color(ThemeColor::BorderSoft);
|
||||
buttons_container->set_background_color(ThemeColor::BGShadow);
|
||||
buttons_container->set_border_top_width(theme::border::width);
|
||||
buttons_container->set_border_top_color(theme::color::BorderSoft);
|
||||
buttons_container->set_background_color(theme::color::BGShadow);
|
||||
{
|
||||
enable_container = context.create_element<Container>(buttons_container, FlexDirection::Row, JustifyContent::FlexStart);
|
||||
enable_container->set_align_items(AlignItems::Center);
|
||||
@@ -68,6 +68,7 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
enable_toggle->set_nav_manual(NavDirection::Up, mod_tab_id);
|
||||
|
||||
enable_label = context.create_element<Label>(enable_container, "A currently enabled mod requires this mod", LabelStyle::Annotation);
|
||||
enable_label->set_color(theme::color::Primary);
|
||||
}
|
||||
|
||||
configure_button = context.create_element<Button>(buttons_container, "Configure", recompui::ButtonStyle::Secondary);
|
||||
|
||||
+17
-17
@@ -42,17 +42,17 @@ ModEntryView::ModEntryView(Element *parent) : Element(parent, Events(EventType::
|
||||
set_height_auto();
|
||||
set_padding(modEntryPadding);
|
||||
set_border_left_width(2.0f);
|
||||
set_border_color(ThemeColor::BorderSoft);
|
||||
set_background_color(ThemeColor::BorderSoft);
|
||||
set_border_color(theme::color::BorderSoft);
|
||||
set_background_color(theme::color::BorderSoft);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(ThemeColor::Text);
|
||||
set_color(theme::color::Text);
|
||||
|
||||
checked_style.set_border_color(ThemeColor::BorderSolid);
|
||||
checked_style.set_color(ThemeColor::White);
|
||||
checked_style.set_background_color(recompui::ThemeColor::Background3);
|
||||
hover_style.set_border_color(ThemeColor::BorderHard);
|
||||
checked_hover_style.set_border_color(ThemeColor::Text);
|
||||
pulsing_style.set_border_color(ThemeColor::SecondaryA80);
|
||||
checked_style.set_border_color(theme::color::BorderSolid);
|
||||
checked_style.set_color(theme::color::White);
|
||||
checked_style.set_background_color(recompui::theme::color::Background3);
|
||||
hover_style.set_border_color(theme::color::BorderHard);
|
||||
checked_hover_style.set_border_color(theme::color::Text);
|
||||
pulsing_style.set_border_color(theme::color::SecondaryA80);
|
||||
|
||||
{
|
||||
thumbnail_image = context.create_element<Image>(this, "");
|
||||
@@ -60,7 +60,7 @@ ModEntryView::ModEntryView(Element *parent) : Element(parent, Events(EventType::
|
||||
thumbnail_image->set_height(modEntryHeight);
|
||||
thumbnail_image->set_min_width(modEntryHeight);
|
||||
thumbnail_image->set_min_height(modEntryHeight);
|
||||
thumbnail_image->set_background_color(ThemeColor::BGOverlay);
|
||||
thumbnail_image->set_background_color(theme::color::BGOverlay);
|
||||
|
||||
|
||||
body_container = context.create_element<Element>(this);
|
||||
@@ -75,7 +75,7 @@ ModEntryView::ModEntryView(Element *parent) : Element(parent, Events(EventType::
|
||||
name_label = context.create_element<Label>(body_container, LabelStyle::Normal);
|
||||
description_label = context.create_element<Label>(body_container, LabelStyle::Small);
|
||||
description_label->set_margin_top(4.0f);
|
||||
description_label->set_color(ThemeColor::TextDim);
|
||||
description_label->set_color(theme::color::TextDim);
|
||||
} // body_container
|
||||
} // this
|
||||
|
||||
@@ -650,7 +650,7 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
list_container->set_flex_basis(100.0f);
|
||||
list_container->set_align_items(AlignItems::Center);
|
||||
list_container->set_height(100.0f, Unit::Percent);
|
||||
list_container->set_background_color(ThemeColor::BGShadow);
|
||||
list_container->set_background_color(theme::color::BGShadow);
|
||||
list_container->set_border_bottom_left_radius(16.0f);
|
||||
{
|
||||
list_scroll_container = context.create_element<ScrollContainer>(list_container, ScrollDirection::Vertical);
|
||||
@@ -673,9 +673,9 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
footer_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart);
|
||||
footer_container->set_width(100.0f, recompui::Unit::Percent);
|
||||
footer_container->set_align_items(recompui::AlignItems::Center);
|
||||
footer_container->set_background_color(ThemeColor::BGShadow);
|
||||
footer_container->set_border_top_width(1.1f);
|
||||
footer_container->set_border_top_color(ThemeColor::BorderSoft);
|
||||
footer_container->set_background_color(theme::color::BGShadow);
|
||||
footer_container->set_border_top_width(theme::border::width);
|
||||
footer_container->set_border_top_color(theme::color::BorderSoft);
|
||||
footer_container->set_padding(20.0f);
|
||||
footer_container->set_gap(20.0f);
|
||||
footer_container->set_border_bottom_left_radius(16.0f);
|
||||
@@ -688,11 +688,11 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
Element* footer_spacer = context.create_element<Element>(footer_container);
|
||||
footer_spacer->set_flex(1.0f, 0.0f);
|
||||
|
||||
refresh_button = context.create_element<Button>(footer_container, "Refresh", recompui::ButtonStyle::Tertiary);
|
||||
refresh_button = context.create_element<IconButton>(footer_container, "icons/Reset.svg", recompui::ButtonStyle::Secondary, recompui::IconButtonSize::XLarge);
|
||||
refresh_button->add_pressed_callback([this](){ refresh_mods(true); });
|
||||
refresh_button->set_nav_manual(NavDirection::Up, mod_tab_id);
|
||||
|
||||
mods_folder_button = context.create_element<Button>(footer_container, "Open Mods Folder", recompui::ButtonStyle::Primary);
|
||||
mods_folder_button = context.create_element<Button>(footer_container, "Open Mods Folder", recompui::ButtonStyle::Tertiary);
|
||||
mods_folder_button->add_pressed_callback([this](){ open_mods_folder(); });
|
||||
mods_folder_button->set_nav(NavDirection::Up, configure_button);
|
||||
mods_folder_button->set_nav_manual(NavDirection::Up, mod_tab_id);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "librecomp/mods.hpp"
|
||||
#include "elements/ui_scroll_container.h"
|
||||
#include "elements/ui_icon_button.h"
|
||||
#include "ui_config_sub_menu.h"
|
||||
#include "ui_mod_details_panel.h"
|
||||
|
||||
@@ -101,7 +102,7 @@ private:
|
||||
Container *body_empty_container = nullptr;
|
||||
Container *footer_container = nullptr;
|
||||
Button *install_mods_button = nullptr;
|
||||
Button *refresh_button = nullptr;
|
||||
IconButton *refresh_button = nullptr;
|
||||
Button *mods_folder_button = nullptr;
|
||||
int32_t active_mod_index = -1;
|
||||
std::vector<ModEntryButton *> mod_entry_buttons;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "ui_player_card.h"
|
||||
#include "elements/ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
static constexpr float assign_player_card_size = 128.0f;
|
||||
static constexpr float assign_player_card_icon_size = 64.0f;
|
||||
|
||||
static constexpr float static_player_card_size = 256.0f;
|
||||
static constexpr float static_player_card_icon_size = 128.0f;
|
||||
|
||||
PlayerCard::PlayerCard(
|
||||
Element *parent,
|
||||
int player_index,
|
||||
bool is_assignment_card
|
||||
) : Element(parent, 0, "div", false),
|
||||
player_index(player_index),
|
||||
is_assignment_card(is_assignment_card)
|
||||
{
|
||||
const float size = is_assignment_card ? assign_player_card_size : static_player_card_size;
|
||||
const float icon_size = is_assignment_card ? assign_player_card_icon_size : static_player_card_icon_size;
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_align_items(AlignItems::FlexStart);
|
||||
set_justify_content(JustifyContent::FlexStart);
|
||||
set_width(size);
|
||||
set_height_auto();
|
||||
set_gap(8.0f);
|
||||
|
||||
if (!is_assignment_card) {
|
||||
auto player_label = context.create_element<Label>(this, "Player " + std::to_string(player_index + 1), LabelStyle::Small);
|
||||
}
|
||||
|
||||
card = context.create_element<Element>(this, 0, "div", false);
|
||||
card->set_display(Display::Flex);
|
||||
card->set_flex_direction(FlexDirection::Column);
|
||||
card->set_align_items(AlignItems::Center);
|
||||
card->set_justify_content(JustifyContent::Center);
|
||||
card->set_width(size);
|
||||
card->set_height(size);
|
||||
card->set_border_color(theme::color::BorderSoft);
|
||||
card->set_border_width(theme::border::width, Unit::Dp);
|
||||
card->set_border_radius(theme::border::radius_sm, Unit::Dp);
|
||||
card->set_background_color(theme::color::Transparent);
|
||||
|
||||
icon = context.create_element<Svg>(card, "icons/RecordBorder.svg");
|
||||
icon->set_width(icon_size, Unit::Dp);
|
||||
icon->set_height(icon_size, Unit::Dp);
|
||||
icon->set_color(theme::color::TextDim);
|
||||
icon->set_display(Display::None);
|
||||
|
||||
if (!is_assignment_card) {
|
||||
recompinput::AssignedPlayer& assigned_player = recompinput::get_assigned_player(player_index, is_assignment_card);
|
||||
update_player_card_icon();
|
||||
|
||||
auto profile_label = context.create_element<Label>(this, "Profile", LabelStyle::Small);
|
||||
|
||||
profile_label->set_margin_top(8.0f);
|
||||
|
||||
bool has_controller = recompinput::does_player_have_controller(player_index);
|
||||
recomp::InputDevice device = has_controller ? recomp::InputDevice::Controller : recomp::InputDevice::Keyboard;
|
||||
const std::vector<int> profile_indices = recomp::get_indices_for_custom_profiles(device);
|
||||
int cur_profile = recomp::get_input_profile_for_player(player_index, device);
|
||||
|
||||
std::vector<SelectOption> options;
|
||||
|
||||
int device_profile_index = has_controller
|
||||
? recomp::get_controller_profile_index_from_sdl_controller(assigned_player.controller)
|
||||
: recomp::get_mp_keyboard_profile_index(player_index);
|
||||
|
||||
if (device_profile_index >= 0) {
|
||||
options.emplace_back(
|
||||
recomp::get_input_profile_name(device_profile_index),
|
||||
std::to_string(device_profile_index)
|
||||
);
|
||||
}
|
||||
|
||||
if (has_controller) {
|
||||
options.emplace_back(
|
||||
recomp::get_input_profile_name(recomp::get_sp_controller_profile_index()),
|
||||
std::to_string(recomp::get_sp_controller_profile_index())
|
||||
);
|
||||
} else {
|
||||
options.emplace_back(
|
||||
recomp::get_input_profile_name(recomp::get_sp_keyboard_profile_index()),
|
||||
std::to_string(recomp::get_sp_keyboard_profile_index())
|
||||
);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < profile_indices.size(); ++i) {
|
||||
int profile_index = profile_indices[i];
|
||||
std::string profile_name = recomp::get_input_profile_name(profile_index);
|
||||
options.emplace_back(profile_name, std::to_string(profile_index));
|
||||
}
|
||||
|
||||
auto select = context.create_element<Select>(
|
||||
this,
|
||||
options,
|
||||
std::to_string(cur_profile)
|
||||
);
|
||||
|
||||
select->add_change_callback([this](SelectOption& option, int option_index) {
|
||||
this->on_select_player_profile(std::stoi(option.value));
|
||||
});
|
||||
select->set_width(100.0f, Unit::Percent);
|
||||
select->set_enabled(assigned_player.is_assigned);
|
||||
|
||||
auto edit_profile_button = context.create_element<Button>(this, "Edit Profile", ButtonStyle::Secondary, ButtonSize::Medium);
|
||||
edit_profile_button->add_pressed_callback([this]() {
|
||||
this->on_edit_profile();
|
||||
});
|
||||
edit_profile_button->set_width(100.0f, Unit::Percent);
|
||||
edit_profile_button->set_enabled(assigned_player.is_assigned);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCard::~PlayerCard() {
|
||||
}
|
||||
|
||||
void PlayerCard::on_select_player_profile(int profile_index) {
|
||||
if (this->on_select_profile_callback) {
|
||||
this->on_select_profile_callback(this->player_index, profile_index);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCard::on_edit_profile() {
|
||||
if (this->on_edit_profile_callback) {
|
||||
this->on_edit_profile_callback(this->player_index);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCard::update_player_card_icon() {
|
||||
recompinput::AssignedPlayer& assigned_player = recompinput::get_assigned_player(player_index, is_assignment_card);
|
||||
if (assigned_player.is_assigned) {
|
||||
if (assigned_player.controller != nullptr) {
|
||||
icon->set_display(Display::Block);
|
||||
icon->set_src("icons/Cont.svg");
|
||||
} else {
|
||||
icon->set_display(Display::Block);
|
||||
icon->set_src("icons/Keyboard.svg");
|
||||
}
|
||||
} else {
|
||||
if (is_assignment_card) {
|
||||
icon->set_display(Display::Block);
|
||||
icon->set_src("icons/RecordBorder.svg");
|
||||
} else {
|
||||
icon->set_display(Display::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCard::update_assignment_player_card() {
|
||||
static const float scale_anim_duration = 0.25f;
|
||||
|
||||
update_player_card_icon();
|
||||
|
||||
if (!recompinput::get_player_is_assigned(player_index)) {
|
||||
icon->set_scale_2D(1.0f, 1.0f);
|
||||
card->set_background_color(theme::color::Transparent);
|
||||
icon->set_color(theme::color::TextDim);
|
||||
return;
|
||||
}
|
||||
|
||||
card->set_background_color(theme::color::PrimaryA20);
|
||||
|
||||
bool has_controller = recompinput::does_player_have_controller(player_index);
|
||||
|
||||
std::chrono::steady_clock::duration time_since_last_button_press = recompinput::get_player_time_since_last_button_press(player_index);
|
||||
auto millis = static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(time_since_last_button_press).count());
|
||||
float seconds = millis / 1000.0f;
|
||||
|
||||
if (seconds > 0 && seconds < scale_anim_duration) {
|
||||
float t = 1.0f - (seconds / scale_anim_duration);
|
||||
float scale = 1.0f + t * 0.15f;
|
||||
icon->set_scale_2D(scale, scale);
|
||||
icon->set_color(theme::color::Text, 200 + static_cast<int>(t * 55.0f));
|
||||
} else {
|
||||
icon->set_scale_2D(1.0f, 1.0f);
|
||||
icon->set_color(theme::color::Text, 200);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_svg.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_select.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
// player index, profile index
|
||||
using on_select_player_profile_callback = std::function<void(int, int)>;
|
||||
// player index
|
||||
using on_edit_player_profile_callback = std::function<void(int)>;
|
||||
|
||||
class PlayerCard : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
Element *card = nullptr;
|
||||
Svg* icon = nullptr;
|
||||
Select *profile_select = nullptr;
|
||||
int player_index = -1;
|
||||
bool is_assignment_card = false;
|
||||
|
||||
on_select_player_profile_callback on_select_profile_callback;
|
||||
on_edit_player_profile_callback on_edit_profile_callback;
|
||||
|
||||
std::string_view get_type_name() override { return "PlayerCard"; }
|
||||
private:
|
||||
void on_select_player_profile(int profile_index);
|
||||
void on_edit_profile();
|
||||
public:
|
||||
PlayerCard(Element *parent, int player_index, bool is_assignment_card = false);
|
||||
virtual ~PlayerCard();
|
||||
void update_assignment_player_card();
|
||||
void update_player_card_icon();
|
||||
void set_on_select_profile_callback(on_select_player_profile_callback callback) {
|
||||
on_select_profile_callback = std::move(callback);
|
||||
}
|
||||
void set_on_edit_profile_callback(on_edit_player_profile_callback callback) {
|
||||
on_edit_profile_callback = std::move(callback);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -61,10 +61,10 @@ void recompui::init_prompt_context() {
|
||||
Element* window = context.create_element<Element>(context.get_root_element());
|
||||
window->set_display(Display::Flex);
|
||||
window->set_flex_direction(FlexDirection::Column);
|
||||
window->set_background_color(ThemeColor::Transparent);
|
||||
window->set_background_color(theme::color::Transparent);
|
||||
|
||||
Element* prompt_overlay = context.create_element<Element>(window);
|
||||
prompt_overlay->set_background_color(ThemeColor::BGOverlay);
|
||||
prompt_overlay->set_background_color(theme::color::BGOverlay);
|
||||
prompt_overlay->set_position(Position::Absolute);
|
||||
prompt_overlay->set_top(0);
|
||||
prompt_overlay->set_right(0);
|
||||
@@ -91,10 +91,10 @@ void recompui::init_prompt_context() {
|
||||
prompt_content->set_max_width(700, Unit::Dp);
|
||||
prompt_content->set_height_auto();
|
||||
prompt_content->set_margin_auto();
|
||||
prompt_content->set_border_width(1.1, Unit::Dp);
|
||||
prompt_content->set_border_radius(16, Unit::Dp);
|
||||
prompt_content->set_border_color(ThemeColor::WhiteA20);
|
||||
prompt_content->set_background_color(ThemeColor::ModalOverlay);
|
||||
prompt_content->set_border_width(theme::border::width, Unit::Dp);
|
||||
prompt_content->set_border_radius(theme::border::radius_lg, Unit::Dp);
|
||||
prompt_content->set_border_color(theme::color::WhiteA20);
|
||||
prompt_content->set_background_color(theme::color::ModalOverlay);
|
||||
|
||||
prompt_state.prompt_header = context.create_element<Label>(prompt_content, "", LabelStyle::Large);
|
||||
prompt_state.prompt_header->set_margin(24, Unit::Dp);
|
||||
@@ -112,8 +112,8 @@ void recompui::init_prompt_context() {
|
||||
prompt_state.prompt_controls->set_padding_bottom(24, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_padding_left(12, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_padding_right(12, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_border_top_width(1.1, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_border_top_color(ThemeColor::BorderSoft);
|
||||
prompt_state.prompt_controls->set_border_top_width(theme::border::width, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_border_top_color(theme::color::BorderSoft);
|
||||
|
||||
prompt_state.confirm_button = context.create_element<Button>(prompt_state.prompt_controls, "", ButtonStyle::Success);
|
||||
prompt_state.confirm_button->set_min_width(185.0f, Unit::Dp);
|
||||
|
||||
+23
-22
@@ -26,6 +26,7 @@
|
||||
#include "ui_mod_menu.h"
|
||||
#include "ui_mod_installer.h"
|
||||
#include "ui_renderer.h"
|
||||
#include "ui_assign_players_modal.h"
|
||||
|
||||
bool can_focus(Rml::Element* element) {
|
||||
return element->GetOwnerDocument() != nullptr && element->GetProperty(Rml::PropertyId::TabIndex)->Get<Rml::Style::TabIndex>() != Rml::Style::TabIndex::None;
|
||||
@@ -237,6 +238,7 @@ public:
|
||||
launcher_menu_controller->load_document();
|
||||
config_menu_controller->load_document();
|
||||
recompui::init_prompt_context();
|
||||
recompui::init_assign_players_modal();
|
||||
}
|
||||
|
||||
void unload() {
|
||||
@@ -464,30 +466,31 @@ bool recompui::try_deque_event(SDL_Event& out) {
|
||||
}
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
// TODO: Needs the profile index.
|
||||
// Configurable accept button in menu
|
||||
auto menuAcceptBinding0 = recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuAcceptBinding1 = recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller);
|
||||
auto menuAcceptBinding0 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0);
|
||||
auto menuAcceptBinding1 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuAcceptBinding0.input_type != 0 && button.button == menuAcceptBinding0.input_id) ||
|
||||
(menuAcceptBinding1.input_type != 0 && button.button == menuAcceptBinding1.input_id)) {
|
||||
if ((menuAcceptBinding0.input_type != recomp::InputType::None && button.button == menuAcceptBinding0.input_id) ||
|
||||
(menuAcceptBinding1.input_type != recomp::InputType::None && button.button == menuAcceptBinding1.input_id)) {
|
||||
return SDLK_RETURN;
|
||||
}
|
||||
|
||||
// Configurable apply button in menu
|
||||
auto menuApplyBinding0 = recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuApplyBinding1 = recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller);
|
||||
auto menuApplyBinding0 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0);
|
||||
auto menuApplyBinding1 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuApplyBinding0.input_type != 0 && button.button == menuApplyBinding0.input_id) ||
|
||||
(menuApplyBinding1.input_type != 0 && button.button == menuApplyBinding1.input_id)) {
|
||||
if ((menuApplyBinding0.input_type != recomp::InputType::None && button.button == menuApplyBinding0.input_id) ||
|
||||
(menuApplyBinding1.input_type != recomp::InputType::None && button.button == menuApplyBinding1.input_id)) {
|
||||
return SDLK_f;
|
||||
}
|
||||
|
||||
// Allows closing the menu
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuToggleBinding0.input_type != 0 && button.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != 0 && button.button == menuToggleBinding1.input_id)) {
|
||||
if ((menuToggleBinding0.input_type != recomp::InputType::None && button.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != recomp::InputType::None && button.button == menuToggleBinding1.input_id)) {
|
||||
return SDLK_ESCAPE;
|
||||
}
|
||||
|
||||
@@ -581,6 +584,8 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
static clock::time_point next_repeat_time = {};
|
||||
static int latest_controller_key_pressed = SDLK_UNKNOWN;
|
||||
|
||||
bool all_input_is_disabled = recomp::all_input_disabled();
|
||||
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool context_capturing_input = recompui::is_context_capturing_input();
|
||||
bool context_capturing_mouse = recompui::is_context_capturing_mouse();
|
||||
@@ -593,7 +598,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
}
|
||||
|
||||
if (!recomp::all_input_disabled()) {
|
||||
if (!all_input_is_disabled) {
|
||||
bool is_mouse_input = false;
|
||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
||||
switch (cur_event.type) {
|
||||
@@ -713,11 +718,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
|
||||
// TODO: Needs the profile index.
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuToggleBinding0.input_type != 0 && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != 0 && cur_event.cbutton.button == menuToggleBinding1.input_id)) {
|
||||
if ((menuToggleBinding0.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding1.input_id)) {
|
||||
open_config = true;
|
||||
}
|
||||
break;
|
||||
@@ -743,11 +749,6 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
recomp::config_menu_set_cont_or_kb(ui_state->cont_is_active);
|
||||
|
||||
recomp::InputField scanned_field = recomp::get_scanned_input();
|
||||
if (scanned_field != recomp::InputField{}) {
|
||||
recomp::finish_scanning_input(scanned_field);
|
||||
}
|
||||
|
||||
ui_state->update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_state->update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ recompui::Color recompui::get_pulse_color(uint32_t pulse_milliseconds) {
|
||||
|
||||
float factor = std::abs((2.0f * anim_offset / pulse_milliseconds) - 1.0f);
|
||||
return lerp_color(
|
||||
recompui::get_theme_color(ThemeColor::Secondary),
|
||||
recompui::get_theme_color(ThemeColor::SecondaryL),
|
||||
recompui::theme::get_theme_color(theme::color::Secondary),
|
||||
recompui::theme::get_theme_color(theme::color::SecondaryL),
|
||||
factor
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user