Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 129 additions & 33 deletions qtfred/src/mission/dialogs/BriefingEditorDialogModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include "object/object.h"
#include "mission/missiongrid.h"

#include <algorithm>
#include <functional>

#include <QMessageBox>

namespace fso::fred::dialogs {
Expand Down Expand Up @@ -51,6 +54,27 @@ void copyBriefingData(briefing& dst, const briefing& src)
copyStageData(dst.stages[i], src.stages[i]);
}
}

// Returns the value that `get` yields for every icon in `sel`, or `sentinel` if they diverge (or the
// selection is empty). Used to blank a field in the UI when a multi-icon selection disagrees on it.
template <typename T, typename Fn>
T common_icon_value(const brief_stage& s, const SCP_vector<int>& sel, Fn get, T sentinel)
{
bool first = true;
T common = sentinel;
for (int idx : sel) {
if (idx < 0 || idx >= s.num_icons)
continue;
T v = get(s.icons[idx]);
if (first) {
common = v;
first = false;
} else if (!(v == common)) {
return sentinel;
}
}
return common;
}
} // namespace

BriefingEditorDialogModel::BriefingEditorDialogModel(QObject* parent, EditorViewport* viewport)
Expand Down Expand Up @@ -772,6 +796,63 @@ void BriefingEditorDialogModel::setIconPosition(const vec3d& pos)
applyToSelectedIconsCurrentAndForward([&](brief_icon& ic) { modify(ic.pos, pos); });
}

void BriefingEditorDialogModel::nudgeSelectedIcons(const vec3d& worldDelta)
{
applyToSelectedIconsCurrentAndForward([&](brief_icon& ic) {
vec3d p = ic.pos;
vm_vec_add2(&p, &worldDelta);
modify(ic.pos, p);
});
}

void BriefingEditorDialogModel::beginIconDrag()
{
_iconDragBaseline.clear();

auto& briefing = _wipBriefings[_currentTeam];
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
return;

auto& stage = briefing.stages[_currentStage];
for (int idx : getEffectiveSelection(stage)) {
if (valid_icon_index(stage, idx)) {
_iconDragBaseline.push_back({idx, stage.icons[idx].id, stage.icons[idx].pos});
}
}
}

void BriefingEditorDialogModel::dragSelectedIconsBy(const vec3d& worldDelta)
{
auto& briefing = _wipBriefings[_currentTeam];
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
return;

auto& stage = briefing.stages[_currentStage];
for (const auto& base : _iconDragBaseline) {
if (!valid_icon_index(stage, base.index))
continue;

vec3d target = base.pos;
vm_vec_add2(&target, &worldDelta);
modify(stage.icons[base.index].pos, target);

// Match setIconPosition()'s forward propagation: unless editing locally, place same-id icons in
// later stages at the same position so the icon stays put across the briefing.
if (!_changeLocally) {
for (int st = _currentStage + 1; st < briefing.num_stages; ++st) {
auto& stg = briefing.stages[st];
for (int i = 0; i < stg.num_icons; ++i) {
if (stg.icons[i].id == base.id) {
modify(stg.icons[i].pos, target);
}
}
}
}
}

set_modified();
}

int BriefingEditorDialogModel::getIconId() const
{
const auto& b = _wipBriefings[_currentTeam];
Expand Down Expand Up @@ -856,10 +937,9 @@ SCP_string BriefingEditorDialogModel::getIconLabel() const
return {};

const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
return {};

return s.icons[_currentIcon].label;
// Blank when the selection diverges on the label.
return common_icon_value<SCP_string>(
s, getEffectiveSelection(s), [](const brief_icon& ic) { return SCP_string(ic.label); }, {});
}

void BriefingEditorDialogModel::setIconLabel(const SCP_string& text)
Expand All @@ -878,10 +958,9 @@ SCP_string BriefingEditorDialogModel::getIconCloseupLabel() const
return {};

const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
return {};

return s.icons[_currentIcon].closeup_label;
// Blank when the selection diverges on the closeup label.
return common_icon_value<SCP_string>(
s, getEffectiveSelection(s), [](const brief_icon& ic) { return SCP_string(ic.closeup_label); }, {});
}

void BriefingEditorDialogModel::setIconCloseupLabel(const SCP_string& text)
Expand All @@ -899,9 +978,8 @@ int BriefingEditorDialogModel::getIconTypeIndex() const
if (b.num_stages <= 0 || _currentStage < 0 || _currentStage >= b.num_stages)
return -1;
const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
return -1;
return s.icons[_currentIcon].type; // 0..MIN_BRIEF_ICONS-1
// -1 (blank combo) when the selection diverges. type is 0..MIN_BRIEF_ICONS-1, so -1 is unambiguous.
return common_icon_value<int>(s, getEffectiveSelection(s), [](const brief_icon& ic) { return ic.type; }, -1);
}

void BriefingEditorDialogModel::setIconTypeIndex(int idx)
Expand All @@ -915,9 +993,9 @@ int BriefingEditorDialogModel::getIconShipTypeIndex() const
if (b.num_stages <= 0 || _currentStage < 0 || _currentStage >= b.num_stages)
return -1;
const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
return -1;
return s.icons[_currentIcon].ship_class; // may be -1 for unset depending on icon type
// -1 (blank combo) when the selection diverges; ship_class is also -1 when a single icon is unset,
// which likewise blanks the combo, so both cases collapse to the same display.
return common_icon_value<int>(s, getEffectiveSelection(s), [](const brief_icon& ic) { return ic.ship_class; }, -1);
}

