Rewrite mod details under new UI system.

This commit is contained in:
Dario 2025-01-08 21:14:55 -03:00 committed by Mr-Wiseguy
parent 226ccdda74
commit 425dfeec24
31 changed files with 3765 additions and 377 deletions

View File

@ -172,8 +172,14 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeRange.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeTextField.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementModMenu.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementModDetailsPanel.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/presets.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_mod_details_panel.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp
${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp

View File

@ -26,7 +26,8 @@
<link type="text/template" href="config_menu/general.rml" />
<link type="text/template" href="config_menu/controls.rml" />
<link type="text/template" href="config_menu/graphics.rml" />
<link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/mods.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" />
@ -65,6 +66,13 @@
<panel class="config" data-model="sound_options_model">
<template src="config-menu__sound" />
</panel>
<tab class="tab" id="tab_mods">
<div>Mods</div>
<div class="tab__indicator"></div>
</tab>
<panel class="config">
<template src="config-menu__mods" />
</panel>
<tab class="tab" data-model="debug_model" data-if="debug_enabled" id="tab_debug">
<div>Debug</div>
<div class="tab__indicator"></div>

View File

@ -0,0 +1,9 @@
<template name="config-menu__mods">
<head>
</head>
<body>
<form class="config__form">
<recomp-mod-menu />
</form>
</body>
</template>

View File

@ -51,6 +51,10 @@
<div class="menu-list-item__bullet">•</div>
<div class="menu-list-item__label">Settings</div>
</button>
<button onclick="open_mods" class="menu-list-item menu-list-item--right">
<div class="menu-list-item__bullet">•</div>
<div class="menu-list-item__label">Mods</div>
</button>
<button onclick="exit_game" class="menu-list-item menu-list-item--right">
<div class="menu-list-item__bullet">•</div>
<div class="menu-list-item__label">Exit</div>
@ -61,7 +65,6 @@
<label>v{{version_number}}</label>
</div>
</div>
<recomp-mod-menu />
</div>
</body>
</rml>

File diff suppressed because one or more lines are too long

View File

@ -1,74 +0,0 @@
.mod-details {
display: block;
position: relative;
flex: 1 1 200%;
height: 100%;
flex-direction: column;
border-bottom-right-radius: $border-radius-modal;
background-color: $color-bg-overlay;
&__header {
display: flex;
position: relative;
flex-direction: row;
width: 100%;
height: auto;
padding: space(16);
// border-width: $border-width-thickness;
// border-radius: $border-radius-modal;
// border-color: $color-border;
background: $color-bg-shadow;
}
&__thumbnail-container {
height: auto;
width: auto;
}
&__thumbnail {
height: 100dp;
width: 100dp;
// max-width: 100dp;
// max-height: 100dp;
// padding: space(16);
// border-bottom-width: $border-width-thickness;
// border-top-left-radius: $border-radius-modal;
// border-top-right-radius: $border-radius-modal;
// border-bottom-color: $color-border;
background-color: $color-bg-overlay;
}
&__header-details {
display: flex;
position: relative;
flex-direction: column;
justify-content:space-evenly;
width: auto;
height: auto;
margin-left: space(16);
overflow: hidden;
}
&__title {
@extend %header-3;
}
&__version {
@extend %label-md;
}
&__body {
display: flex;
position: relative;
flex-direction: column;
width: 100%;
height: auto;
padding-left: space(16);
}
&__authors, &__description {
@extend %label-md;
margin-top: space(16);
}
}

View File

@ -1,58 +1,11 @@
.mod-menu {
display: flex;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
flex-direction: row;
position: relative;
flex: 1 1 100%;
flex-direction: column;
align-items: center;
justify-content: center;
padding: space($page-margin);
background-color: $color-modal-overlay;
&__modal-wrapper {
display: flex;
position: relative;
flex: 1 1 100%;
flex-direction: column;
width: 100%;
max-width: space($base-modal-max-width);
height: 100%;
margin: auto;
border-width: $border-width-thickness;
border-radius: $border-radius-modal;
border-color: $color-border;
background: $color-bg-shadow;
}
&__modal-header {
display: flex;
position: relative;
flex: 1 1 auto;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
height: auto;
padding: space(16);
border-bottom-width: $border-width-thickness;
border-top-left-radius: $border-radius-modal;
border-top-right-radius: $border-radius-modal;
border-bottom-color: $color-border;
background-color: $color-bg-overlay;
}
&__modal-body {
display: flex;
position: relative;
flex: 1 1 auto;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
height: auto;
}
height: 100%;
&__list {
display: block;
@ -91,6 +44,7 @@
&:hover, &:focus {
cursor: pointer;
}
&[is_selected] {
border-color: rgb(255,255,255);
}

View File

@ -15,4 +15,3 @@
@import "./BottomLeft";
@import "./Prompt";
@import "./ModMenu";
@import "./ModDetails";

View File

@ -53,6 +53,7 @@ namespace recompui {
Controls,
Graphics,
Audio,
Mods,
Debug,
Count
};

View File

@ -1,72 +0,0 @@
#include "ElementModDetailsPanel.h"
#include "presets.h"
#include "librecomp/mods.hpp"
#include <string>
#define MOD_DETAILS_BEM "mod-details"
namespace recompui {
static const std::string cls_base = BLOCK(MOD_DETAILS_BEM);
static const std::string cls_header = EL(MOD_DETAILS_BEM, "header");
static const std::string cls_thumbnail_container = EL(MOD_DETAILS_BEM, "thumbnail-container");
static const std::string cls_thumbnail = EL(MOD_DETAILS_BEM, "thumbnail");
static const std::string cls_header_details = EL(MOD_DETAILS_BEM, "header-details");
static const std::string cls_title = EL(MOD_DETAILS_BEM, "title");
static const std::string cls_version = EL(MOD_DETAILS_BEM, "version");
static const std::string cls_body = EL(MOD_DETAILS_BEM, "body");
static const std::string cls_authors = EL(MOD_DETAILS_BEM, "authors");
static const std::string cls_description = EL(MOD_DETAILS_BEM, "description");
ElementModDetailsPanel::ElementModDetailsPanel(const Rml::String& tag) : Rml::Element(tag)
{
SetAttribute("recomp-store-element", true);
Rml::ElementDocument *doc = GetOwnerDocument();
SetClass(cls_base, true);
{
Rml::Element *header_el = add_div_with_class(doc, this, cls_header);
{
Rml::Element *thumbnail_container_el = add_div_with_class(doc, header_el, cls_thumbnail_container);
{
Rml::Element *thumbnail_el = add_div_with_class(doc, thumbnail_container_el, cls_thumbnail);
} // thumbnail_container_el
Rml::Element *header_details_el = add_div_with_class(doc, header_el, cls_header_details);
{
title_el = add_div_with_class(doc, header_details_el, cls_title);
version_el = add_div_with_class(doc, header_details_el, cls_version);
} // header_details_el
}
Rml::Element* body_el = add_div_with_class(doc, this, cls_body);
{
description_el = add_div_with_class(doc, body_el, cls_description);
authors_el = add_div_with_class(doc, body_el, cls_authors);
} // body_el
}
}
ElementModDetailsPanel::~ElementModDetailsPanel()
{
}
void ElementModDetailsPanel::SetModDetails(const recomp::mods::ModDetails& details) {
cur_details = details;
title_el->SetInnerRML(cur_details.mod_id);
version_el->SetInnerRML(cur_details.version.to_string());
std::string authors_str = "<i>Authors</i>:";
bool first = true;
for (const std::string& author : details.authors) {
authors_str += (first ? " " : ", ") + author;
first = false;
}
authors_el->SetInnerRML(authors_str);
description_el->SetInnerRML("Placeholder description. Some long text to make sure that wrapping is working correctly. Yet more text and so on.");
DirtyLayout();
}
} // namespace Rml

View File

@ -1,24 +0,0 @@
#ifndef RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
#define RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
#include "common.h"
#include "librecomp/mods.hpp"
namespace recompui {
class ElementModDetailsPanel : public Rml::Element {
public:
ElementModDetailsPanel(const Rml::String& tag);
virtual ~ElementModDetailsPanel();
void SetModDetails(const recomp::mods::ModDetails& details);
private:
recomp::mods::ModDetails cur_details;
Rml::Element* thumbnail_el;
Rml::Element* title_el;
Rml::Element* authors_el;
Rml::Element* version_el;
Rml::Element* description_el;
};
} // namespace recompui
#endif

View File

@ -1,5 +1,4 @@
#include "ElementModMenu.h"
#include "ElementModDetailsPanel.h"
#include "presets.h"
#include "librecomp/mods.hpp"
@ -10,9 +9,6 @@
namespace recompui {
static const std::string cls_base = BLOCK(MOD_MENU_BEM);
static const std::string cls_modal_wrapper = EL(MOD_MENU_BEM, "modal-wrapper");
static const std::string cls_modal_header = EL(MOD_MENU_BEM, "modal-header");
static const std::string cls_modal_body = EL(MOD_MENU_BEM, "modal-body");
static const std::string cls_list = EL(MOD_MENU_BEM, "list");
static const std::string cls_list_scroll = EL(MOD_MENU_BEM, "list-scroll");
static const std::string cls_list_entry = EL(MOD_MENU_BEM, "list-entry");
@ -30,10 +26,6 @@ void ElementModMenu::ProcessEvent(Rml::Event& event) {
// Refresh
if (event_element == refresh_button) {
RefreshMods();
}
// Close
else if (event_element == close_button) {
}
break;
case Rml::EventId::Focus:
@ -41,7 +33,7 @@ void ElementModMenu::ProcessEvent(Rml::Event& event) {
size_t mod_index;
Rml::Variant *val = event_element->GetAttribute("mod_index");
if (val->GetInto(mod_index) && mod_index < mod_details.size()) {
details_el->SetModDetails(mod_details[mod_index]);
mod_details_panel->set_mod_details(mod_details[mod_index]);
}
if (active_list_entry_el != nullptr) {
active_list_entry_el->RemoveAttribute("is_selected");
@ -49,6 +41,8 @@ void ElementModMenu::ProcessEvent(Rml::Event& event) {
event_element->SetAttribute("is_selected", true);
active_list_entry_el = event_element;
}
default:
break;
}
}
@ -111,7 +105,7 @@ void ElementModMenu::RefreshMods() {
recomp::mods::scan_mods();
mod_details = recomp::mods::get_mod_details(game_mod_id);
details_el->SetModDetails(mod_details[0]);
mod_details_panel->set_mod_details(mod_details[0]);
CreateModList();
}
@ -122,34 +116,23 @@ ElementModMenu::ElementModMenu(const Rml::String& tag) : Rml::Element(tag) {
Rml::ElementDocument *doc = GetOwnerDocument();
SetClass(cls_base, true);
Rml::Element *body_el = add_div_with_class(doc, this, "config__hz-wrapper");
{
Rml::Element *modal_wrapper_el = add_div_with_class(doc, this, cls_modal_wrapper);
list_el = add_div_with_class(doc, body_el, cls_list);
{
Rml::Element *header_el = add_div_with_class(doc, modal_wrapper_el, cls_modal_header);
{
refresh_button = add_button(doc, header_el, "Refresh", ButtonVariant::Primary);
refresh_button->AddEventListener(Rml::EventId::Click, this, false);
refresh_button->SetId("refresh-button");
close_button = add_icon_button(doc, header_el, "icons/X.svg", ButtonVariant::Tertiary);
close_button->AddEventListener(Rml::EventId::Click, this, false);
close_button->SetId("close-button");
list_el_scroll = add_div_with_class(doc, list_el, cls_list_scroll);
} // list_el
refresh_button->SetProperty("nav-right", "#" + close_button->GetId());
close_button->SetProperty("nav-left", "#" + refresh_button->GetId());
} // header_el
recompui::Element body_el_compat(body_el);
mod_details_panel = std::make_unique<ModDetailsPanel>(&body_el_compat);
} // body_el
Rml::Element *body_el = add_div_with_class(doc, modal_wrapper_el, cls_modal_body);
{
list_el = add_div_with_class(doc, body_el, cls_list);
{
list_el_scroll = add_div_with_class(doc, list_el, cls_list_scroll);
} // list_el
details_el =
static_cast<ElementModDetailsPanel*>(body_el->AppendChild(doc->CreateElement("recomp-mod-details-panel")));
} // body_el
} // modal_wrapper_el
}
Rml::Element *footer_el = add_div_with_class(doc, this, "config__footer");
{
refresh_button = add_button(doc, footer_el, "Refresh", ButtonVariant::Primary);
refresh_button->AddEventListener(Rml::EventId::Click, this, false);
refresh_button->SetId("refresh-button");
} // footer_el
RefreshMods();
}

View File

@ -3,7 +3,7 @@
#include "common.h"
#include "librecomp/mods.hpp"
#include "ElementModDetailsPanel.h"
#include "ui_mod_details_panel.h"
namespace recompui {
@ -17,10 +17,9 @@ private:
void CreateModList();
Rml::ElementPtr CreateModListEntry(const recomp::mods::ModDetails& details, size_t index);
Rml::Element *refresh_button;
Rml::Element *close_button;
Rml::Element *list_el; // The root mod list element.
Rml::Element *list_el_scroll; // The scroll within the root mod list element.
ElementModDetailsPanel *details_el; // The details panel.
std::unique_ptr<ModDetailsPanel> mod_details_panel;
Rml::Element *active_list_entry_el = nullptr;
std::vector<recomp::mods::ModDetails> mod_details{};
std::string game_mod_id;

View File

@ -0,0 +1,69 @@
#include "ui_button.h"
#include <cassert>
namespace recompui {
Button::Button(const std::string &text, ButtonStyle style, Element *parent) : Element(parent, Events(EventType::Click, EventType::Hover), "button") {
this->style = style;
set_text(text);
set_display(Display::Block);
set_padding(23.0f);
set_border_width(1.1f);
set_border_radius(12.0f);
set_font_size(28.0f);
set_letter_spacing(3.08f);
set_line_height(28.0f);
set_font_style(FontStyle::Normal);
set_font_weight(700);
set_cursor(Cursor::Pointer);
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
update_properties();
}
void Button::update_properties() {
uint8_t border_opacity = hovered ? 255 : 204;
uint8_t background_opacity = hovered ? 76 : 13;
set_color(hovered ? Color{ 242, 242, 242, 255 } : Color{ 204, 204, 204, 255 });
switch (style) {
case ButtonStyle::Primary: {
set_border_color({ 185, 125, 242, border_opacity });
set_background_color({ 185, 125, 242, background_opacity });
break;
}
case ButtonStyle::Secondary: {
set_border_color({ 23, 214, 232, border_opacity });
set_background_color({ 23, 214, 232, background_opacity });
break;
}
default:
assert(false && "Unknown button style.");
break;
}
}
void Button::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
for (const auto &function : pressed_callbacks) {
function();
}
break;
case EventType::Hover:
hovered = e.hover.active;
update_properties();
break;
default:
assert(false && "Unknown event type.");
break;
}
}
void Button::add_pressed_callback(std::function<void()> callback) {
pressed_callbacks.emplace_back(callback);
}
};

View File

@ -0,0 +1,28 @@
#pragma once
#include "ui_element.h"
namespace recompui {
enum class ButtonStyle {
Primary,
Secondary
};
class Button : public Element {
protected:
ButtonStyle style = ButtonStyle::Primary;
std::unique_ptr<Element> floater;
std::list<std::function<void()>> pressed_callbacks;
bool hovered = false;
void update_properties();
// Element overrides.
virtual void process_event(const Event &e) override;
public:
Button(const std::string &text, ButtonStyle style, Element *parent);
void add_pressed_callback(std::function<void()> callback);
};
} // namespace recompui

View File

@ -0,0 +1,14 @@
#include "ui_container.h"
#include <cassert>
namespace recompui {
Container::Container(FlexDirection direction, JustifyContent justify_content, Element *parent) : Element(parent) {
set_display(Display::Flex);
set_flex(1.0f, 1.0f);
set_flex_direction(direction);
set_justify_content(justify_content);
}
};

View File

@ -0,0 +1,12 @@
#pragma once
#include "ui_element.h"
namespace recompui {
class Container : public Element {
public:
Container(FlexDirection direction, JustifyContent justify_content, Element *parent);
};
} // namespace recompui

View File

@ -0,0 +1,415 @@
#include "ui_element.h"
#include <cassert>
namespace recompui {
static Rml::Unit to_rml(Unit unit) {
switch (unit) {
case Unit::Float:
return Rml::Unit::NUMBER;
case Unit::Dp:
return Rml::Unit::DP;
case Unit::Percent:
return Rml::Unit::PERCENT;
default:
return Rml::Unit::UNKNOWN;
}
}
static Rml::Style::Overflow to_rml(Overflow overflow) {
switch (overflow) {
case Overflow::Visible:
return Rml::Style::Overflow::Visible;
case Overflow::Hidden:
return Rml::Style::Overflow::Hidden;
case Overflow::Auto:
return Rml::Style::Overflow::Auto;
case Overflow::Scroll:
return Rml::Style::Overflow::Scroll;
default:
assert(false && "Unknown overflow.");
return Rml::Style::Overflow::Visible;
}
}
Element::Element(Rml::Element *base) {
assert(base != nullptr);
this->base = base;
this->parent = nullptr;
this->owner = false;
}
Element::Element(Element *parent, uint32_t events_enabled, Rml::String base_class) {
assert(parent != nullptr);
this->parent = parent;
this->owner = true;
base = parent->base->AppendChild(parent->base->GetOwnerDocument()->CreateElement(base_class));
register_event_listeners(events_enabled);
}
Element::~Element() {
if (owner) {
base->GetParentNode()->RemoveChild(base);
}
}
void Element::set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation animation) {
assert(base != nullptr);
if (animation.type == AnimationType::None) {
base->SetProperty(property_id, property);
}
else {
const Rml::String property_name = Rml::StyleSheetSpecification::GetPropertyName(property_id);
base->Animate(property_name, property, animation.duration);
}
}
void Element::register_event_listeners(uint32_t events_enabled) {
assert(base != nullptr);
if (events_enabled & Events(EventType::Click)) {
base->AddEventListener(Rml::EventId::Mousedown, this);
}
if (events_enabled & Events(EventType::Focus)) {
base->AddEventListener(Rml::EventId::Focus, this);
base->AddEventListener(Rml::EventId::Blur, this);
}
if (events_enabled & Events(EventType::Hover)) {
base->AddEventListener(Rml::EventId::Mouseover, this);
base->AddEventListener(Rml::EventId::Mouseout, this);
}
}
void Element::ProcessEvent(Rml::Event &event) {
// Events that are processed during any phase.
switch (event.GetId()) {
case Rml::EventId::Mousedown:
process_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
break;
default:
break;
}
// Events that are only processed during the Target phase.
if (event.GetPhase() == Rml::EventPhase::Target) {
switch (event.GetId()) {
case Rml::EventId::Mouseover:
process_event(Event::hover_event(true));
break;
case Rml::EventId::Mouseout:
process_event(Event::hover_event(false));
break;
case Rml::EventId::Focus:
process_event(Event::focus_event(true));
break;
case Rml::EventId::Blur:
process_event(Event::focus_event(false));
break;
default:
break;
}
}
}
void Element::process_event(const Event &) {
// Does nothing by default.
}
void Element::set_position(Position position) {
switch (position) {
case Position::Absolute:
set_property(Rml::PropertyId::Position, Rml::Style::Position::Absolute);
break;
case Position::Relative:
set_property(Rml::PropertyId::Position, Rml::Style::Position::Relative);
break;
default:
assert(false && "Unknown position.");
break;
}
}
void Element::set_left(float left, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Left, Rml::Property(left, to_rml(unit)), animation);
}
void Element::set_top(float top, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Top, Rml::Property(top, to_rml(unit)), animation);
}
void Element::set_right(float right, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Right, Rml::Property(right, to_rml(unit)), animation);
}
void Element::set_bottom(float bottom, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Bottom, Rml::Property(bottom, to_rml(unit)), animation);
}
void Element::set_width(float width, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Width, Rml::Property(width, to_rml(unit)), animation);
}
void Element::set_width_auto() {
set_property(Rml::PropertyId::Width, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
}
void Element::set_height(float height, Unit unit, Animation animation) {
set_property(Rml::PropertyId::Height, Rml::Property(height, to_rml(unit)), animation);
}
void Element::set_height_auto() {
set_property(Rml::PropertyId::Height, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
}
void Element::set_padding(float padding, Unit unit, Animation animation) {
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)), animation);
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)), animation);
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)), animation);
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)), animation);
}
void Element::set_padding_left(float padding, Unit unit, Animation animation) {
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)), animation);
}
void Element::set_padding_top(float padding, Unit unit, Animation animation) {
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)), animation);
}
void Element::set_padding_right(float padding, Unit unit, Animation animation) {
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)), animation);
}
void Element::set_padding_bottom(float padding, Unit unit, Animation animation) {
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)), animation);
}
void Element::set_margin(float margin, Unit unit, Animation animation) {
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)), animation);
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)), animation);
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)), animation);
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)), animation);
}
void Element::set_margin_left(float margin, Unit unit, Animation animation) {
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)), animation);
}
void Element::set_margin_top(float margin, Unit unit, Animation animation) {
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)), animation);
}
void Element::set_margin_right(float margin, Unit unit, Animation animation) {
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)), animation);
}
void Element::set_margin_bottom(float margin, Unit unit, Animation animation) {
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)), animation);
}
void Element::set_border_width(float width, Unit unit, Animation animation) {
Rml::Property property(width, to_rml(unit));
set_property(Rml::PropertyId::BorderTopWidth, property, animation);
set_property(Rml::PropertyId::BorderBottomWidth, property, animation);
set_property(Rml::PropertyId::BorderLeftWidth, property, animation);
set_property(Rml::PropertyId::BorderRightWidth, property, animation);
}
void Element::set_border_radius(float radius, Unit unit, Animation animation) {
Rml::Property property(radius, to_rml(unit));
set_property(Rml::PropertyId::BorderTopLeftRadius, property, animation);
set_property(Rml::PropertyId::BorderTopRightRadius, property, animation);
set_property(Rml::PropertyId::BorderBottomLeftRadius, property, animation);
set_property(Rml::PropertyId::BorderBottomRightRadius, property, animation);
}
void Element::set_border_top_left_radius(float radius, Unit unit, Animation animation) {
set_property(Rml::PropertyId::BorderTopLeftRadius, Rml::Property(radius, to_rml(unit)), animation);
}
void Element::set_border_top_right_radius(float radius, Unit unit, Animation animation) {
set_property(Rml::PropertyId::BorderTopRightRadius, Rml::Property(radius, to_rml(unit)), animation);
}
void Element::set_border_bottom_left_radius(float radius, Unit unit, Animation animation) {
set_property(Rml::PropertyId::BorderBottomLeftRadius, Rml::Property(radius, to_rml(unit)), animation);
}
void Element::set_border_bottom_right_radius(float radius, Unit unit, Animation animation) {
set_property(Rml::PropertyId::BorderBottomRightRadius, Rml::Property(radius, to_rml(unit)), animation);
}
void Element::set_background_color(const Color &color, Animation animation) {
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
set_property(Rml::PropertyId::BackgroundColor, property, animation);
}
void Element::set_border_color(const Color &color, Animation animation) {
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
set_property(Rml::PropertyId::BorderTopColor, property, animation);
set_property(Rml::PropertyId::BorderBottomColor, property, animation);
set_property(Rml::PropertyId::BorderLeftColor, property, animation);
set_property(Rml::PropertyId::BorderRightColor, property, animation);
}
void Element::set_color(const Color &color, Animation animation) {
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
set_property(Rml::PropertyId::Color, property, animation);
}
void Element::set_cursor(Cursor cursor) {
switch (cursor) {
case Cursor::None:
assert(false && "Unimplemented.");
break;
case Cursor::Pointer:
set_property(Rml::PropertyId::Cursor, Rml::Property("pointer", Rml::Unit::STRING));
break;
default:
assert(false && "Unknown cursor.");
break;
}
}
void Element::set_opacity(float opacity, Animation animation) {
set_property(Rml::PropertyId::Opacity, Rml::Property(opacity, Rml::Unit::NUMBER), animation);
}
void Element::set_display(Display display) {
switch (display) {
case Display::Block:
set_property(Rml::PropertyId::Display, Rml::Style::Display::Block);
break;
case Display::Flex:
set_property(Rml::PropertyId::Display, Rml::Style::Display::Flex);
break;
default:
assert(false && "Unknown display.");
break;
}
}
void Element::set_justify_content(JustifyContent justify_content) {
switch (justify_content) {
case JustifyContent::FlexStart:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::FlexStart);
break;
case JustifyContent::FlexEnd:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::FlexEnd);
break;
case JustifyContent::Center:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center);
break;
case JustifyContent::SpaceBetween:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceBetween);
break;
case JustifyContent::SpaceAround:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceAround);
break;
case JustifyContent::SpaceEvenly:
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceEvenly);
break;
default:
assert(false && "Unknown justify content.");
break;
}
}
void Element::set_flex_grow(float grow, Animation animation) {
set_property(Rml::PropertyId::FlexGrow, Rml::Property(grow, Rml::Unit::NUMBER), animation);
}
void Element::set_flex_shrink(float shrink, Animation animation) {
set_property(Rml::PropertyId::FlexShrink, Rml::Property(shrink, Rml::Unit::NUMBER), animation);
}
void Element::set_flex_basis_auto() {
set_property(Rml::PropertyId::FlexBasis, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
}
void Element::set_flex_basis_percentage(float basis, Animation animation) {
set_property(Rml::PropertyId::FlexBasis, Rml::Property(basis, Rml::Unit::PERCENT), animation);
}
void Element::set_flex(float grow, float shrink, Animation animation) {
set_flex_grow(grow, animation);
set_flex_shrink(shrink, animation);
set_flex_basis_auto();
}
void Element::set_flex(float grow, float shrink, float basis_percentage, Animation animation) {
set_flex_grow(grow, animation);
set_flex_shrink(shrink, animation);
set_flex_basis_percentage(basis_percentage, animation);
}
void Element::set_flex_direction(FlexDirection flex_direction) {
switch (flex_direction) {
case FlexDirection::Row:
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row);
break;
case FlexDirection::Column:
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column);
break;
default:
assert(false && "Unknown flex direction.");
break;
}
}
void Element::set_overflow(Overflow overflow) {
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
}
void Element::set_overflow_x(Overflow overflow) {
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
}
void Element::set_overflow_y(Overflow overflow) {
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
}
void Element::set_font_size(float size, Unit unit, Animation animation) {
set_property(Rml::PropertyId::FontSize, Rml::Property(size, to_rml(unit)), animation);
}
void Element::set_letter_spacing(float spacing, Unit unit, Animation animation) {
set_property(Rml::PropertyId::LetterSpacing, Rml::Property(spacing, to_rml(unit)), animation);
}
void Element::set_line_height(float height, Unit unit, Animation animation) {
set_property(Rml::PropertyId::LineHeight, Rml::Property(height, to_rml(unit)), animation);
}
void Element::set_font_style(FontStyle style) {
switch (style) {
case FontStyle::Normal:
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Normal);
break;
case FontStyle::Italic:
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Italic);
break;
default:
assert(false && "Unknown font style.");
break;
}
}
void Element::set_font_weight(uint32_t weight, Animation animation) {
set_property(Rml::PropertyId::FontWeight, Rml::Style::FontWeight(weight), animation);
}
void Element::set_text(const std::string &text) {
base->SetInnerRML(text);
}
};

