Dropdown reacts to window size, and color picker always fits in window
This commit is contained in:
parent
270cd98195
commit
903914c7ef
3 changed files with 145 additions and 71 deletions
|
|
@ -24,7 +24,6 @@
|
|||
#include <modules/skparagraph/include/FontCollection.h>
|
||||
#include <modules/skparagraph/include/TextStyle.h>
|
||||
#include <modules/skunicode/include/SkUnicode_icu.h>
|
||||
#include <Helpers/Random.hpp>
|
||||
#include "../FontData.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
|
@ -536,6 +535,11 @@ bool GUIManager::input_scalar(const std::string& id, uint8_t* val, uint8_t min,
|
|||
return isUpdating;
|
||||
}
|
||||
|
||||
std::string GUIManager::get_clay_unique_id() {
|
||||
std::string toRet = "CLAY_UNIQUE_ID_" + std::to_string(clayUniqueIDCounter++);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
void GUIManager::input_path(const std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void(SelectionHelper&)>& elemUpdate) {
|
||||
push_id(id);
|
||||
insert_element<TextBox<std::filesystem::path>>()->update(*io, val,
|
||||
|
|
@ -564,9 +568,17 @@ bool GUIManager::font_picker(const std::string& id, std::string* fontName) {
|
|||
return toRet;
|
||||
}
|
||||
|
||||
void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::vector<std::string>& selections, float width, float maxHeight, const std::function<void()>& hoverboxElemUpdate) {
|
||||
void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::vector<std::string>& selections, float width, const std::function<void()>& hoverboxElemUpdate) {
|
||||
push_id(id);
|
||||
bool& dropDownOpen = insert_any_with_id(0, false);
|
||||
struct DropdownData {
|
||||
std::string uniqueID;
|
||||
bool isOpen = false;
|
||||
};
|
||||
DropdownData& dropdownData = insert_any_with_id_with_function<DropdownData>(0, [&]() {
|
||||
return DropdownData {
|
||||
.uniqueID = get_clay_unique_id()
|
||||
};
|
||||
});
|
||||
left_to_right_layout(CLAY_SIZING_FIXED(width), CLAY_SIZING_FIT(0), [&]() {
|
||||
bool click = selectable_button(id, [&](SelectionHelper& s, bool iS) {
|
||||
CLAY({
|
||||
|
|
@ -585,14 +597,20 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::
|
|||
.sizing = {.width = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE)}
|
||||
}
|
||||
}) {
|
||||
svg_icon("dropico", "data/icons/droparrow.svg", dropDownOpen);
|
||||
svg_icon("dropico", "data/icons/droparrow.svg", dropdownData.isOpen);
|
||||
}
|
||||
}
|
||||
}, SelectableButton::DrawType::FILLED, dropDownOpen);
|
||||
if(dropDownOpen) {
|
||||
}, SelectableButton::DrawType::FILLED, dropdownData.isOpen);
|
||||
if(dropdownData.isOpen) {
|
||||
Clay_ElementId clayID = Clay_GetElementId(strArena.std_str_to_clay_str(dropdownData.uniqueID));
|
||||
Clay_ElementData dropdownElemData = Clay_GetElementData(clayID);
|
||||
float calculatedDropdownMaxHeight = 0.0f;
|
||||
if(dropdownElemData.found)
|
||||
calculatedDropdownMaxHeight = std::max(windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f);
|
||||
CLAY({
|
||||
.id = clayID,
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0, maxHeight)},
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)},
|
||||
.childGap = 0
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
|
|
@ -644,7 +662,7 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::
|
|||
text_label(selections[i]);
|
||||
if(io->mouse.leftClick && Clay_Hovered()) {
|
||||
*val = i;
|
||||
dropDownOpen = false;
|
||||
dropdownData.isOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -654,12 +672,13 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::
|
|||
}
|
||||
}
|
||||
if(click)
|
||||
dropDownOpen = !dropDownOpen;
|
||||
dropdownData.isOpen = !dropdownData.isOpen;
|
||||
});
|
||||
pop_id();
|
||||
}
|
||||
|
||||
void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, const std::function<void(float, float, float&)>& elemUpdate) {
|
||||
push_id(id);
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
||||
|
|
@ -679,11 +698,11 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con
|
|||
std::string uniqueID;
|
||||
};
|
||||
|
||||
push_id(id);
|
||||
ScrollAreaData& sD = insert_any(ScrollAreaData{
|
||||
.uniqueID = Random::get().alphanumeric_str(30)
|
||||
ScrollAreaData& sD = insert_any_with_id_with_function<ScrollAreaData>(0, [&](){
|
||||
return ScrollAreaData{
|
||||
.uniqueID = get_clay_unique_id()
|
||||
};
|
||||
});
|
||||
pop_id();
|
||||
|
||||
Clay_ElementId clayID = Clay_GetElementId(strArena.std_str_to_clay_str(sD.uniqueID));
|
||||
|
||||
|
|
@ -696,7 +715,9 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con
|
|||
},
|
||||
.clip = {.horizontal = clipHorizontal, .vertical = true, .childOffset = {.x = 0, .y = Clay_GetScrollOffset().y}}
|
||||
}) {
|
||||
push_id(1);
|
||||
elemUpdate(sD.contentDimensions, sD.containerDimensions, sD.currentScrollPos);
|
||||
pop_id();
|
||||
}
|
||||
|
||||
// Make sure you are accessing this data after the element has been created
|
||||
|
|
@ -770,6 +791,7 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con
|
|||
else
|
||||
sD.currentScrollPos = 0.0f;
|
||||
}
|
||||
pop_id();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class GUIManager {
|
|||
void input_path(const std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
|
||||
void input_path_field(const std::string& id, const std::string& name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
|
||||
|
||||
void dropdown_select(const std::string& id, size_t* val, const std::vector<std::string>& selections, float width = 200.0f, float maxHeight = 0.0f, const std::function<void()>& hoverboxElemUpdate = nullptr);
|
||||
void dropdown_select(const std::string& id, size_t* val, const std::vector<std::string>& selections, float width = 200.0f, const std::function<void()>& hoverboxElemUpdate = nullptr);
|
||||
|
||||
void paint_circle_popup_menu(const std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function<void()>& elemUpdate = nullptr);
|
||||
|
||||
|
|
@ -126,6 +126,22 @@ class GUIManager {
|
|||
pop_id();
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> T& insert_any_with_function(std::function<T()> funcToRun) {
|
||||
auto [it, inserted] = elements.emplace(idStack, ElementContainer());
|
||||
auto& container = it->second;
|
||||
if(inserted)
|
||||
it->second.extra = funcToRun();
|
||||
container.isUsedThisFrame = true;
|
||||
return std::any_cast<T&>(it->second.extra);
|
||||
}
|
||||
|
||||
template <typename T> T& insert_any_with_id_with_function(int64_t id, std::function<T()> funcToRun) {
|
||||
push_id(id);
|
||||
T& toRet = insert_any_with_function(funcToRun);
|
||||
pop_id();
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> bool input_generic(const std::string& id, T* val, const std::function<std::optional<T>(const std::string&)>& fromStr, const std::function<std::string(const T&)>& toStr, bool singleLine, bool updateEveryEdit, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr) {
|
||||
bool isUpdating = false;
|
||||
|
|
@ -307,10 +323,20 @@ class GUIManager {
|
|||
|
||||
bool font_picker(const std::string& id, std::string* fontName);
|
||||
|
||||
template <typename T> bool color_picker_items(const std::string& id, T* val, bool selectAlpha) {
|
||||
template <typename T> bool color_picker_items(const std::string& id, T* val, bool selectAlpha, float fixedPickerWidth = 0.0f) {
|
||||
bool isUpdating = false;
|
||||
push_id(id);
|
||||
isUpdating |= color_picker("c", val, selectAlpha);
|
||||
if(fixedPickerWidth == 0.0f)
|
||||
isUpdating |= color_picker("c", val, selectAlpha);
|
||||
else {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(fixedPickerWidth), .height = CLAY_SIZING_FIXED(fixedPickerWidth)}
|
||||
}
|
||||
}) {
|
||||
isUpdating |= color_picker("c", val, selectAlpha);
|
||||
}
|
||||
}
|
||||
left_to_right_line_layout([&]() {
|
||||
text_label("R");
|
||||
isUpdating |= input_color_component_255("r", &(*val)[0]);
|
||||
|
|
@ -341,6 +367,9 @@ class GUIManager {
|
|||
}
|
||||
|
||||
private:
|
||||
std::string get_clay_unique_id();
|
||||
uint64_t clayUniqueIDCounter = 0;
|
||||
|
||||
static void clay_error_handler(Clay_ErrorData errorData);
|
||||
static Clay_Dimensions clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData);
|
||||
|
||||
|
|
|
|||
133
src/Toolbar.cpp
133
src/Toolbar.cpp
|
|
@ -1062,21 +1062,27 @@ void Toolbar::drawing_program_gui() {
|
|||
if(colorLeft) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)},
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1)
|
||||
.padding = {.top = 40, .bottom = 40}
|
||||
}
|
||||
}) {
|
||||
gui.obstructing_window();
|
||||
isUpdatingColorLeft |= gui.color_picker_items("colorpickerleft", colorLeft, true);
|
||||
bool hoveringOnDropdown = false;
|
||||
isUpdatingColorLeft |= color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown);
|
||||
if(!Clay_Hovered() && !justAssignedColorLeft && !hoveringOnDropdown && io->mouse.leftClick)
|
||||
colorLeft = nullptr;
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)},
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1)
|
||||
}) {
|
||||
gui.obstructing_window();
|
||||
isUpdatingColorLeft |= gui.color_picker_items("colorpickerleft", colorLeft, true, 300.0f - io->theme->padding1 * 2.0f);
|
||||
bool hoveringOnDropdown = false;
|
||||
isUpdatingColorLeft |= color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown);
|
||||
if(!Clay_Hovered() && !justAssignedColorLeft && !hoveringOnDropdown && io->mouse.leftClick)
|
||||
colorLeft = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
CLAY({
|
||||
|
|
@ -1087,21 +1093,27 @@ void Toolbar::drawing_program_gui() {
|
|||
if(colorRight) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)},
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1)
|
||||
.padding = {.top = 40, .bottom = 40}
|
||||
}
|
||||
}) {
|
||||
gui.obstructing_window();
|
||||
isUpdatingColorRight |= gui.color_picker_items("colorpickerright", colorRight, true);
|
||||
bool hoveringOnDropdown = false;
|
||||
isUpdatingColorRight |= color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown);
|
||||
if(!Clay_Hovered() && !justAssignedColorRight && !hoveringOnDropdown && io->mouse.leftClick)
|
||||
colorRight = nullptr;
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)},
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1)
|
||||
}) {
|
||||
gui.obstructing_window();
|
||||
isUpdatingColorRight |= gui.color_picker_items("colorpickerright", colorRight, true, 300.0f - io->theme->padding1 * 2.0f);
|
||||
bool hoveringOnDropdown = false;
|
||||
isUpdatingColorRight |= color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown);
|
||||
if(!Clay_Hovered() && !justAssignedColorRight && !hoveringOnDropdown && io->mouse.leftClick)
|
||||
colorRight = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
main.world->drawProg.tool_options_gui();
|
||||
|
|
@ -1112,42 +1124,53 @@ bool Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri
|
|||
bool isUpdating = false;
|
||||
gui.push_id(id);
|
||||
auto& palette = paletteData.palettes[paletteData.selectedPalette].colors;
|
||||
size_t i = 0;
|
||||
constexpr float COLOR_BUTTON_SIZE = GUIStuff::GUIManager::BIG_BUTTON_SIZE;
|
||||
while(i < palette.size()) {
|
||||
|
||||
gui.scroll_bar_area("color palette scroll area", false, [&](float, float, float &) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)},
|
||||
.padding = {.top = 3, .bottom = 3},
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
}
|
||||
}) {
|
||||
size_t i = 0;
|
||||
while(i < palette.size()) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)},
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)},
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
}
|
||||
}) {
|
||||
Vector4f newC = {palette[i].x(), palette[i].y(), palette[i].z(), 1.0f};
|
||||
if(gui.color_button("c" + std::to_string(i), &newC, (paletteData.selectedColor == (int)i))) {
|
||||
paletteData.selectedColor = (int)i;
|
||||
// We want to keep the old color's alpha
|
||||
color->x() = newC.x();
|
||||
color->y() = newC.y();
|
||||
color->z() = newC.z();
|
||||
isUpdating = true;
|
||||
while(i < palette.size()) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)}
|
||||
}
|
||||
}) {
|
||||
Vector4f newC = {palette[i].x(), palette[i].y(), palette[i].z(), 1.0f};
|
||||
if(gui.color_button("c" + std::to_string(i), &newC, (paletteData.selectedColor == (int)i))) {
|
||||
paletteData.selectedColor = (int)i;
|
||||
// We want to keep the old color's alpha
|
||||
color->x() = newC.x();
|
||||
color->y() = newC.y();
|
||||
color->z() = newC.z();
|
||||
isUpdating = true;
|
||||
}
|
||||
if(paletteData.selectedColor == (int)i && (newC.x() != color->x() || newC.y() != color->y() || newC.z() != color->z()))
|
||||
paletteData.selectedColor = -1;
|
||||
}
|
||||
i++;
|
||||
if(i % 6 == 0)
|
||||
break;
|
||||
}
|
||||
if(paletteData.selectedColor == (int)i && (newC.x() != color->x() || newC.y() != color->y() || newC.z() != color->z()))
|
||||
paletteData.selectedColor = -1;
|
||||
}
|
||||
i++;
|
||||
if(i % 6 == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(paletteData.selectedPalette != 0) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
|
|
@ -1182,7 +1205,7 @@ bool Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri
|
|||
std::vector<std::string> paletteNames;
|
||||
for(auto& p : paletteData.palettes)
|
||||
paletteNames.emplace_back(p.name);
|
||||
gui.dropdown_select("paletteselector", &paletteData.selectedPalette, paletteNames, 200.0f, 90.0f, [&]() {
|
||||
gui.dropdown_select("paletteselector", &paletteData.selectedPalette, paletteNames, 200.0f, [&]() {
|
||||
hoveringOnDropdown = Clay_Hovered();
|
||||
});
|
||||
if(gui.svg_icon_button("paletteadd", "data/icons/plus.svg", paletteData.addingPalette, 25.0f)) paletteData.addingPalette = !paletteData.addingPalette;
|
||||
|
|
@ -1442,14 +1465,14 @@ void Toolbar::options_menu() {
|
|||
switch(generalSettingsOptions) {
|
||||
case GSETTINGS_GENERAL: {
|
||||
gui.push_id("general settings");
|
||||
gui.input_text_field("display name input", "Display Name", &main.displayName);
|
||||
gui.input_text_field("display name input", "Display name", &main.displayName);
|
||||
main.update_display_names();
|
||||
SkColor4f newBackColor{main.defaultCanvasBackgroundColor.x(), main.defaultCanvasBackgroundColor.y(), main.defaultCanvasBackgroundColor.z(), 1.0f};
|
||||
gui.color_picker_button_field("defaultCanvasBackgroundColor", "Default canvas background color", &newBackColor, false);
|
||||
main.defaultCanvasBackgroundColor = convert_vec3<Vector3f>(newBackColor);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
gui.checkbox_field("native file pick", "Use Native File Picker", &useNativeFilePicker);
|
||||
gui.checkbox_field("update notifications enable", "Check For Updates on Startup", &updateCheckerData.checkForUpdates);
|
||||
gui.checkbox_field("native file pick", "Use native file picker", &useNativeFilePicker);
|
||||
gui.checkbox_field("update notifications enable", "Check for updates on startup", &updateCheckerData.checkForUpdates);
|
||||
#endif
|
||||
gui.slider_scalar_field("drag zoom slider", "Drag zoom speed", &dragZoomSpeed, 0.0, 1.0, 3);
|
||||
gui.slider_scalar_field("scroll zoom slider", "Scroll zoom speed", &scrollZoomSpeed, 0.0, 1.0, 3);
|
||||
|
|
@ -1597,8 +1620,8 @@ void Toolbar::options_menu() {
|
|||
}
|
||||
case GSETTINGS_DEBUG: {
|
||||
gui.push_id("debug settings menu");
|
||||
gui.checkbox_field("show performance metrics", "Show Metrics", &showPerformance);
|
||||
gui.input_scalar_fields("jump transition easing", "Jump Easing", &jumpTransitionEasing, 4, -10.0f, 10.0f, 2);
|
||||
gui.checkbox_field("show performance metrics", "Show metrics", &showPerformance);
|
||||
gui.input_scalar_fields("jump transition easing", "Jump easing", &jumpTransitionEasing, 4, -10.0f, 10.0f, 2);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
gui.input_scalar_field("fps cap slider", "FPS cap", &main.fpsLimit, 3.0f, 10000.0f);
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue