mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-04-07 05:56:53 +02:00
Draggable improvements to mod menu and runtime update.
This commit is contained in:
parent
a4df328a4e
commit
c5c1507fb7
@ -6,8 +6,8 @@ namespace recompui {
|
||||
|
||||
static Rml::Unit to_rml(Unit unit) {
|
||||
switch (unit) {
|
||||
case Unit::Float:
|
||||
return Rml::Unit::NUMBER;
|
||||
case Unit::Px:
|
||||
return Rml::Unit::PX;
|
||||
case Unit::Dp:
|
||||
return Rml::Unit::DP;
|
||||
case Unit::Percent:
|
||||
@ -97,6 +97,62 @@ namespace recompui {
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Display to_rml(Display display) {
|
||||
switch (display) {
|
||||
case Display::None:
|
||||
return Rml::Style::Display::None;
|
||||
case Display::Block:
|
||||
return Rml::Style::Display::Block;
|
||||
case Display::Inline:
|
||||
return Rml::Style::Display::Inline;
|
||||
case Display::InlineBlock:
|
||||
return Rml::Style::Display::InlineBlock;
|
||||
case Display::FlowRoot:
|
||||
return Rml::Style::Display::FlowRoot;
|
||||
case Display::Flex:
|
||||
return Rml::Style::Display::Flex;
|
||||
case Display::InlineFlex:
|
||||
return Rml::Style::Display::InlineFlex;
|
||||
case Display::Table:
|
||||
return Rml::Style::Display::Table;
|
||||
case Display::InlineTable:
|
||||
return Rml::Style::Display::InlineTable;
|
||||
case Display::TableRow:
|
||||
return Rml::Style::Display::TableRow;
|
||||
case Display::TableRowGroup:
|
||||
return Rml::Style::Display::TableRowGroup;
|
||||
case Display::TableColumn:
|
||||
return Rml::Style::Display::TableColumn;
|
||||
case Display::TableColumnGroup:
|
||||
return Rml::Style::Display::TableColumnGroup;
|
||||
case Display::TableCell:
|
||||
return Rml::Style::Display::TableCell;
|
||||
default:
|
||||
assert(false && "Unknown display.");
|
||||
return Rml::Style::Display::Block;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::JustifyContent to_rml(JustifyContent justify_content) {
|
||||
switch (justify_content) {
|
||||
case JustifyContent::FlexStart:
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
case JustifyContent::FlexEnd:
|
||||
return Rml::Style::JustifyContent::FlexEnd;
|
||||
case JustifyContent::Center:
|
||||
return Rml::Style::JustifyContent::Center;
|
||||
case JustifyContent::SpaceBetween:
|
||||
return Rml::Style::JustifyContent::SpaceBetween;
|
||||
case JustifyContent::SpaceAround:
|
||||
return Rml::Style::JustifyContent::SpaceAround;
|
||||
case JustifyContent::SpaceEvenly:
|
||||
return Rml::Style::JustifyContent::SpaceEvenly;
|
||||
default:
|
||||
assert(false && "Unknown justify content.");
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation) {
|
||||
property_map[property_id] = property;
|
||||
}
|
||||
@ -322,43 +378,11 @@ namespace recompui {
|
||||
}
|
||||
|
||||
void Style::set_display(Display display) {
|
||||
switch (display) {
|
||||
case Display::Block:
|
||||
set_property(Rml::PropertyId::Display, Rml::Style::Display::Block, Animation());
|
||||
break;
|
||||
case Display::Flex:
|
||||
set_property(Rml::PropertyId::Display, Rml::Style::Display::Flex, Animation());
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown display.");
|
||||
break;
|
||||
}
|
||||
set_property(Rml::PropertyId::Display, to_rml(display), Animation());
|
||||
}
|
||||
|
||||
void Style::set_justify_content(JustifyContent justify_content) {
|
||||
switch (justify_content) {
|
||||
case JustifyContent::FlexStart:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::FlexStart, Animation());
|
||||
break;
|
||||
case JustifyContent::FlexEnd:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::FlexEnd, Animation());
|
||||
break;
|
||||
case JustifyContent::Center:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center, Animation());
|
||||
break;
|
||||
case JustifyContent::SpaceBetween:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceBetween, Animation());
|
||||
break;
|
||||
case JustifyContent::SpaceAround:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceAround, Animation());
|
||||
break;
|
||||
case JustifyContent::SpaceEvenly:
|
||||
set_property(Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceEvenly, Animation());
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown justify content.");
|
||||
break;
|
||||
}
|
||||
set_property(Rml::PropertyId::JustifyContent, to_rml(justify_content), Animation());
|
||||
}
|
||||
|
||||
void Style::set_flex_grow(float grow, Animation animation) {
|
||||
|
@ -127,8 +127,20 @@ namespace recompui {
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
None,
|
||||
Block,
|
||||
Flex
|
||||
Inline,
|
||||
InlineBlock,
|
||||
FlowRoot,
|
||||
Flex,
|
||||
InlineFlex,
|
||||
Table,
|
||||
InlineTable,
|
||||
TableRow,
|
||||
TableRowGroup,
|
||||
TableColumn,
|
||||
TableColumnGroup,
|
||||
TableCell
|
||||
};
|
||||
|
||||
enum class Position {
|
||||
@ -166,7 +178,7 @@ namespace recompui {
|
||||
};
|
||||
|
||||
enum class Unit {
|
||||
Float,
|
||||
Px,
|
||||
Dp,
|
||||
Percent
|
||||
};
|
||||
@ -209,6 +221,7 @@ namespace recompui {
|
||||
static Animation set() {
|
||||
Animation a;
|
||||
a.type = AnimationType::Set;
|
||||
a.duration = 0.0f;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,10 @@
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ModEntry::ModEntry(Element *parent, uint32_t mod_index, ModMenu *mod_menu) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Focus, EventType::Drag)) {
|
||||
assert(mod_menu != nullptr);
|
||||
// ModEntryView
|
||||
|
||||
this->mod_index = mod_index;
|
||||
this->mod_menu = mod_menu;
|
||||
ModEntryView::ModEntryView(Element *parent) : Element(parent) {
|
||||
ContextId context = get_current_context();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
@ -29,9 +28,6 @@ ModEntry::ModEntry(Element *parent, uint32_t mod_index, ModMenu *mod_menu) : Ele
|
||||
set_border_color(Color{ 242, 242, 242, 204 });
|
||||
set_background_color(Color{ 242, 242, 242, 12 });
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_drag(Drag::Drag);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
{
|
||||
thumbnail_image = context.create_element<Image>(this);
|
||||
@ -54,22 +50,45 @@ ModEntry::ModEntry(Element *parent, uint32_t mod_index, ModMenu *mod_menu) : Ele
|
||||
} // this
|
||||
}
|
||||
|
||||
ModEntry::~ModEntry() {
|
||||
ModEntryView::~ModEntryView() {
|
||||
|
||||
}
|
||||
|
||||
void ModEntry::set_mod_drag_callback(std::function<void(uint32_t, EventDrag)> callback) {
|
||||
drag_callback = callback;
|
||||
}
|
||||
|
||||
void ModEntry::set_mod_details(const recomp::mods::ModDetails &details) {
|
||||
void ModEntryView::set_mod_details(const recomp::mods::ModDetails &details) {
|
||||
name_label->set_text(details.mod_id);
|
||||
}
|
||||
|
||||
void ModEntry::process_event(const Event& e) {
|
||||
// ModEntryButton
|
||||
|
||||
ModEntryButton::ModEntryButton(Element *parent, uint32_t mod_index) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Focus, EventType::Drag)) {
|
||||
this->mod_index = mod_index;
|
||||
|
||||
set_drag(Drag::Drag);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
view = context.create_element<ModEntryView>(this);
|
||||
}
|
||||
|
||||
ModEntryButton::~ModEntryButton() {
|
||||
|
||||
}
|
||||
|
||||
void ModEntryButton::set_mod_selected_callback(std::function<void(uint32_t)> callback) {
|
||||
selected_callback = callback;
|
||||
}
|
||||
|
||||
void ModEntryButton::set_mod_drag_callback(std::function<void(uint32_t, EventDrag)> callback) {
|
||||
drag_callback = callback;
|
||||
}
|
||||
|
||||
void ModEntryButton::set_mod_details(const recomp::mods::ModDetails &details) {
|
||||
view->set_mod_details(details);
|
||||
}
|
||||
|
||||
void ModEntryButton::process_event(const Event& e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
mod_menu->set_active_mod(mod_index);
|
||||
selected_callback(mod_index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
break;
|
||||
@ -83,15 +102,7 @@ void ModEntry::process_event(const Event& e) {
|
||||
}
|
||||
}
|
||||
|
||||
void ModMenu::set_active_mod(int32_t mod_index) {
|
||||
active_mod_index = mod_index;
|
||||
if (active_mod_index >= 0) {
|
||||
bool mod_enabled = recomp::mods::is_mod_enabled(mod_details[mod_index].mod_id);
|
||||
bool auto_enabled = recomp::mods::is_mod_auto_enabled(mod_details[mod_index].mod_id);
|
||||
bool toggle_enabled = !auto_enabled && (mod_details[mod_index].runtime_toggleable || !ultramodern::is_game_started());
|
||||
mod_details_panel->set_mod_details(mod_details[mod_index], mod_enabled, toggle_enabled);
|
||||
}
|
||||
}
|
||||
// ModMenu
|
||||
|
||||
void ModMenu::refresh_mods() {
|
||||
recomp::mods::scan_mods();
|
||||
@ -105,34 +116,93 @@ void ModMenu::mod_toggled(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void ModMenu::mod_selected(uint32_t mod_index) {
|
||||
active_mod_index = mod_index;
|
||||
if (active_mod_index >= 0) {
|
||||
bool mod_enabled = recomp::mods::is_mod_enabled(mod_details[mod_index].mod_id);
|
||||
bool auto_enabled = recomp::mods::is_mod_auto_enabled(mod_details[mod_index].mod_id);
|
||||
bool toggle_enabled = !auto_enabled && (mod_details[mod_index].runtime_toggleable || !ultramodern::is_game_started());
|
||||
mod_details_panel->set_mod_details(mod_details[mod_index], mod_enabled, toggle_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void ModMenu::mod_dragged(uint32_t mod_index, EventDrag drag) {
|
||||
// Binary search for the drag area.
|
||||
size_t low = 0;
|
||||
size_t high = mod_entries.size();
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
float drag_area_top = mod_entries[mid]->get_absolute_top();
|
||||
if (drag.y < drag_area_top) {
|
||||
high = mid;
|
||||
}
|
||||
else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t new_index = 0;
|
||||
if (low > 0) {
|
||||
new_index = low - 1;
|
||||
}
|
||||
|
||||
switch (drag.phase) {
|
||||
case DragPhase::End:
|
||||
recomp::mods::set_mod_index(game_mod_id, mod_details[mod_index].mod_id, new_index);
|
||||
case DragPhase::Start: {
|
||||
for (size_t i = 0; i < mod_entry_buttons.size(); i++) {
|
||||
mod_entry_middles[i] = mod_entry_buttons[i]->get_absolute_top() + mod_entry_buttons[i]->get_client_height() / 2.0f;
|
||||
}
|
||||
|
||||
// When the drag phase starts, we make the floating mod details visible and store the relative coordinate of the
|
||||
// mouse cursor. Instantly hide the real element and use a spacer in its place that will stay on the same size as
|
||||
// long as the cursor is hovering over this slot.
|
||||
float width = mod_entry_buttons[mod_index]->get_client_width();
|
||||
float height = mod_entry_buttons[mod_index]->get_client_height();
|
||||
float left = mod_entry_buttons[mod_index]->get_absolute_left() - get_absolute_left();
|
||||
float top = mod_entry_buttons[mod_index]->get_absolute_top() - (height / 2.0f); // TODO: Figure out why this adjustment is even necessary.
|
||||
mod_entry_buttons[mod_index]->set_display(Display::None);
|
||||
mod_entry_floating_view->set_display(Display::Flex);
|
||||
mod_entry_floating_view->set_mod_details(mod_details[mod_index]);
|
||||
mod_entry_floating_view->set_left(left, Unit::Px);
|
||||
mod_entry_floating_view->set_top(top, Unit::Px);
|
||||
mod_entry_floating_view->set_width(width, Unit::Px);
|
||||
mod_entry_floating_view->set_height(height, Unit::Px);
|
||||
mod_drag_start_coordinates[0] = drag.x;
|
||||
mod_drag_start_coordinates[1] = drag.y;
|
||||
mod_drag_view_coordinates[0] = left;
|
||||
mod_drag_view_coordinates[1] = top;
|
||||
|
||||
mod_drag_target_index = mod_index;
|
||||
mod_drag_spacer_height = height;
|
||||
mod_entry_spacers[mod_drag_target_index]->set_height(mod_drag_spacer_height, Unit::Px);
|
||||
break;
|
||||
}
|
||||
case DragPhase::Move: {
|
||||
// Binary search for the drag area.
|
||||
uint32_t low = 0;
|
||||
uint32_t high = mod_entry_buttons.size();
|
||||
while (low < high) {
|
||||
uint32_t mid = low + (high - low) / 2;
|
||||
if (drag.y < mod_entry_middles[mid]) {
|
||||
high = mid;
|
||||
}
|
||||
else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t new_index = low;
|
||||
float delta_x = drag.x - mod_drag_start_coordinates[0];
|
||||
float delta_y = drag.y - mod_drag_start_coordinates[1];
|
||||
mod_entry_floating_view->set_left(mod_drag_view_coordinates[0] + delta_x, Unit::Px);
|
||||
mod_entry_floating_view->set_top(mod_drag_view_coordinates[1] + delta_y, Unit::Px);
|
||||
if (mod_drag_target_index != new_index) {
|
||||
mod_entry_spacers[mod_drag_target_index]->set_height(0.0f, Unit::Px);
|
||||
mod_entry_spacers[new_index]->set_height(mod_drag_spacer_height, Unit::Px);
|
||||
mod_drag_target_index = new_index;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DragPhase::End: {
|
||||
// Dragging has ended, hide the floating view.
|
||||
mod_entry_buttons[mod_index]->set_display(Display::Block);
|
||||
mod_entry_spacers[mod_drag_target_index]->set_height(0.0f, Unit::Px);
|
||||
mod_entry_floating_view->set_display(Display::None);
|
||||
|
||||
// Result needs a small substraction when dragging downwards.
|
||||
if (mod_drag_target_index > mod_index) {
|
||||
mod_drag_target_index--;
|
||||
}
|
||||
|
||||
// Re-order the mods and update all the details on the menu.
|
||||
recomp::mods::set_mod_index(game_mod_id, mod_details[mod_index].mod_id, mod_drag_target_index);
|
||||
mod_details = recomp::mods::get_mod_details(game_mod_id);
|
||||
for (size_t i = 0; i < mod_entries.size(); i++) {
|
||||
mod_entries[i]->set_mod_details(mod_details[i]);
|
||||
for (size_t i = 0; i < mod_entry_buttons.size(); i++) {
|
||||
mod_entry_buttons[i]->set_mod_details(mod_details[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -218,17 +288,28 @@ void ModMenu::create_mod_list() {
|
||||
|
||||
// Clear the contents of the list scroll.
|
||||
list_scroll_container->clear_children();
|
||||
mod_entries.clear();
|
||||
mod_entry_buttons.clear();
|
||||
mod_entry_spacers.clear();
|
||||
|
||||
// Create the child elements for the list scroll.
|
||||
for (size_t mod_index = 0; mod_index < mod_details.size(); mod_index++) {
|
||||
ModEntry *mod_entry = context.create_element<ModEntry>(list_scroll_container, mod_index, this);
|
||||
Element *spacer = context.create_element<Element>(list_scroll_container);
|
||||
mod_entry_spacers.emplace_back(spacer);
|
||||
|
||||
ModEntryButton *mod_entry = context.create_element<ModEntryButton>(list_scroll_container, mod_index);
|
||||
mod_entry->set_mod_selected_callback(std::bind(&ModMenu::mod_selected, this, std::placeholders::_1));
|
||||
mod_entry->set_mod_drag_callback(std::bind(&ModMenu::mod_dragged, this, std::placeholders::_1, std::placeholders::_2));
|
||||
mod_entry->set_mod_details(mod_details[mod_index]);
|
||||
mod_entries.emplace_back(mod_entry);
|
||||
mod_entry_buttons.emplace_back(mod_entry);
|
||||
}
|
||||
|
||||
set_active_mod(0);
|
||||
// Add one extra spacer at the bottom.
|
||||
Element *spacer = context.create_element<Element>(list_scroll_container);
|
||||
mod_entry_spacers.emplace_back(spacer);
|
||||
|
||||
mod_entry_middles.resize(mod_entry_buttons.size());
|
||||
|
||||
mod_selected(0);
|
||||
}
|
||||
|
||||
ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
@ -281,6 +362,10 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
refresh_button->add_pressed_callback(std::bind(&ModMenu::refresh_mods, this));
|
||||
} // footer_container
|
||||
} // this
|
||||
|
||||
mod_entry_floating_view = context.create_element<ModEntryView>(this);
|
||||
mod_entry_floating_view->set_display(Display::None);
|
||||
mod_entry_floating_view->set_position(Position::Absolute);
|
||||
|
||||
refresh_mods();
|
||||
|
||||
|
@ -10,21 +10,31 @@ namespace recompui {
|
||||
|
||||
class ModMenu;
|
||||
|
||||
class ModEntry : public Element {
|
||||
class ModEntryView : public Element {
|
||||
public:
|
||||
ModEntry(Element *parent, uint32_t mod_index, ModMenu *mod_menu);
|
||||
virtual ~ModEntry();
|
||||
ModEntryView(Element *parent);
|
||||
virtual ~ModEntryView();
|
||||
void set_mod_details(const recomp::mods::ModDetails &details);
|
||||
private:
|
||||
Image *thumbnail_image = nullptr;
|
||||
Container *body_container = nullptr;
|
||||
Label *name_label = nullptr;
|
||||
Label *description_label = nullptr;
|
||||
};
|
||||
|
||||
class ModEntryButton : public Element {
|
||||
public:
|
||||
ModEntryButton(Element *parent, uint32_t mod_index);
|
||||
virtual ~ModEntryButton();
|
||||
void set_mod_selected_callback(std::function<void(uint32_t)> callback);
|
||||
void set_mod_drag_callback(std::function<void(uint32_t, EventDrag)> callback);
|
||||
void set_mod_details(const recomp::mods::ModDetails &details);
|
||||
protected:
|
||||
virtual void process_event(const Event &e);
|
||||
private:
|
||||
uint32_t mod_index = 0;
|
||||
ModMenu *mod_menu = nullptr;
|
||||
Image *thumbnail_image = nullptr;
|
||||
Container *body_container = nullptr;
|
||||
Label *name_label = nullptr;
|
||||
Label *description_label = nullptr;
|
||||
ModEntryView *view = nullptr;
|
||||
std::function<void(uint32_t)> selected_callback = nullptr;
|
||||
std::function<void(uint32_t, EventDrag)> drag_callback = nullptr;
|
||||
};
|
||||
|
||||
@ -32,10 +42,10 @@ class ModMenu : public Element {
|
||||
public:
|
||||
ModMenu(Element *parent);
|
||||
virtual ~ModMenu();
|
||||
void set_active_mod(int32_t mod_index);
|
||||
private:
|
||||
void refresh_mods();
|
||||
void mod_toggled(bool enabled);
|
||||
void mod_selected(uint32_t mod_index);
|
||||
void mod_dragged(uint32_t mod_index, EventDrag drag);
|
||||
void mod_configure_requested();
|
||||
void mod_enum_option_changed(const std::string &id, uint32_t value);
|
||||
@ -50,7 +60,14 @@ private:
|
||||
Container *footer_container = nullptr;
|
||||
Button *refresh_button = nullptr;
|
||||
int32_t active_mod_index = -1;
|
||||
std::vector<ModEntry *> mod_entries;
|
||||
std::vector<ModEntryButton *> mod_entry_buttons;
|
||||
std::vector<Element *> mod_entry_spacers;
|
||||
std::vector<float> mod_entry_middles;
|
||||
ModEntryView *mod_entry_floating_view = nullptr;
|
||||
float mod_drag_start_coordinates[2] = {};
|
||||
float mod_drag_view_coordinates[2] = {};
|
||||
uint32_t mod_drag_target_index = 0;
|
||||
float mod_drag_spacer_height = 0.0f;
|
||||
std::vector<recomp::mods::ModDetails> mod_details{};
|
||||
std::string game_mod_id;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user