View File

@ -0,0 +1,211 @@
#pragma once
#include "RmlUi/Core.h"
namespace recompui {
struct Color {
uint8_t r = 255;
uint8_t g = 255;
uint8_t b = 255;
uint8_t a = 255;
};
enum class Cursor {
None,
Pointer
};
enum class EventType {
None,
Click,
Focus,
Hover,
Count
};
template <typename Enum, typename = std::enable_if_t<std::is_enum_v<Enum>>>
constexpr uint32_t Events(Enum first) {
return 1u << static_cast<uint32_t>(first);
}
template <typename Enum, typename... Enums, typename = std::enable_if_t<std::is_enum_v<Enum>>>
constexpr uint32_t Events(Enum first, Enums... rest) {
return Events(first) | Events(rest...);
}
struct Event {
struct Mouse {
float x;
float y;
};
EventType type;
union {
struct {
Mouse mouse;
} click;
struct {
bool active;
} focus;
struct {
bool active;
} hover;
};
static Event click_event(float x, float y) {
Event e = {};
e.type = EventType::Click;
e.click.mouse.x = x;
e.click.mouse.y = y;
return e;
}
static Event focus_event(bool active) {
Event e = {};
e.type = EventType::Focus;
e.focus.active = active;
return e;
}
static Event hover_event(bool active) {
Event e = {};
e.type = EventType::Hover;
e.focus.active = active;
return e;
}
};
enum class Display {
Block,
Flex
};
enum class Position {
Absolute,
Relative
};
enum class JustifyContent {
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly
};
enum class FlexDirection {
Row,
Column
};
enum class Overflow {
Visible,
Hidden,
Auto,
Scroll
};
enum class Unit {
Float,
Dp,
Percent
};
enum class AnimationType {
None,
Tween
};
enum class FontStyle {
Normal,
Italic
};
struct Animation {
AnimationType type = AnimationType::None;
float duration = 0.0f;
static Animation tween(float duration) {
Animation a;
a.type = AnimationType::Tween;
a.duration = duration;
return a;
}
};
class Element : public Rml::EventListener {
private:
void set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation animation = Animation());
void register_event_listeners(uint32_t events_enabled);
// Rml::EventListener overrides.
virtual void ProcessEvent(Rml::Event &event) override;
protected:
Element *parent;
Rml::Element *base;
bool owner;
virtual void process_event(const Event &e);
public:
// Used for backwards compatibility with legacy UI elements.
Element(Rml::Element *base);
// Used to actually construct elements.
Element(Element *parent, uint32_t events_enabled = 0, Rml::String base_class = "div");
virtual ~Element();
void set_position(Position position);
void set_left(float left, Unit unit = Unit::Dp, Animation animation = Animation());
void set_top(float top, Unit unit = Unit::Dp, Animation animation = Animation());
void set_right(float right, Unit unit = Unit::Dp, Animation animation = Animation());
void set_bottom(float bottom, Unit unit = Unit::Dp, Animation animation = Animation());
void set_width(float width, Unit unit = Unit::Dp, Animation animation = Animation());
void set_width_auto();
void set_height(float height, Unit unit = Unit::Dp, Animation animation = Animation());
void set_height_auto();
void set_padding(float padding, Unit unit = Unit::Dp, Animation animation = Animation());
void set_padding_left(float padding, Unit unit = Unit::Dp, Animation animation = Animation());
void set_padding_top(float padding, Unit unit = Unit::Dp, Animation animation = Animation());
void set_padding_right(float padding, Unit unit = Unit::Dp, Animation animation = Animation());
void set_padding_bottom(float padding, Unit unit = Unit::Dp, Animation animation = Animation());
void set_margin(float margin, Unit unit = Unit::Dp, Animation animation = Animation());
void set_margin_left(float margin, Unit unit = Unit::Dp, Animation animation = Animation());
void set_margin_top(float margin, Unit unit = Unit::Dp, Animation animation = Animation());
void set_margin_right(float margin, Unit unit = Unit::Dp, Animation animation = Animation());
void set_margin_bottom(float margin, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_width(float width, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_radius(float radius, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_top_left_radius(float radius, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_top_right_radius(float radius, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_bottom_left_radius(float radius, Unit unit = Unit::Dp, Animation animation = Animation());
void set_border_bottom_right_radius(float radius, Unit unit = Unit::Dp, Animation animation = Animation());
void set_background_color(const Color &color, Animation animation = Animation());
void set_border_color(const Color &color, Animation animation = Animation());
void set_color(const Color &color, Animation animation = Animation());
void set_cursor(Cursor cursor);
void set_opacity(float opacity, Animation animation = Animation());
void set_display(Display display);
void set_justify_content(JustifyContent justify_content);
void set_flex_grow(float grow, Animation animation = Animation());
void set_flex_shrink(float shrink, Animation animation = Animation());
void set_flex_basis_auto();
void set_flex_basis_percentage(float basis_percentage, Animation animation = Animation());
void set_flex(float grow, float shrink, Animation animation = Animation());
void set_flex(float grow, float shrink, float basis_percentage, Animation animation = Animation());
void set_flex_direction(FlexDirection flex_direction);
void set_overflow(Overflow overflow);
void set_overflow_x(Overflow overflow);
void set_overflow_y(Overflow overflow);
void set_font_size(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_letter_spacing(float spacing, Unit unit = Unit::Dp, Animation animation = Animation());
void set_line_height(float height, Unit unit = Unit::Dp, Animation animation = Animation());
void set_font_style(FontStyle style);
void set_font_weight(uint32_t weight, Animation animation = Animation());
void set_text(const std::string &text);
};
} // namespace recompui

View File

@ -0,0 +1,11 @@
#include "ui_image.h"
#include <cassert>
namespace recompui {
Image::Image(Element *parent) : Element(parent) {
}
};

View File

@ -0,0 +1,12 @@
#pragma once
#include "ui_element.h"
namespace recompui {
class Image : public Element {
public:
Image(Element *parent);
};
} // namespace recompui

View File

@ -0,0 +1,29 @@
#include "ui_label.h"
#include <cassert>
namespace recompui {
Label::Label(LabelStyle label_style, Element *parent) : Element(parent) {
switch (label_style) {
case LabelStyle::Normal:
set_font_size(28.0f);
set_letter_spacing(3.08f);
set_line_height(28.0f);
break;
case LabelStyle::Large:
set_font_size(36.0f);
set_letter_spacing(2.52f);
set_line_height(36.0f);
break;
}
set_font_style(FontStyle::Normal);
set_font_weight(700);
}
Label::Label(const std::string &text, LabelStyle label_style, Element *parent) : Label(label_style, parent) {
set_text(text);
}
};

View File

@ -0,0 +1,18 @@
#pragma once
#include "ui_element.h"
namespace recompui {
enum class LabelStyle {
Normal,
Large
};
class Label : public Element {
public:
Label(LabelStyle label_style, Element *parent);
Label(const std::string &text, LabelStyle label_style, Element *parent);
};
} // namespace recompui

View File

@ -0,0 +1,73 @@
#include "ui_mod_details_panel.h"
#include "presets.h"
#include "librecomp/mods.hpp"
namespace recompui {
ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
set_flex(1.0f, 1.0f, 200.0f);
set_height(100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Column);
set_border_bottom_right_radius(16.0f);
set_background_color(Color{ 190, 184, 219, 25 });
header_container = std::make_unique<recompui::Container>(FlexDirection::Row, JustifyContent::FlexStart, this);
header_container->set_padding(16.0f);
header_container->set_background_color(Color{ 0, 0, 0, 89 });
{
thumbnail_container = std::make_unique<recompui::Container>(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container.get());
{
thumbnail_image = std::make_unique<recompui::Image>(thumbnail_container.get());
thumbnail_image->set_width(100.0f);
thumbnail_image->set_height(100.0f);
thumbnail_image->set_background_color(Color{ 190, 184, 219, 25 });
}
header_details_container = std::make_unique<recompui::Container>(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container.get());
header_details_container->set_width(100.0f, Unit::Percent);
header_details_container->set_margin_left(32.0f);
header_details_container->set_overflow(Overflow::Hidden);
{
title_label = std::make_unique<recompui::Label>(LabelStyle::Large, header_details_container.get());
version_label = std::make_unique<recompui::Label>(LabelStyle::Normal, header_details_container.get());
}
}
body_container = std::make_unique<recompui::Container>(FlexDirection::Column, JustifyContent::FlexStart, this);
body_container->set_padding_left(16.0f);
{
description_label = std::make_unique<recompui::Label>(LabelStyle::Normal, body_container.get());
authors_label = std::make_unique<recompui::Label>(LabelStyle::Normal, body_container.get());
buttons_container = std::make_unique<recompui::Container>(FlexDirection::Row, JustifyContent::SpaceAround, body_container.get());
buttons_container->set_padding_left(16.0f);
{
enable_toggle = std::make_unique<recompui::Toggle>(buttons_container.get());
configure_button = std::make_unique<recompui::Button>("Configure", recompui::ButtonStyle::Secondary, buttons_container.get());
erase_button = std::make_unique<recompui::Button>("Erase", recompui::ButtonStyle::Secondary, buttons_container.get());
}
}
}
ModDetailsPanel::~ModDetailsPanel() {
}
void ModDetailsPanel::set_mod_details(const recomp::mods::ModDetails& details) {
cur_details = details;
title_label->set_text(cur_details.mod_id);
version_label->set_text(cur_details.version.to_string());
std::string authors_str = "<i>Authors</i>:";
bool first = true;
for (const std::string& author : details.authors) {
authors_str += (first ? " " : ", ") + author;
first = false;
}
authors_label->set_text(authors_str);
description_label->set_text("Placeholder description. Some long text to make sure that wrapping is working correctly. Yet more text and so on.");
}
} // namespace recompui

View File

@ -0,0 +1,37 @@
#ifndef RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
#define RECOMPUI_ELEMENT_MOD_DETAILS_PANEL_H
#include "common.h"
#include "librecomp/mods.hpp"
#include "ui_button.h"
#include "ui_container.h"
#include "ui_image.h"
#include "ui_label.h"
#include "ui_toggle.h"
namespace recompui {
class ModDetailsPanel : public Element {
public:
ModDetailsPanel(Element *parent);
virtual ~ModDetailsPanel();
void set_mod_details(const recomp::mods::ModDetails& details);
private:
recomp::mods::ModDetails cur_details;
std::unique_ptr<recompui::Container> thumbnail_container;
std::unique_ptr<recompui::Image> thumbnail_image;
std::unique_ptr<recompui::Container> header_container;
std::unique_ptr<recompui::Container> header_details_container;
std::unique_ptr<recompui::Label> title_label;
std::unique_ptr<recompui::Label> version_label;
std::unique_ptr<recompui::Container> body_container;
std::unique_ptr<recompui::Label> description_label;
std::unique_ptr<recompui::Label> authors_label;
std::unique_ptr<recompui::Container> buttons_container;
std::unique_ptr<recompui::Toggle> enable_toggle;
std::unique_ptr<recompui::Button> configure_button;
std::unique_ptr<recompui::Button> erase_button;
};
} // namespace recompui
#endif

View File

@ -0,0 +1,82 @@
#include "ui_toggle.h"
#include <cassert>
namespace recompui {
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Hover), "button") {
set_width(162.0f);
set_height(72.0f);
set_border_radius(36.0f);
set_opacity(0.9f);
set_cursor(Cursor::Pointer);
set_border_width(2.0f);
floater = std::make_unique<Element>(this);
floater->set_position(Position::Relative);
floater->set_top(2.0f);
floater->set_width(80.0f);
floater->set_height(64.0f);
floater->set_border_radius(32.0f);
set_checked_internal(false, false, true);
update_properties();
}
void Toggle::set_checked_internal(bool checked, bool animate, bool setup) {
if (this->checked != checked || setup) {
this->checked = checked;
floater->set_left(floater_left_target(), Unit::Dp, animate ? Animation::tween(0.1f) : Animation());
if (!setup) {
update_properties();
for (const auto &function : checked_callbacks) {
function(checked);
}
}
}
}
void Toggle::update_properties() {
Color border_color = checked ? Color{ 34, 177, 76, 255 } : Color{ 177, 76, 34, 255 };
Color main_color = checked ? Color{ 68, 206, 120, 255 } : Color{ 206, 120, 68, 255 };
main_color.a = hovered ? 76 : 0;
set_border_color(border_color);
set_background_color(main_color);
floater->set_background_color(border_color);
}
float Toggle::floater_left_target() const {
return checked ? 4.0f : 78.0f;
}
void Toggle::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
set_checked_internal(!checked, true, false);
break;
case EventType::Hover:
hovered = e.hover.active;
update_properties();
break;
default:
assert(false && "Unknown event type.");
break;
}
}
void Toggle::set_checked(bool checked) {
set_checked_internal(checked, false, false);
}
bool Toggle::is_checked() const {
return checked;
}
void Toggle::add_checked_callback(std::function<void(bool)> callback) {
checked_callbacks.emplace_back(callback);
}
};

