init config opt system w/ 3 types and description support
This commit is contained in:
parent
1e792299c8
commit
851541c550
|
@ -157,6 +157,15 @@ set (SOURCES
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementConfigGroup.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementConfigOption.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeCheckbox.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeRadioTabs.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeRange.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementDescription.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/config_options/ConfigRegistry.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/config_options/ConfigOption.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
|
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
|
||||||
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<link type="text/template" href="config_menu/graphics.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/sound.rml" />
|
||||||
<link type="text/template" href="config_menu/debug.rml" />
|
<link type="text/template" href="config_menu/debug.rml" />
|
||||||
|
<link type="text/template" href="config_menu/cheats.rml" />
|
||||||
<link type="text/template" href="components/prompt.rml" />
|
<link type="text/template" href="components/prompt.rml" />
|
||||||
</head>
|
</head>
|
||||||
<body class="window">
|
<body class="window">
|
||||||
|
@ -71,6 +72,13 @@
|
||||||
<panel class="config" data-model="debug_model">
|
<panel class="config" data-model="debug_model">
|
||||||
<template src="config-menu__debug" />
|
<template src="config-menu__debug" />
|
||||||
</panel>
|
</panel>
|
||||||
|
<tab class="tab" id="tab_cheats">
|
||||||
|
<div>Cheats</div>
|
||||||
|
<div class="tab__indicator"></div>
|
||||||
|
</tab>
|
||||||
|
<panel class="config">
|
||||||
|
<template src="config-menu__cheats" />
|
||||||
|
</panel>
|
||||||
</tabset>
|
</tabset>
|
||||||
<div class="config__icon-buttons">
|
<div class="config__icon-buttons">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template name="config-menu__cheats">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="config__form" id="conf-cheats__form">
|
||||||
|
<div class="config__hz-wrapper" id="conf-cheats__hz-wrapper">
|
||||||
|
<recomp-config-group recomp-data="cheats" />
|
||||||
|
<recomp-description />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</template>
|
File diff suppressed because one or more lines are too long
|
@ -99,159 +99,3 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-option {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin: space(16) space(0) space(24);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-top: space(16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__title {
|
|
||||||
@extend %label-md;
|
|
||||||
padding: 0 space(12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
input:first-of-type {
|
|
||||||
nav-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:last-of-type {
|
|
||||||
nav-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__tab-label {
|
|
||||||
@extend %label-sm;
|
|
||||||
@include trans-colors-opa;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
height: auto;
|
|
||||||
margin: space(4) space(12) 0;
|
|
||||||
padding: space(8) 0;
|
|
||||||
color: $color-text-inactive;
|
|
||||||
tab-index: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $color-text;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.radio {
|
|
||||||
@extend %nav-all;
|
|
||||||
@include trans-colors-opa;
|
|
||||||
visibility: visible;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
&:not(:disabled) {
|
|
||||||
&:checked + .config-option__tab-label {
|
|
||||||
border-bottom: 1dp;
|
|
||||||
border-color: $color-text;
|
|
||||||
color: $color-text;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rmlui-window:not([mouse-active]) &:focus + .config-option__tab-label {
|
|
||||||
transition: none;
|
|
||||||
animation: $focus-anim-border;
|
|
||||||
border-color: $color-secondary;
|
|
||||||
color: $color-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus + .config-option__tab-label,
|
|
||||||
&:hover + .config-option__tab-label {
|
|
||||||
color: $color-text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled + .config-option__tab-label {
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range slidertrack {
|
|
||||||
@include trans-colors;
|
|
||||||
height: 2dp;
|
|
||||||
margin-top: space(8);
|
|
||||||
background-color: $color-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderbar {
|
|
||||||
@include trans-colors;
|
|
||||||
width: space(16);
|
|
||||||
height: space(16);
|
|
||||||
margin-top: space(1);
|
|
||||||
margin-right: space(-8);
|
|
||||||
margin-left: space(-8);
|
|
||||||
transition: background-color $transition-quick;
|
|
||||||
border-radius: 8dp;
|
|
||||||
background-color: $color-text-dim;
|
|
||||||
|
|
||||||
.rmlui-window:not([mouse-active]) &:focus {
|
|
||||||
@include border($color-a);
|
|
||||||
animation: $focus-anim-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $color-text;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderbar:active,
|
|
||||||
input.range slidertrack:active + sliderbar {
|
|
||||||
background-color: $color-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.range sliderarrowdec,
|
|
||||||
input.range sliderarrowinc {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__details {
|
|
||||||
@extend %label-xs;
|
|
||||||
height: space(18);
|
|
||||||
margin: space(14) space(12) 0;
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__range-wrapper {
|
|
||||||
max-width: space(360);
|
|
||||||
margin-top: space(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-option__range-label {
|
|
||||||
@extend %label-sm;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
// flex: 0 0 space(32);
|
|
||||||
width: space(56);
|
|
||||||
margin: 0 12dp;
|
|
||||||
margin-right: space(16);
|
|
||||||
padding: 0;
|
|
||||||
color: $color-text;
|
|
||||||
tab-index: none;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
.config-description {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: space(16);
|
||||||
|
border-radius: 0dp;
|
||||||
|
border-bottom-right-radius: $border-radius-modal;
|
||||||
|
border-bottom-left-radius: $border-radius-modal;
|
||||||
|
background-color: $color-bg-shadow;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&__contents {
|
||||||
|
@extend %body;
|
||||||
|
padding: space(16);
|
||||||
|
line-height: space(28);
|
||||||
|
white-space: pre-line;
|
||||||
|
|
||||||
|
b {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $color-warning;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
.config-group {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&--scrollable {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: 0 0 0 space(16);
|
||||||
|
|
||||||
|
.config-group__wrapper {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
@extend %label-md;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
padding: space(16) 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
.config-option {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin: space(16) space(0) space(24);
|
||||||
|
|
||||||
|
&--hz {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: space(4);
|
||||||
|
margin-bottom: space(4);
|
||||||
|
|
||||||
|
.config-option__title {
|
||||||
|
@extend %label-md;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__list {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__title {
|
||||||
|
@extend %label-md;
|
||||||
|
padding: 0 space(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__radio-tabs,
|
||||||
|
.config-option__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
input:first-of-type {
|
||||||
|
nav-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:last-of-type {
|
||||||
|
nav-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__tab-label {
|
||||||
|
@extend %label-sm;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
margin: space(4) space(12) 0;
|
||||||
|
padding: space(8) 0;
|
||||||
|
color: $color-text-inactive;
|
||||||
|
tab-index: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__checkbox-wrapper {
|
||||||
|
@include trans-colors-opa;
|
||||||
|
width: space(32);
|
||||||
|
height: space(32);
|
||||||
|
margin: space(4) space(12) 0;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: $color-bg-overlay;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[checked] {
|
||||||
|
background-color: $color-a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__checkbox {
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
visibility: visible;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove & Replace old stylings
|
||||||
|
input.radio {
|
||||||
|
@extend %nav-all;
|
||||||
|
@include trans-colors-opa;
|
||||||
|
visibility: visible;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
&:checked + .config-option__tab-label {
|
||||||
|
border-bottom: 1dp;
|
||||||
|
border-color: $color-text;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus + .config-option__tab-label {
|
||||||
|
transition: none;
|
||||||
|
animation: $focus-anim-border;
|
||||||
|
border-color: $color-secondary;
|
||||||
|
color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus + .config-option__tab-label,
|
||||||
|
&:hover + .config-option__tab-label {
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled + .config-option__tab-label {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range slidertrack {
|
||||||
|
@include trans-colors;
|
||||||
|
height: 2dp;
|
||||||
|
margin-top: space(8);
|
||||||
|
background-color: $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderbar {
|
||||||
|
@include trans-colors;
|
||||||
|
width: space(16);
|
||||||
|
height: space(16);
|
||||||
|
margin-top: space(1);
|
||||||
|
margin-right: space(-8);
|
||||||
|
margin-left: space(-8);
|
||||||
|
transition: background-color $transition-quick;
|
||||||
|
border-radius: 8dp;
|
||||||
|
background-color: $color-text-dim;
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus {
|
||||||
|
@include border($color-a);
|
||||||
|
animation: $focus-anim-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderbar:active,
|
||||||
|
input.range slidertrack:active + sliderbar {
|
||||||
|
background-color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.range sliderarrowdec,
|
||||||
|
input.range sliderarrowinc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__details {
|
||||||
|
@extend %label-xs;
|
||||||
|
height: space(18);
|
||||||
|
margin: space(14) space(12) 0;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option-range {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
max-width: space(360);
|
||||||
|
height: auto;
|
||||||
|
margin-top: space(4);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
@extend %label-sm;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: space(56);
|
||||||
|
margin: 0 12dp;
|
||||||
|
margin-right: space(16);
|
||||||
|
padding: 0;
|
||||||
|
color: $color-text;
|
||||||
|
tab-index: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range-input {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
slidertrack {
|
||||||
|
@include trans-colors;
|
||||||
|
height: 2dp;
|
||||||
|
margin-top: space(8);
|
||||||
|
background-color: $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderbar {
|
||||||
|
@include trans-colors;
|
||||||
|
width: space(16);
|
||||||
|
height: space(16);
|
||||||
|
margin-top: space(1);
|
||||||
|
margin-right: space(-8);
|
||||||
|
margin-left: space(-8);
|
||||||
|
transition: background-color $transition-quick;
|
||||||
|
border-radius: 8dp;
|
||||||
|
background-color: $color-text-dim;
|
||||||
|
|
||||||
|
.rmlui-window:not([mouse-active]) &:focus {
|
||||||
|
@include border($color-a);
|
||||||
|
animation: $focus-anim-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderbar:active,
|
||||||
|
slidertrack:active + sliderbar {
|
||||||
|
background-color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderarrowdec,
|
||||||
|
sliderarrowinc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__range-wrapper {
|
||||||
|
max-width: space(360);
|
||||||
|
margin-top: space(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-option__range-label {
|
||||||
|
@extend %label-sm;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
// flex: 0 0 space(32);
|
||||||
|
width: space(56);
|
||||||
|
margin: 0 12dp;
|
||||||
|
margin-right: space(16);
|
||||||
|
padding: 0;
|
||||||
|
color: $color-text;
|
||||||
|
tab-index: none;
|
||||||
|
}
|
|
@ -2,6 +2,9 @@
|
||||||
@import "./ControlOption";
|
@import "./ControlOption";
|
||||||
@import "./Tabs";
|
@import "./Tabs";
|
||||||
@import "./Config";
|
@import "./Config";
|
||||||
|
@import "./ConfigGroup";
|
||||||
|
@import "./ConfigOption";
|
||||||
|
@import "./ConfigDescription";
|
||||||
@import "./InputConfig";
|
@import "./InputConfig";
|
||||||
@import "./Button";
|
@import "./Button";
|
||||||
@import "./IconButton";
|
@import "./IconButton";
|
||||||
|
|
|
@ -189,8 +189,7 @@ select {
|
||||||
// background: rgb(150,150,150)
|
// background: rgb(150,150,150)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
input.radio,
|
input.radio {
|
||||||
input.checkbox {
|
|
||||||
flex: 0;
|
flex: 0;
|
||||||
width:0dp;
|
width:0dp;
|
||||||
nav-up:auto;
|
nav-up:auto;
|
||||||
|
@ -200,3 +199,14 @@ input.checkbox {
|
||||||
tab-index:auto;
|
tab-index:auto;
|
||||||
focus:auto;
|
focus:auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.checkbox {
|
||||||
|
width: space(20);
|
||||||
|
height: space(20);
|
||||||
|
nav-up:auto;
|
||||||
|
nav-right:auto;
|
||||||
|
nav-down:auto;
|
||||||
|
nav-left:auto;
|
||||||
|
tab-index:auto;
|
||||||
|
focus:auto;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"consumables": "Consumables",
|
||||||
|
"consumables/infinite_magic": "Infinite Magic",
|
||||||
|
"consumables/infinite_magic:description": "You get sooo much magic lol!!",
|
||||||
|
"consumables/infinite_rupees": "Infinite Rupees",
|
||||||
|
"consumables/infinite_arrows": "Infinite Arrows",
|
||||||
|
"consumables/infinite_bombs": "Infinite Bombs",
|
||||||
|
"consumables/infinite_health": "Infinite Health",
|
||||||
|
|
||||||
|
"gameplay": "Gameplay",
|
||||||
|
|
||||||
|
"gameplay/movement": "Movement",
|
||||||
|
"gameplay/movement/L_for_fast": "Hold L to move fast",
|
||||||
|
"gameplay/movement/L_for_fast/values/off": "Off",
|
||||||
|
"gameplay/movement/L_for_fast/values/x2": "X2",
|
||||||
|
"gameplay/movement/L_for_fast/values/x4": "X4",
|
||||||
|
"gameplay/movement/L_for_fast/values/x6": "X6",
|
||||||
|
"gameplay/movement/L_to_levitate": "Hold L to levitate",
|
||||||
|
"gameplay/movement/always_quickspin": "Always quickspin",
|
||||||
|
"gameplay/movement/always_quickspin:description": "Always <b>quickspin</b> whenever using your <i>sword</i> and in a <i>state</i> where <i>you</i> can <b>quickspin.</b><br /><br />yeah...",
|
||||||
|
"gameplay/movement/link_size": "Link's Size",
|
||||||
|
|
||||||
|
"gameplay/abilities": "Abilities",
|
||||||
|
"gameplay/abilities/fd_anywhere": "Fierce Deity Anywhere",
|
||||||
|
"gameplay/abilities/permanent_razor_sword": "Permanent Razor Sword",
|
||||||
|
"gameplay/abilities/permanent_razor_sword2": "MORE Permanent Razor Sword"
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "CheckboxGroup",
|
||||||
|
"key": "consumables",
|
||||||
|
"toggle": true,
|
||||||
|
"toggleDefault": false,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_magic",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_rupees",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_arrows",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_bombs",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "infinite_health",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "gameplay",
|
||||||
|
"toggle": true,
|
||||||
|
"toggleDefault": true,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "movement",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "RadioTabs",
|
||||||
|
"key": "L_for_fast",
|
||||||
|
"default": "x2",
|
||||||
|
"values": [
|
||||||
|
"off",
|
||||||
|
"x2",
|
||||||
|
"x4",
|
||||||
|
"x6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "L_to_levitate",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "always_quickspin",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Range",
|
||||||
|
"key": "link_size",
|
||||||
|
"default": 100,
|
||||||
|
"suffix": "%",
|
||||||
|
"min": 20,
|
||||||
|
"max": 400,
|
||||||
|
"step": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Group",
|
||||||
|
"key": "abilities",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "fd_anywhere",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "permanent_razor_sword",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Checkbox",
|
||||||
|
"key": "permanent_razor_sword2",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -122,6 +122,8 @@ namespace recompui {
|
||||||
void message_box(const char* msg);
|
void message_box(const char* msg);
|
||||||
|
|
||||||
void set_render_hooks();
|
void set_render_hooks();
|
||||||
|
|
||||||
|
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 356b9f901e65dcde3b7368116f5a7e84087b07ef
|
Subproject commit cd8f4f4f2856677c50114dc7e7e6c3b77d80c1e0
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "librecomp/recomp.h"
|
#include "librecomp/recomp.h"
|
||||||
#include "librecomp/overlays.hpp"
|
#include "librecomp/overlays.hpp"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
#include "zelda_config.h"
|
#include "zelda_config.h"
|
||||||
#include "recomp_input.h"
|
#include "recomp_input.h"
|
||||||
#include "recomp_ui.h"
|
#include "recomp_ui.h"
|
||||||
|
@ -166,3 +167,20 @@ extern "C" void recomp_set_right_analog_suppressed(uint8_t* rdram, recomp_contex
|
||||||
|
|
||||||
recomp::set_right_analog_suppressed(suppressed);
|
recomp::set_right_analog_suppressed(suppressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_get_config_store_int(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
thread_local std::vector<char> key_buffer{};
|
||||||
|
key_buffer.clear();
|
||||||
|
|
||||||
|
char c;
|
||||||
|
PTR(char) cur_char = _arg<0, PTR(char)>(rdram, ctx);
|
||||||
|
size_t i = 0;
|
||||||
|
while ((c = MEM_B(cur_char, i)) != '\0') {
|
||||||
|
key_buffer.push_back(c);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_return(ctx, recomp::get_config_store_value<int>(
|
||||||
|
std::string_view{key_buffer.data(), key_buffer.size()}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
#include "ConfigOption.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
bool ConfigOption::validate(json& opt_json) {
|
||||||
|
auto type_struct = get_type_structure();
|
||||||
|
for (const auto& [key, expected_type] : type_struct) {
|
||||||
|
if (!opt_json.contains(key) || opt_json[key].type() != expected_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOption::ConfigOption(json& opt_json)
|
||||||
|
{
|
||||||
|
type = opt_json[ConfigOption::schema::type];
|
||||||
|
key = opt_json[ConfigOption::schema::key];
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOption::~ConfigOption()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,65 @@
|
||||||
|
#ifndef RECOMPUI_CONFIG_OPTION_H
|
||||||
|
#define RECOMPUI_CONFIG_OPTION_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
enum class ConfigOptionType {
|
||||||
|
Label,
|
||||||
|
// Base types
|
||||||
|
Checkbox,
|
||||||
|
RadioTabs,
|
||||||
|
Dropdown,
|
||||||
|
Range,
|
||||||
|
Trigger,
|
||||||
|
// Group types
|
||||||
|
CheckboxGroup,
|
||||||
|
Group,
|
||||||
|
OptionCount
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(ConfigOptionType, {
|
||||||
|
{ConfigOptionType::Label, "Label"},
|
||||||
|
{ConfigOptionType::Checkbox, "Checkbox"},
|
||||||
|
{ConfigOptionType::RadioTabs, "RadioTabs"},
|
||||||
|
{ConfigOptionType::Dropdown, "Dropdown"},
|
||||||
|
{ConfigOptionType::Range, "Range"},
|
||||||
|
{ConfigOptionType::Trigger, "Trigger"},
|
||||||
|
{ConfigOptionType::CheckboxGroup, "CheckboxGroup"},
|
||||||
|
{ConfigOptionType::Group, "Group"}
|
||||||
|
});
|
||||||
|
|
||||||
|
typedef std::unordered_map<std::string, nlohmann::detail::value_t> config_type_structure;
|
||||||
|
|
||||||
|
inline bool config_option_is_group(ConfigOptionType option_type) {
|
||||||
|
return option_type == ConfigOptionType::Group || option_type == ConfigOptionType::CheckboxGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Base Config Option class. Defines what type of option or group it is, and
|
||||||
|
sets the key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ConfigOption {
|
||||||
|
public:
|
||||||
|
ConfigOption(nlohmann::json& opt_json);
|
||||||
|
virtual ~ConfigOption();
|
||||||
|
|
||||||
|
bool validate(nlohmann::json& opt_json);
|
||||||
|
|
||||||
|
ConfigOptionType type;
|
||||||
|
std::string key;
|
||||||
|
|
||||||
|
struct schema {
|
||||||
|
static constexpr const char* type = "type";
|
||||||
|
static constexpr const char* key = "key";
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual const config_type_structure& get_type_structure() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,230 @@
|
||||||
|
#include "ConfigRegistry.h"
|
||||||
|
#include "ConfigOption.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
ConfigRegistry config_registry = {{}, {}};
|
||||||
|
|
||||||
|
#define TODO_PARSE_ERROR(m, t) printf("Value at key: \"%s\" was invalid. Expected: \"%s\"\n", m, t)
|
||||||
|
|
||||||
|
std::string get_string_in_json(const json& j, const std::string& key) {
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
auto find_it = j.find(key);
|
||||||
|
if (find_it != j.end()) {
|
||||||
|
auto at_val = j.at(key);
|
||||||
|
if (!at_val.is_string()) {
|
||||||
|
TODO_PARSE_ERROR(key, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
find_it->get_to(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_string_in_json_with_default(const json& j, const std::string& key, const std::string& default_val) {
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
auto find_it = j.find(key);
|
||||||
|
if (find_it != j.end()) {
|
||||||
|
auto at_val = j.at(key);
|
||||||
|
if (!at_val.is_string()) {
|
||||||
|
return default_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_it->get_to(ret);
|
||||||
|
} else {
|
||||||
|
return default_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validate_json_value_is_array(const json &j, const std::string& json_path, const std::string& arr_key) {
|
||||||
|
const auto &options = j.find(arr_key);
|
||||||
|
if (options == j.end()) {
|
||||||
|
TODO_PARSE_ERROR(json_path + "/" + arr_key, "array");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &opt_array = j[arr_key];
|
||||||
|
|
||||||
|
if (!opt_array.is_array()) {
|
||||||
|
TODO_PARSE_ERROR(json_path + "/" + arr_key, "array");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option
|
||||||
|
void register_config_option(
|
||||||
|
const json& j,
|
||||||
|
const std::string& previous_key, // previous path before current key
|
||||||
|
const std::string& config_group,
|
||||||
|
const std::string& json_path // path to this json object from the root
|
||||||
|
) {
|
||||||
|
const std::string key = get_string_in_json(j, ConfigOption::schema::key);
|
||||||
|
const std::string this_key = previous_key + "/" + key;
|
||||||
|
|
||||||
|
ConfigOptionType type = get_value_in_json<ConfigOptionType>(j, ConfigOption::schema::type);
|
||||||
|
|
||||||
|
config_registry.key_ref_map[this_key] = { config_group, json_path };
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ConfigOptionType::Checkbox: {
|
||||||
|
bool default_val = false;
|
||||||
|
if (j.find("default") != j.end()) {
|
||||||
|
default_val = get_value_in_json<bool>(j, "default");
|
||||||
|
}
|
||||||
|
recomp::set_config_store_value_and_default(this_key, default_val, default_val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConfigOptionType::RadioTabs: {
|
||||||
|
if (!validate_json_value_is_array(j, json_path, "values")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int default_val = 0;
|
||||||
|
if (j.find("default") != j.end()) {
|
||||||
|
const auto &opt_array = j["values"];
|
||||||
|
const std::string default_val_string = get_string_in_json(j, "default");
|
||||||
|
// Based on default value's string, find which option index corresponds
|
||||||
|
for (int i = 0; i < opt_array.size(); i++) {
|
||||||
|
const auto &j_opt = opt_array[i];
|
||||||
|
if (!j_opt.is_string()) {
|
||||||
|
TODO_PARSE_ERROR(key + "/values/" + std::to_string(i) , "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string opt_val = j_opt.get<std::string>();
|
||||||
|
if (opt_val == default_val_string) {
|
||||||
|
default_val = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recomp::set_config_store_value_and_default(this_key, default_val, default_val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConfigOptionType::Range: {
|
||||||
|
int default_val = 0;
|
||||||
|
int max = 0;
|
||||||
|
int min = 0;
|
||||||
|
int step = 1;
|
||||||
|
|
||||||
|
if (j.find("default") != j.end()) {
|
||||||
|
default_val = get_value_in_json<int>(j, "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max is required
|
||||||
|
if (j.find("max") != j.end()) {
|
||||||
|
max = get_value_in_json<int>(j, "max");
|
||||||
|
if (default_val > max) default_val = max;
|
||||||
|
} else {
|
||||||
|
TODO_PARSE_ERROR(key + "/max", "int");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.find("min") != j.end()) {
|
||||||
|
min = get_value_in_json<int>(j, "min");
|
||||||
|
if (default_val < min) default_val = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.find("step") != j.end()) {
|
||||||
|
step = get_value_in_json<int>(j, "step");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(max > min);
|
||||||
|
assert(step < max - min);
|
||||||
|
recomp::set_config_store_value_and_default(this_key, default_val, default_val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j.find("label") != j.end()) {
|
||||||
|
const std::string label = get_string_in_json(j, "label");
|
||||||
|
recomp::set_config_store_value(this_key, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((type == ConfigOptionType::Group || type == ConfigOptionType::CheckboxGroup) && j.find("options") != j.end()) {
|
||||||
|
if (!validate_json_value_is_array(j, json_path, "options")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &opt_array = j["options"];
|
||||||
|
|
||||||
|
for (int i = 0; i < opt_array.size(); i++) {
|
||||||
|
const auto &el = opt_array[i];
|
||||||
|
register_config_option(
|
||||||
|
el,
|
||||||
|
this_key,
|
||||||
|
config_group,
|
||||||
|
json_path + "/options/" + std::to_string(i)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_config(const std::string &json_str, const std::string &config_group) {
|
||||||
|
config_registry.group_json_map[config_group] = json::parse(json_str);
|
||||||
|
const auto &j = config_registry.group_json_map[config_group];
|
||||||
|
|
||||||
|
if (!j.is_array()) {
|
||||||
|
TODO_PARSE_ERROR("/", "array");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < j.size(); i++) {
|
||||||
|
const auto &el = j[i];
|
||||||
|
register_config_option(
|
||||||
|
el,
|
||||||
|
config_group,
|
||||||
|
config_group,
|
||||||
|
"/" + std::to_string(i) // json_path at top level
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_translation(const std::string &json_str, const std::string &config_group) {
|
||||||
|
const auto &j = json::parse(json_str);
|
||||||
|
|
||||||
|
if (!j.is_object()) {
|
||||||
|
TODO_PARSE_ERROR("/", "object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& el : j.items())
|
||||||
|
{
|
||||||
|
std::string translation_key = "translations/" + config_group + "/" + el.key();
|
||||||
|
const std::string value = el.value();
|
||||||
|
recomp::set_config_store_value(translation_key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json get_json_from_key(const std::string &config_key) {
|
||||||
|
if (config_registry.key_ref_map.find(config_key) == config_registry.key_ref_map.end()) {
|
||||||
|
// TODO: handle not finding config_key
|
||||||
|
printf("FAILURE: AddOptionTypeElement failed to find config_key '%s' in config_registry.key_ref_map\n", config_key);
|
||||||
|
}
|
||||||
|
const JSONRef& json_ref = config_registry.key_ref_map[config_key];
|
||||||
|
const json& group_json = config_registry.group_json_map[json_ref.config_group];
|
||||||
|
json::json_pointer pointer(json_ref.json_path);
|
||||||
|
return group_json[pointer];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool config_key_is_base_group(const std::string &config_group_key) {
|
||||||
|
// determine if the key references a base group by checking the group_json_map
|
||||||
|
if (config_registry.group_json_map.find(config_group_key) == config_registry.group_json_map.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
json& get_group_json(const std::string &config_group_key) {
|
||||||
|
return config_registry.group_json_map[config_group_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace recompui
|
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef RECOMPUI_CONFIG_REGISTRY_H
|
||||||
|
#define RECOMPUI_CONFIG_REGISTRY_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
#include <map>
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
struct JSONRef {
|
||||||
|
std::string config_group;
|
||||||
|
std::string json_path; // used as a json pointer
|
||||||
|
};
|
||||||
|
// config key -> JSONRef
|
||||||
|
typedef std::unordered_map<std::string, JSONRef> config_registry_key_reference_map;
|
||||||
|
// config group -> json
|
||||||
|
typedef std::unordered_map<std::string, nlohmann::json> config_registry_group_json_map;
|
||||||
|
|
||||||
|
struct ConfigRegistry {
|
||||||
|
config_registry_key_reference_map key_ref_map;
|
||||||
|
config_registry_group_json_map group_json_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ConfigRegistry config_registry;
|
||||||
|
|
||||||
|
void register_config(const std::string& json_str, const std::string& config_group);
|
||||||
|
void register_translation(const std::string &json_str, const std::string &config_group);
|
||||||
|
|
||||||
|
nlohmann::json get_json_from_key(const std::string &config_key);
|
||||||
|
std::string get_string_in_json(const nlohmann::json& j, const std::string& key);
|
||||||
|
std::string get_string_in_json_with_default(const nlohmann::json& j, const std::string& key, const std::string& default_val);
|
||||||
|
bool config_key_is_base_group(const std::string &config_group_key);
|
||||||
|
nlohmann::json& get_group_json(const std::string &config_group_key);
|
||||||
|
|
||||||
|
#define TODO_PARSE_ERROR(m, t) printf("Value at key: \"%s\" was invalid. Expected: \"%s\"\n", m, t)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T get_value_in_json(const nlohmann::json& j, const std::string& key) {
|
||||||
|
T ret;
|
||||||
|
|
||||||
|
auto find_it = j.find(key);
|
||||||
|
if (find_it != j.end()) {
|
||||||
|
auto at_val = j.at(key);
|
||||||
|
find_it->get_to(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T get_value_in_json_with_default(const nlohmann::json& j, const std::string& key, T default_val) {
|
||||||
|
T ret = default_val;
|
||||||
|
|
||||||
|
auto find_it = j.find(key);
|
||||||
|
if (find_it != j.end()) {
|
||||||
|
auto at_val = j.at(key);
|
||||||
|
find_it->get_to(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,139 @@
|
||||||
|
|
||||||
|
#include "ElementConfigGroup.h"
|
||||||
|
#include "ElementConfigOption.h"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include "ElementOptionTypeCheckbox.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string config_group_base_class = "config-group";
|
||||||
|
static const std::string config_group_base_class_scrollable = config_group_base_class + "--scrollable";
|
||||||
|
static const std::string config_group_title_class = config_group_base_class + "__title";
|
||||||
|
static const std::string config_group_title_class_hidden = config_group_title_class + "--hidden";
|
||||||
|
static const std::string config_group_wrapper_class = config_group_base_class + "__wrapper";
|
||||||
|
|
||||||
|
ElementConfigGroup::ElementConfigGroup(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
SetClass(config_group_base_class, true);
|
||||||
|
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
{
|
||||||
|
Rml::Element *title_el = AppendChild(doc->CreateElement("div"));
|
||||||
|
title_el->SetClass(config_group_title_class, true);
|
||||||
|
{
|
||||||
|
Rml::Element *text_el = title_el->AppendChild(doc->CreateTextNode("replace me"));
|
||||||
|
text_el->SetId("config-group-label");
|
||||||
|
} // title_el
|
||||||
|
|
||||||
|
Rml::Element *div_el = AppendChild(doc->CreateElement("div"));
|
||||||
|
div_el->SetClass(config_group_wrapper_class, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementConfigGroup::~ElementConfigGroup()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigGroup::SetTextLabel(const std::string& s) {
|
||||||
|
Rml::Element *title_container = GetChild(0);
|
||||||
|
Rml::ElementText *label = (Rml::ElementText *)title_container->GetElementById("config-group-label");
|
||||||
|
label->SetText(s);
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigGroup::ToggleTextLabelVisibility(bool show) {
|
||||||
|
Rml::Element *title_container = GetChild(0);
|
||||||
|
title_container->SetClass(config_group_title_class_hidden, !show);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigGroup::AddConfigOptionElement(const json& option_json) {
|
||||||
|
ConfigOptionType el_option_type = ConfigOptionType::Label;
|
||||||
|
from_json(option_json["type"], el_option_type);
|
||||||
|
|
||||||
|
const std::string key = get_string_in_json(option_json, ConfigOption::schema::key);
|
||||||
|
|
||||||
|
Rml::Element *option_container = GetChild(1);
|
||||||
|
Rml::Element *child_opt = nullptr;
|
||||||
|
|
||||||
|
if (config_option_is_group(el_option_type)) {
|
||||||
|
child_opt = (ElementConfigGroup *)option_container->AppendChild(
|
||||||
|
recompui::create_custom_element(GetOwnerDocument(), "recomp-config-group")
|
||||||
|
);
|
||||||
|
// TODO: Base class with option type + config_key
|
||||||
|
((ElementConfigGroup *)child_opt)->option_type = el_option_type;
|
||||||
|
} else {
|
||||||
|
child_opt = (ElementConfigOption *)option_container->AppendChild(
|
||||||
|
recompui::create_custom_element(GetOwnerDocument(), "recomp-config-option")
|
||||||
|
);
|
||||||
|
// TODO: Base class with option type + config_key
|
||||||
|
ElementConfigOption *opt_as_conf_opt = (ElementConfigOption *)child_opt;
|
||||||
|
opt_as_conf_opt->option_type = el_option_type;
|
||||||
|
opt_as_conf_opt->in_checkbox_group = option_type == ConfigOptionType::CheckboxGroup;
|
||||||
|
}
|
||||||
|
std::string full_key = config_key + "/" + key;
|
||||||
|
child_opt->SetAttribute("recomp-data", full_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static nlohmann::json get_options(std::string& config_key) {
|
||||||
|
if (config_key_is_base_group(config_key)) {
|
||||||
|
return get_group_json(config_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json& group_json = get_json_from_key(config_key);
|
||||||
|
return group_json["options"];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigGroup::OnAttributeChange(const Rml::ElementAttributes& changed_attributes)
|
||||||
|
{
|
||||||
|
// Call through to the base element's OnAttributeChange().
|
||||||
|
Rml::Element::OnAttributeChange(changed_attributes);
|
||||||
|
|
||||||
|
auto config_store_key_attr = changed_attributes.find("recomp-data");
|
||||||
|
if (config_store_key_attr != changed_attributes.end() && config_store_key_attr->second.GetType() == Rml::Variant::STRING) {
|
||||||
|
config_key = config_store_key_attr->second.Get<Rml::String>();
|
||||||
|
bool is_base_group = config_key_is_base_group(config_key);
|
||||||
|
|
||||||
|
option_type = ConfigOptionType::Label;
|
||||||
|
|
||||||
|
if (is_base_group) {
|
||||||
|
SetClass(config_group_base_class_scrollable, true);
|
||||||
|
option_type = ConfigOptionType::Group;
|
||||||
|
ToggleTextLabelVisibility(false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
auto value = recomp::get_config_store_value<std::string>("translations/" + config_key);
|
||||||
|
SetTextLabel(value);
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
SetTextLabel(e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
const json& group_json = get_json_from_key(config_key);
|
||||||
|
|
||||||
|
from_json(group_json["type"], option_type);
|
||||||
|
assert(
|
||||||
|
option_type == ConfigOptionType::Group || option_type == ConfigOptionType::CheckboxGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nlohmann::json& options = get_options(config_key);
|
||||||
|
|
||||||
|
for (int i = 0; i < options.size(); i++) {
|
||||||
|
const auto &el = options[i];
|
||||||
|
AddConfigOptionElement(el);
|
||||||
|
}
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENTS_CONFIG_GROUP_H
|
||||||
|
#define RECOMPUI_ELEMENTS_CONFIG_GROUP_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "json/json.hpp"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Base custom element representing a single config option.
|
||||||
|
Maps to other custom elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ElementConfigGroup : public Rml::Element {
|
||||||
|
public:
|
||||||
|
ElementConfigGroup(const Rml::String& tag);
|
||||||
|
virtual ~ElementConfigGroup();
|
||||||
|
|
||||||
|
ConfigOptionType option_type;
|
||||||
|
std::string config_key;
|
||||||
|
protected:
|
||||||
|
void OnAttributeChange(const Rml::ElementAttributes& changed_attributes);
|
||||||
|
void AddConfigOptionElement(const nlohmann::json& option_json);
|
||||||
|
void SetTextLabel(const std::string& s);
|
||||||
|
void ToggleTextLabelVisibility(bool show);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,145 @@
|
||||||
|
|
||||||
|
#include "ElementConfigOption.h"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include "ElementOptionTypeCheckbox.h"
|
||||||
|
#include "ElementOptionTypeRadioTabs.h"
|
||||||
|
#include "ElementOptionTypeRange.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string option_type_wrapper_id = "checkbox__input";
|
||||||
|
|
||||||
|
static const std::string config_option_base_class = "config-option";
|
||||||
|
static const std::string config_option_base_class_hz = config_option_base_class + "--hz";
|
||||||
|
static const std::string config_option_title_class = config_option_base_class + "__title";
|
||||||
|
static const std::string config_option_wrapper = config_option_base_class + "__list"; // TODO: move hz listing to radio tabs, make this container dumber
|
||||||
|
|
||||||
|
ElementConfigOption::ElementConfigOption(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
SetClass(config_option_base_class, true);
|
||||||
|
|
||||||
|
{
|
||||||
|
Rml::Element *label_el = AppendChild(doc->CreateElement("label"));
|
||||||
|
label_el->SetClass(config_option_title_class, true);
|
||||||
|
{
|
||||||
|
Rml::Element *label_text_el = label_el->AppendChild(doc->CreateTextNode("Unknown"));
|
||||||
|
label_text_el->SetAttribute("id", "config-opt-label");
|
||||||
|
} // label_el
|
||||||
|
|
||||||
|
Rml::Element *option_type_wrapper = AppendChild(doc->CreateElement("div"));
|
||||||
|
option_type_wrapper->SetId(option_type_wrapper_id);
|
||||||
|
option_type_wrapper->SetClass(config_option_wrapper, true);
|
||||||
|
} // base element
|
||||||
|
|
||||||
|
AddEventListener(Rml::EventId::Mouseover, this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementConfigOption::~ElementConfigOption()
|
||||||
|
{
|
||||||
|
RemoveEventListener(Rml::EventId::Mouseover, this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigOption::SetTextLabel(const std::string& s) {
|
||||||
|
Rml::ElementText *label = (Rml::ElementText *)GetElementById("config-opt-label");
|
||||||
|
label->SetText(s);
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::Element *ElementConfigOption::GetOptionTypeWrapper() {
|
||||||
|
return GetElementById(option_type_wrapper_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void add_option_el(Rml::ElementDocument *doc, Rml::Element *wrapper, const std::string& tag, std::string& config_key) {
|
||||||
|
T *opt = (T *)wrapper->AppendChild(recompui::create_custom_element(doc, tag));
|
||||||
|
opt->init_option(config_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigOption::AddOptionTypeElement() {
|
||||||
|
ConfigOptionType el_option_type = ConfigOptionType::Label;
|
||||||
|
const json& option_json = get_json_from_key(config_key);
|
||||||
|
from_json(option_json["type"], el_option_type);
|
||||||
|
|
||||||
|
Rml::Element *wrapper = GetOptionTypeWrapper();
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
switch (el_option_type) {
|
||||||
|
default:
|
||||||
|
printf("No option type element exists for type '%d'\n", el_option_type);
|
||||||
|
return;
|
||||||
|
case ConfigOptionType::Checkbox: {
|
||||||
|
add_option_el<ElementOptionTypeCheckbox>(doc, wrapper, "recomp-option-type-checkbox", config_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConfigOptionType::RadioTabs: {
|
||||||
|
add_option_el<ElementOptionTypeRadioTabs>(doc, wrapper, "recomp-option-type-radio-tabs", config_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConfigOptionType::Range: {
|
||||||
|
add_option_el<ElementOptionTypeRange>(doc, wrapper, "recomp-option-type-range", config_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigOption::OnAttributeChange(const Rml::ElementAttributes& changed_attributes)
|
||||||
|
{
|
||||||
|
// Call through to the base element's OnAttributeChange().
|
||||||
|
Rml::Element::OnAttributeChange(changed_attributes);
|
||||||
|
|
||||||
|
bool dirty_layout = false;
|
||||||
|
|
||||||
|
auto config_store_key_attr = changed_attributes.find("recomp-data");
|
||||||
|
if (config_store_key_attr != changed_attributes.end() && config_store_key_attr->second.GetType() == Rml::Variant::STRING) {
|
||||||
|
config_key = config_store_key_attr->second.Get<Rml::String>();
|
||||||
|
if (in_checkbox_group) {
|
||||||
|
SetClass(config_option_base_class_hz, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto value = recomp::get_config_store_value<std::string>("translations/" + config_key);
|
||||||
|
SetTextLabel(value);
|
||||||
|
printf("found type and translation\n");
|
||||||
|
AddOptionTypeElement();
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
SetTextLabel(e.what());
|
||||||
|
}
|
||||||
|
dirty_layout = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty_layout) {
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementConfigOption::ProcessEvent(Rml::Event& event)
|
||||||
|
{
|
||||||
|
// Set description key
|
||||||
|
if (event == Rml::EventId::Mouseover)
|
||||||
|
{
|
||||||
|
if (event.GetPhase() == Rml::EventPhase::Capture || event.GetPhase() == Rml::EventPhase::Target)
|
||||||
|
{
|
||||||
|
Rml::ElementList elements;
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
GetElementsByTagName(elements, "input");
|
||||||
|
doc->GetElementsByTagName(elements, "recomp-description");
|
||||||
|
for (size_t i = 0; i < elements.size(); i++) {
|
||||||
|
Rml::Element *el = elements[i];
|
||||||
|
el->SetAttribute("recomp-data", config_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENTS_CONFIG_OPTION_H
|
||||||
|
#define RECOMPUI_ELEMENTS_CONFIG_OPTION_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
#include "RmlUi/Core/EventListener.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Base custom element representing a single config option.
|
||||||
|
Maps to other custom elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ElementConfigOption : public Rml::Element, public Rml::EventListener {
|
||||||
|
public:
|
||||||
|
ElementConfigOption(const Rml::String& tag);
|
||||||
|
virtual ~ElementConfigOption();
|
||||||
|
|
||||||
|
ConfigOptionType option_type;
|
||||||
|
std::string config_key;
|
||||||
|
bool in_checkbox_group = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnAttributeChange(const Rml::ElementAttributes& changed_attributes);
|
||||||
|
void SetTextLabel(const std::string& s);
|
||||||
|
void AddOptionTypeElement();
|
||||||
|
Rml::Element *GetOptionTypeWrapper();
|
||||||
|
void ProcessEvent(Rml::Event& event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
#include "ElementDescription.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include <string>
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string cls_base = "config-description";
|
||||||
|
static const std::string cls_contents = cls_base + "__contents";
|
||||||
|
|
||||||
|
static const std::string contents_id = cls_base + "__contents-text";
|
||||||
|
|
||||||
|
static const std::string default_contents = "";
|
||||||
|
|
||||||
|
ElementDescription::ElementDescription(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
SetClass(cls_base, true);
|
||||||
|
{
|
||||||
|
Rml::Element *p_el = AppendChild(doc->CreateElement("p"));
|
||||||
|
p_el->SetClass(cls_contents, true);
|
||||||
|
p_el->SetId(contents_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementDescription::~ElementDescription()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementDescription::update_text(const std::string& text) {
|
||||||
|
auto *p_el = GetElementById(contents_id);
|
||||||
|
p_el->SetInnerRML(text);
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementDescription::OnAttributeChange(const Rml::ElementAttributes& changed_attributes)
|
||||||
|
{
|
||||||
|
// Call through to the base element's OnAttributeChange().
|
||||||
|
Rml::Element::OnAttributeChange(changed_attributes);
|
||||||
|
|
||||||
|
auto config_store_key_attr = changed_attributes.find("recomp-data");
|
||||||
|
if (config_store_key_attr != changed_attributes.end() && config_store_key_attr->second.GetType() == Rml::Variant::STRING) {
|
||||||
|
config_key = config_store_key_attr->second.Get<Rml::String>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto value = recomp::get_config_store_value<std::string>("translations/" + config_key + ":description");
|
||||||
|
update_text(value);
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
update_text(default_contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENT_DESCRIPTION_H
|
||||||
|
#define RECOMPUI_ELEMENT_DESCRIPTION_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ElementDescription : public Rml::Element {
|
||||||
|
public:
|
||||||
|
ElementDescription(const Rml::String& tag);
|
||||||
|
virtual ~ElementDescription();
|
||||||
|
|
||||||
|
std::string config_key;
|
||||||
|
protected:
|
||||||
|
void update_text(const std::string& text_rml);
|
||||||
|
private:
|
||||||
|
void OnAttributeChange(const Rml::ElementAttributes& changed_attributes);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
||||||
|
#endif
|
|
@ -0,0 +1,78 @@
|
||||||
|
|
||||||
|
#include "ElementOptionTypeCheckbox.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include <string>
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string checkbox_input_id = "checkbox__input";
|
||||||
|
|
||||||
|
ElementOptionTypeCheckbox::ElementOptionTypeCheckbox(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
SetClass("config-option__checkbox-wrapper", true);
|
||||||
|
{
|
||||||
|
Rml::Element *checkbox_el = AppendChild(doc->CreateElement("input"));
|
||||||
|
checkbox_el->SetId(checkbox_input_id);
|
||||||
|
checkbox_el->SetClass("config-option__checkbox", true);
|
||||||
|
checkbox_el->SetAttribute("type", "checkbox");
|
||||||
|
}
|
||||||
|
AddEventListener(Rml::EventId::Click, this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementOptionTypeCheckbox::~ElementOptionTypeCheckbox()
|
||||||
|
{
|
||||||
|
RemoveEventListener(Rml::EventId::Click, this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementFormControlInput *ElementOptionTypeCheckbox::get_input() {
|
||||||
|
return (Rml::ElementFormControlInput *)GetElementById(checkbox_input_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeCheckbox::set_checked(bool checked) {
|
||||||
|
auto *input_el = get_input();
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
SetAttribute("checked", true);
|
||||||
|
input_el->SetAttribute("checked", true);
|
||||||
|
} else {
|
||||||
|
RemoveAttribute("checked");
|
||||||
|
input_el->RemoveAttribute("checked");
|
||||||
|
}
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeCheckbox::init_option(std::string& _config_key) {
|
||||||
|
config_key = _config_key;
|
||||||
|
const json& option_json = get_json_from_key(config_key);
|
||||||
|
int value = recomp::get_config_store_value<int>(config_key);
|
||||||
|
set_checked(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeCheckbox::ProcessEvent(Rml::Event& event)
|
||||||
|
{
|
||||||
|
// Forward clicks to the target.
|
||||||
|
if (event == Rml::EventId::Click && !disable_click)
|
||||||
|
{
|
||||||
|
if (event.GetPhase() == Rml::EventPhase::Capture || event.GetPhase() == Rml::EventPhase::Target)
|
||||||
|
{
|
||||||
|
bool new_value = !recomp::get_config_store_value<int>(config_key);
|
||||||
|
recomp::set_config_store_value(config_key, new_value);
|
||||||
|
set_checked(new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::Element* ElementOptionTypeCheckbox::GetTarget()
|
||||||
|
{
|
||||||
|
return get_input();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENT_OPTION_TYPE_CHECKBOX_H
|
||||||
|
#define RECOMPUI_ELEMENT_OPTION_TYPE_CHECKBOX_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "RmlUi/Core/Elements/ElementFormControlInput.h"
|
||||||
|
#include "RmlUi/Core/EventListener.h"
|
||||||
|
#include "../config_options/ConfigOption.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ElementOptionTypeCheckbox : public Rml::Element, public Rml::EventListener {
|
||||||
|
public:
|
||||||
|
ElementOptionTypeCheckbox(const Rml::String& tag);
|
||||||
|
virtual ~ElementOptionTypeCheckbox();
|
||||||
|
|
||||||
|
void init_option(std::string& _config_key);
|
||||||
|
|
||||||
|
std::string config_key;
|
||||||
|
|
||||||
|
const ConfigOptionType option_type = ConfigOptionType::Checkbox;
|
||||||
|
protected:
|
||||||
|
Rml::ElementFormControlInput *get_input();
|
||||||
|
|
||||||
|
void ProcessEvent(Rml::Event& event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Element* GetTarget();
|
||||||
|
void set_checked(bool checked);
|
||||||
|
|
||||||
|
bool disable_click = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
||||||
|
#endif
|
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
#include "ElementOptionTypeRadioTabs.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include <string>
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string radio_input_id = "recomp-radio__";
|
||||||
|
|
||||||
|
ElementOptionTypeRadioTabs::ElementOptionTypeRadioTabs(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
SetClass("config-option__radio-tabs", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementOptionTypeRadioTabs::~ElementOptionTypeRadioTabs()
|
||||||
|
{
|
||||||
|
Rml::ElementList elements;
|
||||||
|
GetElementsByTagName(elements, "input");
|
||||||
|
for (int i = 0; i < elements.size(); i++) {
|
||||||
|
Rml::Element *el = elements[i];
|
||||||
|
el->RemoveEventListener(Rml::EventId::Click, this, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRadioTabs::set_cur_option(int opt) {
|
||||||
|
Rml::ElementList elements;
|
||||||
|
GetElementsByTagName(elements, "input");
|
||||||
|
for (int i = 0; i < elements.size(); i++) {
|
||||||
|
Rml::Element *el = elements[i];
|
||||||
|
if (i == opt) {
|
||||||
|
SetAttribute("checked", true);
|
||||||
|
el->SetAttribute("checked", true);
|
||||||
|
} else {
|
||||||
|
RemoveAttribute("checked");
|
||||||
|
el->RemoveAttribute("checked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRadioTabs::init_option(std::string& _config_key) {
|
||||||
|
config_key = _config_key;
|
||||||
|
const json& option_json = get_json_from_key(config_key);
|
||||||
|
int opt = recomp::get_config_store_value<int>(config_key);
|
||||||
|
const json& opt_array = option_json["values"];
|
||||||
|
|
||||||
|
for (int i = 0; i < opt_array.size(); i++) {
|
||||||
|
const auto &j_opt = opt_array[i];
|
||||||
|
const std::string opt_val = j_opt.get<std::string>();
|
||||||
|
const std::string opt_id = radio_input_id + config_key + "--" + opt_val;
|
||||||
|
|
||||||
|
const std::string translation_key = "translations/" + config_key + "/values/" + opt_val;
|
||||||
|
const std::string& opt_text = recomp::get_config_store_value<std::string>(translation_key);
|
||||||
|
|
||||||
|
Rml::Element *radio_el = AppendChild(GetOwnerDocument()->CreateElement("input"));
|
||||||
|
|
||||||
|
radio_el->SetId(opt_id);
|
||||||
|
radio_el->SetAttribute("type", "radio");
|
||||||
|
radio_el->SetAttribute("value", i);
|
||||||
|
radio_el->AddEventListener(Rml::EventId::Click, this, false);
|
||||||
|
// TODO: focus event set description
|
||||||
|
// TODO: blur event clear description
|
||||||
|
Rml::Element *label_el = AppendChild(GetOwnerDocument()->CreateElement("label"));
|
||||||
|
label_el->SetAttribute("for", opt_id);
|
||||||
|
label_el->SetClass("config-option__tab-label", true);
|
||||||
|
{
|
||||||
|
label_el->AppendChild(GetOwnerDocument()->CreateTextNode(opt_text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cur_option(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRadioTabs::ProcessEvent(Rml::Event& event)
|
||||||
|
{
|
||||||
|
// Forward clicks to the target.
|
||||||
|
if (event == Rml::EventId::Click)
|
||||||
|
{
|
||||||
|
if (event.GetPhase() == Rml::EventPhase::Bubble || event.GetPhase() == Rml::EventPhase::Target)
|
||||||
|
{
|
||||||
|
Rml::Element *target = event.GetTargetElement();
|
||||||
|
auto val_variant = target->GetAttribute("value");
|
||||||
|
int new_value = val_variant->Get<int>();
|
||||||
|
recomp::set_config_store_value(config_key, new_value);
|
||||||
|
set_cur_option(new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENT_OPTION_TYPE_RADIO_TABS_H
|
||||||
|
#define RECOMPUI_ELEMENT_OPTION_TYPE_RADIO_TABS_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "RmlUi/Core/Elements/ElementFormControlInput.h"
|
||||||
|
#include "RmlUi/Core/EventListener.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ElementOptionTypeRadioTabs : public Rml::Element, public Rml::EventListener {
|
||||||
|
public:
|
||||||
|
ElementOptionTypeRadioTabs(const Rml::String& tag);
|
||||||
|
virtual ~ElementOptionTypeRadioTabs();
|
||||||
|
|
||||||
|
std::string config_key;
|
||||||
|
void init_option(std::string& _config_key);
|
||||||
|
protected:
|
||||||
|
void ProcessEvent(Rml::Event& event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_cur_option(int opt);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
||||||
|
#endif
|
|
@ -0,0 +1,94 @@
|
||||||
|
|
||||||
|
#include "ElementOptionTypeRange.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
#include "../config_options/ConfigRegistry.h"
|
||||||
|
#include <string>
|
||||||
|
#include <RmlUi/Core/ElementDocument.h>
|
||||||
|
#include <RmlUi/Core/ElementText.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
static const std::string range_label_id = "recomp-range__label";
|
||||||
|
static const std::string range_label_text_id = "recomp-range__label-text";
|
||||||
|
static const std::string range_input_id = "recomp-range__input";
|
||||||
|
|
||||||
|
static const std::string cls_base = "config-option-range";
|
||||||
|
static const std::string cls_label = cls_base + "__label";
|
||||||
|
static const std::string cls_range_input = cls_base + "__range-input";
|
||||||
|
|
||||||
|
ElementOptionTypeRange::ElementOptionTypeRange(const Rml::String& tag) : Rml::Element(tag)
|
||||||
|
{
|
||||||
|
SetAttribute("recomp-store-element", true);
|
||||||
|
SetClass(cls_base, true);
|
||||||
|
|
||||||
|
Rml::ElementDocument *doc = GetOwnerDocument();
|
||||||
|
|
||||||
|
{
|
||||||
|
Rml::Element *label_el = AppendChild(doc->CreateElement("label"));
|
||||||
|
label_el->SetClass(cls_label, true);
|
||||||
|
label_el->SetId(range_label_id);
|
||||||
|
{
|
||||||
|
Rml::Element *text_node = label_el->AppendChild(doc->CreateTextNode(""));
|
||||||
|
text_node->SetId(range_label_text_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::Element *range_el = AppendChild(doc->CreateElement("input"));
|
||||||
|
range_el->SetClass(cls_range_input, true);
|
||||||
|
range_el->SetId(range_input_id);
|
||||||
|
range_el->SetAttribute("type", "range");
|
||||||
|
range_el->AddEventListener(Rml::EventId::Change, this, false);
|
||||||
|
// TODO: focus event set description
|
||||||
|
// TODO: blur event clear description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementOptionTypeRange::~ElementOptionTypeRange()
|
||||||
|
{
|
||||||
|
Rml::Element *range = GetElementById(range_input_id);
|
||||||
|
range->RemoveEventListener(Rml::EventId::Change, this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRange::set_value_label(int value) {
|
||||||
|
Rml::ElementText *text_label = (Rml::ElementText *)GetElementById(range_label_text_id);
|
||||||
|
text_label->SetText(std::to_string(value) + suffix);
|
||||||
|
DirtyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRange::init_option(std::string& _config_key) {
|
||||||
|
config_key = _config_key;
|
||||||
|
const json& option_json = get_json_from_key(config_key);
|
||||||
|
|
||||||
|
const int value = recomp::get_config_store_value<int>(config_key);
|
||||||
|
suffix = get_string_in_json_with_default(option_json, "suffix", "");
|
||||||
|
const int min = get_value_in_json<int>(option_json, "min");
|
||||||
|
const int max = get_value_in_json<int>(option_json, "max");
|
||||||
|
const int step = get_value_in_json_with_default<int>(option_json, "step", 1);
|
||||||
|
|
||||||
|
Rml::ElementFormControlInput *range = (Rml::ElementFormControlInput *)GetElementById(range_input_id);
|
||||||
|
range->SetAttribute("min", min);
|
||||||
|
range->SetAttribute("max", max);
|
||||||
|
range->SetAttribute("step", step);
|
||||||
|
range->SetValue(std::to_string(value));
|
||||||
|
|
||||||
|
set_value_label(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementOptionTypeRange::ProcessEvent(Rml::Event& event)
|
||||||
|
{
|
||||||
|
// Forward clicks to the target.
|
||||||
|
if (event == Rml::EventId::Change)
|
||||||
|
{
|
||||||
|
if (event.GetPhase() == Rml::EventPhase::Bubble || event.GetPhase() == Rml::EventPhase::Target)
|
||||||
|
{
|
||||||
|
Rml::ElementFormControlInput *target = (Rml::ElementFormControlInput *)event.GetTargetElement();
|
||||||
|
auto val_s = target->GetValue();
|
||||||
|
int new_value = std::stoi(val_s);
|
||||||
|
recomp::set_config_store_value(config_key, new_value);
|
||||||
|
set_value_label(new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rml
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENT_OPTION_TYPE_RANGE_H
|
||||||
|
#define RECOMPUI_ELEMENT_OPTION_TYPE_RANGE_H
|
||||||
|
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
#include "RmlUi/Core/Elements/ElementFormControlInput.h"
|
||||||
|
#include "RmlUi/Core/EventListener.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
|
||||||
|
class ElementOptionTypeRange : public Rml::Element, public Rml::EventListener {
|
||||||
|
public:
|
||||||
|
ElementOptionTypeRange(const Rml::String& tag);
|
||||||
|
virtual ~ElementOptionTypeRange();
|
||||||
|
|
||||||
|
std::string config_key;
|
||||||
|
std::string suffix;
|
||||||
|
|
||||||
|
void init_option(std::string& _config_key);
|
||||||
|
protected:
|
||||||
|
void ProcessEvent(Rml::Event& event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_cur_option(int opt);
|
||||||
|
void set_value_label(int value);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace recompui
|
||||||
|
#endif
|
|
@ -0,0 +1,50 @@
|
||||||
|
#include "ui_elements.h"
|
||||||
|
#include "librecomp/config_store.hpp"
|
||||||
|
|
||||||
|
struct RecompElementConfig {
|
||||||
|
Rml::String tag;
|
||||||
|
std::unique_ptr<Rml::ElementInstancer> instancer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CUSTOM_ELEMENT(s, e) { s, std::make_unique< Rml::ElementInstancerGeneric< e > >() }
|
||||||
|
|
||||||
|
static RecompElementConfig custom_elements[] = {
|
||||||
|
CUSTOM_ELEMENT("recomp-description", recompui::ElementDescription),
|
||||||
|
CUSTOM_ELEMENT("recomp-config-group", recompui::ElementConfigGroup),
|
||||||
|
CUSTOM_ELEMENT("recomp-config-option", recompui::ElementConfigOption),
|
||||||
|
CUSTOM_ELEMENT("recomp-option-type-checkbox", recompui::ElementOptionTypeCheckbox),
|
||||||
|
CUSTOM_ELEMENT("recomp-option-type-radio-tabs", recompui::ElementOptionTypeRadioTabs),
|
||||||
|
CUSTOM_ELEMENT("recomp-option-type-range", recompui::ElementOptionTypeRange),
|
||||||
|
};
|
||||||
|
|
||||||
|
void recompui::register_custom_elements() {
|
||||||
|
recomp::set_config_store_value_and_default("ligma_balls", "hello!", "whats up");
|
||||||
|
recomp::set_config_store_default_value("ligma_balls2", "12345");
|
||||||
|
recomp::set_config_store_value("ligma_balls3", "hello!");
|
||||||
|
for (auto& element_config : custom_elements) {
|
||||||
|
Rml::Factory::RegisterElementInstancer(element_config.tag, element_config.instancer.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementInstancer* recompui::get_custom_element_instancer(std::string tag) {
|
||||||
|
for (auto& element_config : custom_elements) {
|
||||||
|
if (tag == element_config.tag) {
|
||||||
|
return element_config.instancer.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::ElementPtr recompui::create_custom_element(Rml::Element* parent, std::string tag) {
|
||||||
|
auto instancer = recompui::get_custom_element_instancer(tag);
|
||||||
|
const Rml::XMLAttributes attributes = {};
|
||||||
|
if (Rml::ElementPtr element = instancer->InstanceElement(parent, tag, attributes))
|
||||||
|
{
|
||||||
|
element->SetInstancer(instancer);
|
||||||
|
element->SetAttributes(attributes);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef RECOMPUI_ELEMENTS_H
|
||||||
|
#define RECOMPUI_ELEMENTS_H
|
||||||
|
|
||||||
|
#include "recomp_ui.h"
|
||||||
|
#include "RmlUi/Core/Element.h"
|
||||||
|
|
||||||
|
#include "elements/ElementConfigOption.h"
|
||||||
|
#include "elements/ElementConfigGroup.h"
|
||||||
|
#include "elements/ElementOptionTypeCheckbox.h"
|
||||||
|
#include "elements/ElementOptionTypeRadioTabs.h"
|
||||||
|
#include "elements/ElementOptionTypeRange.h"
|
||||||
|
#include "elements/ElementDescription.h"
|
||||||
|
|
||||||
|
namespace recompui {
|
||||||
|
void register_custom_elements();
|
||||||
|
|
||||||
|
Rml::ElementInstancer* get_custom_element_instancer(std::string tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -28,6 +28,9 @@
|
||||||
#include "RmlUi/../../Source/Core/Elements/ElementLabel.h"
|
#include "RmlUi/../../Source/Core/Elements/ElementLabel.h"
|
||||||
#include "RmlUi_Platform_SDL.h"
|
#include "RmlUi_Platform_SDL.h"
|
||||||
|
|
||||||
|
#include "ui_elements.h"
|
||||||
|
#include "config_options/ConfigRegistry.h"
|
||||||
|
|
||||||
#include "InterfaceVS.hlsl.spirv.h"
|
#include "InterfaceVS.hlsl.spirv.h"
|
||||||
#include "InterfacePS.hlsl.spirv.h"
|
#include "InterfacePS.hlsl.spirv.h"
|
||||||
|
|
||||||
|
@ -1108,6 +1111,13 @@ void recompui::get_window_size(int& width, int& height) {
|
||||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const std::string read_file_to_string(std::filesystem::path path) {
|
||||||
|
std::ifstream stream = std::ifstream{path};
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << stream.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
std::locale::global(std::locale::classic());
|
std::locale::global(std::locale::classic());
|
||||||
|
@ -1130,6 +1140,28 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
Rml::SetRenderInterface(ui_context->rml.render_interface.get()->GetAdaptedInterface());
|
Rml::SetRenderInterface(ui_context->rml.render_interface.get()->GetAdaptedInterface());
|
||||||
Rml::Factory::RegisterEventListenerInstancer(&ui_context->rml.event_listener_instancer);
|
Rml::Factory::RegisterEventListenerInstancer(&ui_context->rml.event_listener_instancer);
|
||||||
|
|
||||||
|
recompui::register_custom_elements();
|
||||||
|
|
||||||
|
// TODO: Remove hardcoded config register
|
||||||
|
// std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||||
|
// std::filesystem::path test_conf_path = zelda64::get_app_folder_path() / "config_example.json";
|
||||||
|
std::filesystem::path test_conf_path = "config_example.cheats.json";
|
||||||
|
std::filesystem::path test_conf_trans_path = "config_example.cheats.en_us.json";
|
||||||
|
if (std::filesystem::exists(test_conf_path)) {
|
||||||
|
const std::string s = read_file_to_string(test_conf_path);
|
||||||
|
recompui::register_config(read_file_to_string(test_conf_path), "cheats");
|
||||||
|
printf("SUCC CONF\n");
|
||||||
|
|
||||||
|
if (std::filesystem::exists(test_conf_trans_path)) {
|
||||||
|
recompui::register_translation(read_file_to_string("config_example.cheats.en_us.json"), "cheats");
|
||||||
|
printf("SUCC TRANSLATION\n");
|
||||||
|
} else {
|
||||||
|
printf("FAIL TRANSLATION");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("FAIL ALL\n");
|
||||||
|
}
|
||||||
|
|
||||||
Rml::Initialise();
|
Rml::Initialise();
|
||||||
|
|
||||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
||||||
|
|
Loading…
Reference in New Issue