diff --git a/src/screenComponents/dockingButton.cpp b/src/screenComponents/dockingButton.cpp index c10b8a2675..e49bf3dc58 100644 --- a/src/screenComponents/dockingButton.cpp +++ b/src/screenComponents/dockingButton.cpp @@ -1,46 +1,87 @@ #include #include "playerInfo.h" #include "dockingButton.h" +#include "gui/gui2_button.h" +#include "gui/gui2_listbox.h" +#include "gui/gui2_panel.h" #include "systems/collision.h" #include "systems/docking.h" #include "components/collision.h" #include "components/docking.h" #include "components/faction.h" +#include "components/name.h" #include "ecs/query.h" - GuiDockingButton::GuiDockingButton(GuiContainer* owner, string id) -: GuiButton(owner, id, "", [this]() { click(); }) +: GuiElement(owner, id) { - setIcon("gui/icons/docking"); -} + background_panel = new GuiPanel(this, id + "_BG"); + background_panel + ->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax) + ->setPosition(0.0f, 0.0f, sp::Alignment::TopLeft) + ->hide(); -void GuiDockingButton::click() -{ - if (!my_spaceship) { return; } - auto port = my_spaceship.getComponent(); - if (!port) { return; } + action_button = new GuiButton(this, id + "_BTN", tr("Request dock"), + [this]() + { + if (!my_spaceship || !my_player_info) return; + auto port = my_spaceship.getComponent(); + if (!port) return; - switch(port->state) - { - case DockingPort::State::NotDocking: - my_player_info->commandDock(findDockingTarget()); - break; - case DockingPort::State::Docking: - my_player_info->commandAbortDock(); - break; - case DockingPort::State::Docked: - my_player_info->commandUndock(); - break; - } + switch (port->state) + { + case DockingPort::State::NotDocking: + dock_targets = findDockingTargets(); + // Expand list if it has more than one entry. + // Otherwise, just dock. + if (dock_targets.size() == 1) + my_player_info->commandDock(dock_targets[0]); + else if (dock_targets.size() > 1) expanded = true; + break; + case DockingPort::State::Docking: + my_player_info->commandAbortDock(); + break; + case DockingPort::State::Docked: + my_player_info->commandUndock(); + break; + } + } + ); + action_button + ->setIcon("gui/icons/docking") + ->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax) + ->setPosition(0.0f, 0.0f, sp::Alignment::TopLeft); + + target_list = new GuiListbox(this, id + "_LIST", + [this](int index, string value) + { + if (!my_player_info) return; + expanded = false; + if (value == "cancel") return; + int idx = value.toInt(); + if (idx >= 0 && idx < static_cast(dock_targets.size())) + my_player_info->commandDock(dock_targets[idx]); + + } + ); + target_list + ->setPosition(0.0f, 0.0f, sp::Alignment::TopLeft) + ->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax) + ->hide(); } void GuiDockingButton::onUpdate() { - if (!my_spaceship) { hide(); return; } + if (!my_spaceship) + { + hide(); + return; + } auto port = my_spaceship.getComponent(); setVisible(port != nullptr); + if (!port) return; + // Keyboard shortcuts dock to the nearest. if (isVisible()) { if (keys.helms_dock_action.getDown()) @@ -48,7 +89,11 @@ void GuiDockingButton::onUpdate() switch(port->state) { case DockingPort::State::NotDocking: - my_player_info->commandDock(findDockingTarget()); + { + auto targets = findDockingTargets(); + if (!targets.empty()) + my_player_info->commandDock(targets[0]); + } break; case DockingPort::State::Docking: my_player_info->commandAbortDock(); @@ -59,62 +104,91 @@ void GuiDockingButton::onUpdate() } } else if (keys.helms_dock_request.getDown()) - my_player_info->commandDock(findDockingTarget()); + { + auto targets = findDockingTargets(); + if (!targets.empty()) + my_player_info->commandDock(targets[0]); + } else if (keys.helms_dock_abort.getDown()) my_player_info->commandAbortDock(); else if (keys.helms_undock.getDown()) my_player_info->commandUndock(); } -} -void GuiDockingButton::onDraw(sp::RenderTarget& renderer) -{ - if (!my_spaceship) { return; } - auto port = my_spaceship.getComponent(); - if (!port) { return; } + // Collapse the listbox when docking state changes away from NotDocking. + if (port->state != DockingPort::State::NotDocking) expanded = false; - switch(port->state) + // Update the docking list if it's expanded. + if (expanded) { - case DockingPort::State::NotDocking: - setText(tr("Request Dock")); - if (DockingSystem::canStartDocking(my_spaceship) && findDockingTarget()) + dock_targets = findDockingTargets(); + if (dock_targets.empty()) expanded = false; + else { - enable(); - }else{ - disable(); + target_list->clear(); + for (int i = 0; i < static_cast(dock_targets.size()); i++) + { + // Use the callsign if available for the entry name. + string name = tr("Unknown"); + if (auto cs = dock_targets[i].getComponent()) + name = cs->callsign; + target_list->addEntry(name, string(i)); + } + target_list->addEntry(tr("Cancel"), "cancel"); + + // Expand height to fit all entries. + layout.size.y = (dock_targets.size() + 1) * item_height; + background_panel->show(); + action_button->hide(); + target_list->show(); + return; } + } + + // If collapsed, just show the action button. + layout.size.y = item_height; + background_panel->hide(); + target_list->hide(); + action_button->show(); + + switch (port->state) + { + case DockingPort::State::NotDocking: + dock_targets = findDockingTargets(); + action_button + ->setText(tr("Request dock")) + ->setEnable(DockingSystem::canStartDocking(my_spaceship) && !dock_targets.empty()); break; case DockingPort::State::Docking: - setText(tr("Cancel Docking")); - enable(); + action_button + ->setText(tr("Cancel docking")) + ->enable(); break; case DockingPort::State::Docked: - setText(tr("Undock")); - enable(); + action_button + ->setText(tr("Undock")) + ->enable(); break; } - - GuiButton::onDraw(renderer); } -sp::ecs::Entity GuiDockingButton::findDockingTarget() +std::vector GuiDockingButton::findDockingTargets() { - if (!my_spaceship) { return {}; } + std::vector targets; + if (!my_spaceship) return targets; auto port = my_spaceship.getComponent(); - if (!port) { return {}; } + if (!port) return targets; auto my_transform = my_spaceship.getComponent(); - if (!my_transform) { return {}; } + if (!my_transform) return targets; - sp::ecs::Entity dock_object; - for(auto [entity, bay, transform, physics] : sp::ecs::Query()) + for (auto [entity, bay, transform, physics] : sp::ecs::Query()) { if (entity == my_spaceship) continue; if (Faction::getRelation(my_spaceship, entity) == FactionRelation::Enemy) continue; if (port->canDockOn(bay) == DockingStyle::None) continue; - if (glm::length(transform.getPosition() - my_transform->getPosition()) > 1000.0f + physics.getSize().x) continue; - - dock_object = entity; - break; + if (glm::length(transform.getPosition() - my_transform->getPosition()) > 1000.0f + std::max(physics.getSize().x, physics.getSize().y)) continue; + targets.push_back(entity); } - return dock_object; + + return targets; } diff --git a/src/screenComponents/dockingButton.h b/src/screenComponents/dockingButton.h index bad68ee23d..294c46beaa 100644 --- a/src/screenComponents/dockingButton.h +++ b/src/screenComponents/dockingButton.h @@ -1,19 +1,27 @@ -#ifndef DOCKING_BUTTON_H -#define DOCKING_BUTTON_H +#pragma once -#include "gui/gui2_button.h" +#include "gui/gui2_element.h" +#include "ecs/entity.h" -class GuiDockingButton : public GuiButton +class GuiButton; +class GuiListbox; +class GuiPanel; + +// Button/listbox combo to manage docking state and select a docking target. +class GuiDockingButton : public GuiElement { public: GuiDockingButton(GuiContainer* owner, string id); virtual void onUpdate() override; - virtual void onDraw(sp::RenderTarget& target) override; private: - void click(); + static constexpr float item_height = 50.0f; - sp::ecs::Entity findDockingTarget(); -}; + GuiPanel* background_panel; + GuiButton* action_button; + GuiListbox* target_list; + std::vector dock_targets; + bool expanded = false; -#endif//DOCKING_BUTTON_H + std::vector findDockingTargets(); +};