View File

@ -0,0 +1,27 @@
#pragma once
#include "ui_element.h"
namespace recompui {
class Toggle : public Element {
protected:
std::unique_ptr<Element> floater;
std::list<std::function<void(bool)>> checked_callbacks;
bool checked = false;
bool hovered = false;
void set_checked_internal(bool checked, bool animate, bool setup);
void update_properties();
float floater_left_target() const;
// Element overrides.
virtual void process_event(const Event &e) override;
public:
Toggle(Element *parent);
void set_checked(bool checked);
bool is_checked() const;
void add_checked_callback(std::function<void(bool)> callback);
};
} // namespace recompui

View File

@ -20,7 +20,6 @@ static RecompElementConfig custom_elements[] = {
CUSTOM_ELEMENT("recomp-option-type-radio-tabs", recompui::ElementOptionTypeRadioTabs),
CUSTOM_ELEMENT("recomp-option-type-range", recompui::ElementOptionTypeRange),
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
CUSTOM_ELEMENT("recomp-mod-details-panel", recompui::ElementModDetailsPanel),
};
void recompui::register_custom_elements() {

View File

@ -15,7 +15,6 @@
#include "elements/ElementOptionTypeTextField.h"
#include "elements/ElementDescription.h"
#include "elements/ElementModMenu.h"
#include "elements/ElementModDetailsPanel.h"
namespace recompui {
void register_custom_elements();

View File

@ -93,6 +93,12 @@ public:
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
}
);
recompui::register_event(listener, "open_mods",
[](const std::string &param, Rml::Event &event) {
recompui::set_current_menu(recompui::Menu::Config);
recompui::set_config_submenu(recompui::ConfigSubmenu::Mods);
}
);
recompui::register_event(listener, "exit_game",
[](const std::string& param, Rml::Event& event) {
ultramodern::quit();

View File

@ -105,6 +105,14 @@ T from_bytes_le(const char* input) {
void load_document();
class RmlRenderInterface_RT64 : public Rml::RenderInterfaceCompatibility {
struct DynamicBuffer {
std::unique_ptr<RT64::RenderBuffer> buffer_{};
uint32_t size_ = 0;
uint32_t bytes_used_ = 0;
uint8_t* mapped_data_ = nullptr;
RT64::RenderBufferFlags flags_ = RT64::RenderBufferFlag::NONE;
};
static constexpr uint32_t per_frame_descriptor_set = 0;
static constexpr uint32_t per_draw_descriptor_set = 1;
@ -129,9 +137,9 @@ class RmlRenderInterface_RT64 : public Rml::RenderInterfaceCompatibility {
Rml::Matrix4f mvp_ = Rml::Matrix4f::Identity();
std::unordered_map<Rml::TextureHandle, TextureHandle> textures_{};
Rml::TextureHandle texture_count_ = 1; // Start at 1 to reserve texture 0 as the 1x1 pixel white texture
std::unique_ptr<RT64::RenderBuffer> upload_buffer_{};
std::unique_ptr<RT64::RenderBuffer> vertex_buffer_{};
std::unique_ptr<RT64::RenderBuffer> index_buffer_{};
DynamicBuffer upload_buffer_;
DynamicBuffer vertex_buffer_;
DynamicBuffer index_buffer_;
std::unique_ptr<RT64::RenderSampler> nearestSampler_{};
std::unique_ptr<RT64::RenderSampler> linearSampler_{};
std::unique_ptr<RT64::RenderShader> vertex_shader_{};
@ -147,11 +155,6 @@ class RmlRenderInterface_RT64 : public Rml::RenderInterfaceCompatibility {
std::unique_ptr<RT64::RenderDescriptorSet> screen_descriptor_set_{};
std::unique_ptr<RT64::RenderBuffer> screen_vertex_buffer_{};
uint64_t screen_vertex_buffer_size_ = 0;
uint32_t upload_buffer_size_ = 0;
uint32_t upload_buffer_bytes_used_ = 0;
uint8_t* upload_buffer_mapped_data_ = nullptr;
uint32_t vertex_buffer_size_ = 0;
uint32_t index_buffer_size_ = 0;
uint32_t gTexture_descriptor_index;
RT64::RenderInputSlot vertex_slot_{ 0, sizeof(Rml::Vertex) };
RT64::RenderCommandList* list_ = nullptr;
@ -167,10 +170,13 @@ public:
multisampling_.sampleCount = desired_sample_count;
}
vertex_buffer_.flags_ = RT64::RenderBufferFlag::VERTEX;
index_buffer_.flags_ = RT64::RenderBufferFlag::INDEX;
// Create the texture upload buffer, vertex buffer and index buffer
resize_upload_buffer(initial_upload_buffer_size, false);
resize_vertex_buffer(initial_vertex_buffer_size);
resize_index_buffer(initial_index_buffer_size);
resize_dynamic_buffer(upload_buffer_, initial_upload_buffer_size, false);
resize_dynamic_buffer(vertex_buffer_, initial_vertex_buffer_size, false);
resize_dynamic_buffer(index_buffer_, initial_index_buffer_size, false);
// Describe the vertex format
std::vector<RT64::RenderInputElement> vertex_elements{};
@ -260,90 +266,78 @@ public:
}
}
void resize_upload_buffer(uint32_t new_size, bool map = true) {
// Unmap the upload buffer if it's mapped
if (upload_buffer_mapped_data_ != nullptr) {
upload_buffer_->unmap();
void reset_dynamic_buffer(DynamicBuffer &dynamic_buffer) {
assert(dynamic_buffer.mapped_data_ == nullptr);
dynamic_buffer.bytes_used_ = 0;
dynamic_buffer.mapped_data_ = reinterpret_cast<uint8_t*>(dynamic_buffer.buffer_->map());
}
void end_dynamic_buffer(DynamicBuffer &dynamic_buffer) {
assert(dynamic_buffer.mapped_data_ != nullptr);
dynamic_buffer.buffer_->unmap();
dynamic_buffer.mapped_data_ = nullptr;
}
void resize_dynamic_buffer(DynamicBuffer &dynamic_buffer, uint32_t new_size, bool map = true) {
// Unmap the buffer if it's mapped
if (dynamic_buffer.mapped_data_ != nullptr) {
dynamic_buffer.buffer_->unmap();
}
// If there's already an upload buffer, move it into the stale buffers so it persists until the start of next frame.
if (upload_buffer_) {
stale_buffers_.emplace_back(std::move(upload_buffer_));
// If there's already a buffer, move it into the stale buffers so it persists until the start of next frame.
if (dynamic_buffer.buffer_ != nullptr) {
stale_buffers_.emplace_back(std::move(dynamic_buffer.buffer_));
}
// Create the new upload buffer, update the size and map it.
upload_buffer_ = render_context_->device->createBuffer(RT64::RenderBufferDesc::UploadBuffer(new_size));
upload_buffer_size_ = new_size;
upload_buffer_bytes_used_ = 0;
// Create the new buffer, update the size and map it.
dynamic_buffer.buffer_ = render_context_->device->createBuffer(RT64::RenderBufferDesc::UploadBuffer(new_size, dynamic_buffer.flags_));
dynamic_buffer.size_ = new_size;
dynamic_buffer.bytes_used_ = 0;
if (map) {
upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map());
}
else {
upload_buffer_mapped_data_ = nullptr;
dynamic_buffer.mapped_data_ = reinterpret_cast<uint8_t*>(dynamic_buffer.buffer_->map());
}
}
uint32_t allocate_upload_data(uint32_t num_bytes) {
// Check if there's enough remaining room in the upload buffer to allocate the requested bytes.
uint32_t total_bytes = num_bytes + upload_buffer_bytes_used_;
uint32_t allocate_dynamic_data(DynamicBuffer &dynamic_buffer, uint32_t num_bytes) {
// Check if there's enough remaining room in the buffer to allocate the requested bytes.
uint32_t total_bytes = num_bytes + dynamic_buffer.bytes_used_;
if (total_bytes > upload_buffer_size_) {
// There isn't, so mark the current upload buffer as stale and allocate a new one with 50% more space than the required amount.
resize_upload_buffer(total_bytes + total_bytes / 2);
if (total_bytes > dynamic_buffer.size_) {
// There isn't, so mark the current buffer as stale and allocate a new one with 50% more space than the required amount.
resize_dynamic_buffer(dynamic_buffer, total_bytes + total_bytes / 2);
}
// Record the current end of the upload buffer to return.
uint32_t offset = upload_buffer_bytes_used_;
// Record the current end of the buffer to return.
uint32_t offset = dynamic_buffer.bytes_used_;
// Bump the upload buffer's end forward by the number of bytes allocated.
upload_buffer_bytes_used_ += num_bytes;
// Bump the buffer's end forward by the number of bytes allocated.
dynamic_buffer.bytes_used_ += num_bytes;
return offset;
}
uint32_t allocate_upload_data_aligned(uint32_t num_bytes, uint32_t alignment) {
// Check if there's enough remaining room in the upload buffer to allocate the requested bytes.
uint32_t total_bytes = num_bytes + upload_buffer_bytes_used_;
uint32_t allocate_dynamic_data_aligned(DynamicBuffer &dynamic_buffer, uint32_t num_bytes, uint32_t alignment) {
// Check if there's enough remaining room in the buffer to allocate the requested bytes.
uint32_t total_bytes = num_bytes + dynamic_buffer.bytes_used_;
// Determine the amount of padding needed to meet the target alignment.
uint32_t padding_bytes = ((upload_buffer_bytes_used_ + alignment - 1) / alignment) * alignment - upload_buffer_bytes_used_;
uint32_t padding_bytes = ((dynamic_buffer.bytes_used_ + alignment - 1) / alignment) * alignment - dynamic_buffer.bytes_used_;
// If there isn't enough room to allocate the required bytes plus the padding then resize the upload buffer and allocate from the start of the new one.
if (total_bytes + padding_bytes > upload_buffer_size_) {
resize_upload_buffer(total_bytes + total_bytes / 2);
// If there isn't enough room to allocate the required bytes plus the padding then resize the buffer and allocate from the start of the new one.
if (total_bytes + padding_bytes > dynamic_buffer.size_) {
resize_dynamic_buffer(dynamic_buffer, total_bytes + total_bytes / 2);
upload_buffer_bytes_used_ += num_bytes;
dynamic_buffer.bytes_used_ += num_bytes;
return 0;
}
// Otherwise allocate the padding and required bytes and offset the allocated position by the padding size.
return allocate_upload_data(padding_bytes + num_bytes) + padding_bytes;
}
void resize_vertex_buffer(uint32_t new_size) {
if (vertex_buffer_) {
stale_buffers_.emplace_back(std::move(vertex_buffer_));
}
vertex_buffer_ = render_context_->device->createBuffer(RT64::RenderBufferDesc::VertexBuffer(new_size, RT64::RenderHeapType::DEFAULT));
vertex_buffer_size_ = new_size;
}
void resize_index_buffer(uint32_t new_size) {
if (index_buffer_) {
stale_buffers_.emplace_back(std::move(index_buffer_));
}
index_buffer_ = render_context_->device->createBuffer(RT64::RenderBufferDesc::IndexBuffer(new_size, RT64::RenderHeapType::DEFAULT));
index_buffer_size_ = new_size;
return allocate_dynamic_data(dynamic_buffer, padding_bytes + num_bytes) + padding_bytes;
}
void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, const Rml::Vector2f& translation) override {
uint32_t vert_size_bytes = num_vertices * sizeof(*vertices);
uint32_t index_size_bytes = num_indices * sizeof(*indices);
uint32_t total_bytes = vert_size_bytes + index_size_bytes;
uint32_t index_bytes_start = vert_size_bytes;
if (!textures_.contains(texture)) {
if (texture == 0) {
// Create a 1x1 pixel white texture as the first handle
@ -355,37 +349,13 @@ public:
}
}
uint32_t upload_buffer_offset = allocate_upload_data(total_bytes);
if (vert_size_bytes > vertex_buffer_size_) {
resize_vertex_buffer(vert_size_bytes + vert_size_bytes / 2);
}
if (index_size_bytes > index_buffer_size_) {
resize_index_buffer(index_size_bytes + index_size_bytes / 2);
}
// Copy the vertex and index data into the mapped upload buffer.
memcpy(upload_buffer_mapped_data_ + upload_buffer_offset, vertices, vert_size_bytes);
memcpy(upload_buffer_mapped_data_ + upload_buffer_offset + vert_size_bytes, indices, index_size_bytes);
// Prepare the vertex and index buffers for being copied to.
RT64::RenderBufferBarrier copy_barriers[] = {
RT64::RenderBufferBarrier(vertex_buffer_.get(), RT64::RenderBufferAccess::WRITE),
RT64::RenderBufferBarrier(index_buffer_.get(), RT64::RenderBufferAccess::WRITE)
};
list_->barriers(RT64::RenderBarrierStage::COPY, copy_barriers, uint32_t(std::size(copy_barriers)));
// Copy from the upload buffer to the vertex and index buffers.
list_->copyBufferRegion(vertex_buffer_->at(0), upload_buffer_->at(upload_buffer_offset), vert_size_bytes);
list_->copyBufferRegion(index_buffer_->at(0), upload_buffer_->at(upload_buffer_offset + index_bytes_start), index_size_bytes);
// Prepare the vertex and index buffers for being used for rendering.
RT64::RenderBufferBarrier usage_barriers[] = {
RT64::RenderBufferBarrier(vertex_buffer_.get(), RT64::RenderBufferAccess::READ),
RT64::RenderBufferBarrier(index_buffer_.get(), RT64::RenderBufferAccess::READ)
};
list_->barriers(RT64::RenderBarrierStage::GRAPHICS, usage_barriers, uint32_t(std::size(usage_barriers)));
// Copy the vertex and index data into the mapped buffers.
uint32_t vert_size_bytes = num_vertices * sizeof(*vertices);
uint32_t index_size_bytes = num_indices * sizeof(*indices);
uint32_t vertex_buffer_offset = allocate_dynamic_data(vertex_buffer_, vert_size_bytes);
uint32_t index_buffer_offset = allocate_dynamic_data(index_buffer_, index_size_bytes);
memcpy(vertex_buffer_.mapped_data_ + vertex_buffer_offset, vertices, vert_size_bytes);
memcpy(index_buffer_.mapped_data_ + index_buffer_offset, indices, index_size_bytes);
list_->setViewports(RT64::RenderViewport{ 0, 0, float(window_width_), float(window_height_) });
if (scissor_enabled_) {
@ -399,9 +369,9 @@ public:
list_->setScissors(RT64::RenderRect{ 0, 0, window_width_, window_height_ });
}
RT64::RenderIndexBufferView index_view{index_buffer_->at(0), index_size_bytes, RT64::RenderFormat::R32_UINT};
RT64::RenderIndexBufferView index_view{index_buffer_.buffer_->at(index_buffer_offset), index_size_bytes, RT64::RenderFormat::R32_UINT};
list_->setIndexBuffer(&index_view);
RT64::RenderVertexBufferView vertex_view{vertex_buffer_->at(0), vert_size_bytes};
RT64::RenderVertexBufferView vertex_view{vertex_buffer_.buffer_->at(vertex_buffer_offset), vert_size_bytes};
list_->setVertexBuffers(0, &vertex_view, 1, &vertex_slot_);
list_->setGraphicsDescriptorSet(textures_.at(texture).set.get(), 1);
@ -522,10 +492,10 @@ public:
uint32_t uploaded_size_bytes = row_byte_width * source_dimensions.y;
// Allocate room in the upload buffer for the uploaded data.
uint32_t upload_buffer_offset = allocate_upload_data_aligned(uploaded_size_bytes, 512);
uint32_t upload_buffer_offset = allocate_dynamic_data_aligned(upload_buffer_, uploaded_size_bytes, 512);
// Copy the source data into the upload buffer.
uint8_t* dst_data = upload_buffer_mapped_data_ + upload_buffer_offset;
uint8_t* dst_data = upload_buffer_.mapped_data_ + upload_buffer_offset;
if (row_byte_padding == 0) {
// Copy row-by-row if the image is flipped.
@ -557,7 +527,7 @@ public:
// Copy the upload buffer into the texture.
list_->copyTextureRegion(
RT64::RenderTextureCopyLocation::Subresource(texture.get()),
RT64::RenderTextureCopyLocation::PlacedFootprint(upload_buffer_.get(), RmlTextureFormat, source_dimensions.x, source_dimensions.y, 1, row_width, upload_buffer_offset));
RT64::RenderTextureCopyLocation::PlacedFootprint(upload_buffer_.buffer_.get(), RmlTextureFormat, source_dimensions.x, source_dimensions.y, 1, row_width, upload_buffer_offset));
// Prepare the texture for being read from a pixel shader.
list_->barriers(RT64::RenderBarrierStage::GRAPHICS, RT64::RenderTextureBarrier(texture.get(), RT64::RenderTextureLayout::SHADER_READ));
@ -621,9 +591,10 @@ public:
// Clear out any stale buffers from the last command list.
stale_buffers_.clear();
// Reset and map the upload buffer.
upload_buffer_bytes_used_ = 0;
upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map());
// Reset buffers.
reset_dynamic_buffer(upload_buffer_);
reset_dynamic_buffer(vertex_buffer_);
reset_dynamic_buffer(index_buffer_);
// Set an internal texture as the render target if MSAA is enabled.
if (multisampling_.sampleCount > 1) {
@ -661,13 +632,11 @@ public:
list->drawInstanced(3, 1, 0, 0);
}
list_ = nullptr;
end_dynamic_buffer(upload_buffer_);
end_dynamic_buffer(vertex_buffer_);
end_dynamic_buffer(index_buffer_);
// Unmap the upload buffer if it's mapped.
if (upload_buffer_mapped_data_) {
upload_buffer_->unmap();
upload_buffer_mapped_data_ = nullptr;
}
list_ = nullptr;
}
};