void BriefingEditorDialogModel::setIconShipTypeIndex(int idx)
Expand All @@ -931,9 +1009,8 @@ int BriefingEditorDialogModel::getIconTeamIndex() const
if (b.num_stages <= 0 || _currentStage < 0 || _currentStage >= b.num_stages)
return -1;
const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
return -1;
return s.icons[_currentIcon].team;
// -1 (blank combo) when the selection diverges on the IFF team.
return common_icon_value<int>(s, getEffectiveSelection(s), [](const brief_icon& ic) { return ic.team; }, -1);
}

void BriefingEditorDialogModel::setIconTeamIndex(int idx)
Expand All @@ -947,10 +1024,11 @@ float BriefingEditorDialogModel::getIconScaleFactor() const
if (b.num_stages <= 0 || _currentStage < 0 || _currentStage >= b.num_stages)
return 1.0f;
const auto& s = b.stages[_currentStage];
if (_currentIcon < 0 || _currentIcon >= s.num_icons)
const auto sel = getEffectiveSelection(s);
if (sel.empty())
return 1.0f;

return s.icons[_currentIcon].scale_factor;
// Negative sentinel (scale is always positive) tells the UI to blank the field on divergence.
return common_icon_value<float>(s, sel, [](const brief_icon& ic) { return ic.scale_factor; }, -1.0f);
}

void BriefingEditorDialogModel::setIconScaleFactor(float factor)
Expand Down Expand Up @@ -1295,22 +1373,17 @@ void BriefingEditorDialogModel::makeIcon(const SCP_string& label, int typeIndex,
set_modified();
}

void BriefingEditorDialogModel::deleteCurrentIcon()
namespace {
// Remove one icon from a stage: drop any lines referencing it, reindex the remaining line endpoints,
// and shift the icons down to fill the gap.
void remove_briefing_icon(brief_stage& s, int del)
{
auto& briefing = _wipBriefings[_currentTeam];
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
return;

auto& s = briefing.stages[_currentStage];
const int del = _currentIcon;
if (del < 0 || del >= s.num_icons)
return;

// Remove any lines that reference the icon being deleted
for (int i = s.num_lines - 1; i >= 0; --i) {
const int a = s.lines[i].start_icon;
const int b = s.lines[i].end_icon;
if (a == del || b == del) {
if (s.lines[i].start_icon == del || s.lines[i].end_icon == del) {
for (int k = i; k + 1 < s.num_lines; ++k) {
s.lines[k] = s.lines[k + 1];
}
Expand All @@ -1331,12 +1404,35 @@ void BriefingEditorDialogModel::deleteCurrentIcon()
s.icons[i] = s.icons[i + 1];
}
--s.num_icons;
}
} // namespace

void BriefingEditorDialogModel::deleteSelectedIcons()
{
auto& briefing = _wipBriefings[_currentTeam];
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
return;

auto& s = briefing.stages[_currentStage];

// Delete in descending index order so each removal doesn't shift the indices still to be deleted.
SCP_vector<int> indices(_lineSelection.begin(), _lineSelection.end());
std::sort(indices.begin(), indices.end(), std::greater<>());
indices.erase(std::unique(indices.begin(), indices.end()), indices.end());

bool removedAny = false;
for (int idx : indices) {
if (idx >= 0 && idx < s.num_icons) {
remove_briefing_icon(s, idx);
removedAny = true;
}
}

// Update selection
_lineSelection.clear();
_currentIcon = -1;

set_modified();
if (removedAny) {
set_modified();
}
}

void BriefingEditorDialogModel::propagateCurrentIconForward()
Expand Down
15 changes: 14 additions & 1 deletion qtfred/src/mission/dialogs/BriefingEditorDialogModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class BriefingEditorDialogModel : public AbstractDialogModel {
void setCurrentIconIndex(int idx);
vec3d getIconPosition() const;
void setIconPosition(const vec3d& pos);
void nudgeSelectedIcons(const vec3d& worldDelta); // moves the selected icon(s) by a world offset
// Relative multi-drag: snapshot the selected icons' positions, then move each by a world offset from
// its snapshot so a multi-selection keeps its relative layout instead of collapsing onto the cursor.
void beginIconDrag();
void dragSelectedIconsBy(const vec3d& worldDelta);
int getIconId() const;
// returns false if the requested id was rejected (e.g. it collides with another icon)
bool setIconId(int id);
Expand Down Expand Up @@ -109,7 +114,7 @@ class BriefingEditorDialogModel : public AbstractDialogModel {
void setIconUseCargo(bool enabled);

void makeIcon(const SCP_string& label, int typeIndex, int teamIndex, int shipClassIndex);
void deleteCurrentIcon();
void deleteSelectedIcons(); // deletes every icon in the line selection on the current stage
void propagateCurrentIconForward();

int getBriefingMusicIndex() const;
Expand Down Expand Up @@ -165,6 +170,14 @@ class BriefingEditorDialogModel : public AbstractDialogModel {

SCP_vector<int> _lineSelection;
bool _changeLocally = false;

// Snapshot taken at drag start so a relative multi-drag can offset each icon from its own origin.
struct IconDragEntry {
int index;
int id;
vec3d pos;
};
SCP_vector<IconDragEntry> _iconDragBaseline;
};

} // namespace fso::fred::dialogs
Loading
Loading