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
78 changes: 59 additions & 19 deletions src/display/Connections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,14 +626,6 @@ void MapCanvas::paintNearbyConnectionPoints()
const bool isSelection = m_canvasMouseMode == CanvasMouseModeEnum::SELECT_CONNECTIONS;
using CD = ConnectionSelection::ConnectionDescriptor;

static const auto allExits = std::invoke([]() -> ExitDirFlags {
ExitDirFlags tmp;
for (const ExitDirEnum dir : ALL_EXITS7) {
tmp |= dir;
}
return tmp;
});

std::vector<ColorVert> points;
const auto addPoint = [isSelection, &points](const Coordinate roomCoord,
const RoomHandle &room,
Expand All @@ -652,6 +644,8 @@ void MapCanvas::paintNearbyConnectionPoints()
}
}

// Cyan means "not yet a complete selection"; green is reserved for the
// anchor/ghost-line once both endpoints form a valid connection (see below).
points.emplace_back(Colors::cyan, roomCoord.to_vec3() + getConnectionOffset(dir));
};
const auto addPoints =
Expand All @@ -669,7 +663,19 @@ void MapCanvas::paintNearbyConnectionPoints()
continue;
}

ExitDirFlags dirs = isSelection ? m_data.getExitDirections(roomCoord) : allExits;
// SELECT_CONNECTIONS only offers directions that already have an exit to pick
// from. CREATE_CONNECTIONS should only offer directions that don't already have
// an outgoing connection, since clicking one that does would silently overwrite it.
ExitDirFlags dirs;
if (isSelection) {
dirs = m_data.getExitDirections(roomCoord);
} else {
for (const ExitDirEnum dir : ALL_EXITS7) {
if (isNESWUD(dir) && room.getExit(dir).outIsEmpty()) {
dirs |= dir;
}
}
}
if (optFirst) {
dirs |= ExitDirEnum::UNKNOWN;
}
Expand All @@ -682,13 +688,19 @@ void MapCanvas::paintNearbyConnectionPoints()
};

// FIXME: This doesn't show dots for red connections.
if (m_connectionSelection != nullptr
if (hasConnectionInteraction() && m_connectionSelection != nullptr
&& (m_connectionSelection->isFirstValid() || m_connectionSelection->isSecondValid())) {
const CD valid = m_connectionSelection->isFirstValid() ? m_connectionSelection->getFirst()
: m_connectionSelection->getSecond();
const Coordinate c = valid.room.getPosition();
const glm::vec3 pos = c.to_vec3();
points.emplace_back(Colors::cyan, pos + getConnectionOffset(valid.direction));
// The anchor point is always Green so it stands out among the Cyan candidates.
// A freshly chained CREATE_CONNECTIONS anchor has direction NONE (no direction
// picked yet), which getConnectionOffset() can't handle, so skip drawing it
// until a real direction is set.
if (isNESWUD(valid.direction) || valid.direction == ExitDirEnum::UNKNOWN) {
points.emplace_back(Colors::green, pos + getConnectionOffset(valid.direction));
}

addPoints(MouseSel{Coordinate2f{pos.x, pos.y}, c.z}, valid);
addPoints(m_sel1, valid);
Expand All @@ -698,7 +710,10 @@ void MapCanvas::paintNearbyConnectionPoints()
addPoints(m_sel2, std::nullopt);
}

getOpenGL().renderPoints(points, GLRenderState().withPointSize(VALID_CONNECTION_POINT_SIZE));
getOpenGL().renderPoints(points,
GLRenderState()
.withPointSize(VALID_CONNECTION_POINT_SIZE + 2.f)
.withDepthFunction(std::nullopt));
}

void MapCanvas::paintSelectedConnection()
Expand All @@ -714,14 +729,26 @@ void MapCanvas::paintSelectedConnection()
ConnectionSelection &sel = deref(m_connectionSelection);

const ConnectionSelection::ConnectionDescriptor &first = sel.getFirst();
// A freshly chained CREATE_CONNECTIONS anchor has direction NONE (no direction
// picked yet); there's no meaningful ghost line to draw until the user hovers
// over a direction, and getConnectionOffset() can't handle NONE anyway.
if (!isNESWUD(first.direction) && first.direction != ExitDirEnum::UNKNOWN) {
return;
}
const auto pos1 = getPosition(first);
// REVISIT: How about not dashed lines to the nearest possible connections
// if the second isn't valid?

const auto optPos2 = std::invoke([this, &sel]() -> std::optional<glm::vec3> {
if (sel.isSecondValid()) {
return getPosition(sel.getSecond());
} else if (hasSel2()) {
return getSel2().to_vec3();
// Snapping logic for ghost line
const auto mouse = getSel2();
const auto dir = ConnectionSelection::computeDirection(mouse.pos);
const auto room = m_data.findRoomHandle(mouse.getCoordinate());
if (room.exists()) {
return room.getPosition().to_vec3() + getConnectionOffset(dir);
}
return mouse.to_vec3();
} else {
return std::nullopt;
}
Expand All @@ -734,17 +761,30 @@ void MapCanvas::paintSelectedConnection()
const glm::vec3 pos2 = optPos2.value();

auto &gl = getOpenGL();
const auto rs = GLRenderState().withColor(Colors::red);

// Green: the current anchor+target form a complete, valid connection.
// Red: hovering a real room, but this target would NOT be a valid connection.
// Cyan: no second room hovered yet (translucent -- nothing to judge yet).
const bool isComplete = (m_canvasMouseMode == CanvasMouseModeEnum::SELECT_CONNECTIONS)
? sel.isCompleteExisting()
: sel.isCompleteNew();
const Color ghostColor = isComplete ? Colors::green
: sel.isSecondValid() ? Colors::red
: Colors::cyan.withAlpha(0.8f);
const auto rs = GLRenderState()
.withColor(ghostColor)
.withBlend(BlendModeEnum::TRANSPARENCY)
.withDepthFunction(std::nullopt);

{
std::vector<ColorVert> verts;
mmgl::generateLineQuadsSafe(verts, pos1, pos2, CONNECTION_LINE_WIDTH, Colors::red);
mmgl::generateLineQuadsSafe(verts, pos1, pos2, CONNECTION_LINE_WIDTH, ghostColor);
gl.renderColoredQuads(verts, rs);
}

std::vector<ColorVert> points;
points.emplace_back(Colors::red, pos1);
points.emplace_back(Colors::red, pos2);
points.emplace_back(ghostColor, pos1);
points.emplace_back(ghostColor, pos2);
gl.renderPoints(points, rs.withPointSize(NEW_CONNECTION_POINT_SIZE));
}

Expand Down
6 changes: 0 additions & 6 deletions src/display/MapCanvasData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ const MMapper::Array<RoomTintEnum, NUM_ROOM_TINTS> &getAllRoomTints()
return all_room_tints;
}

MapCanvasInputState::MapCanvasInputState(PrespammedPath &prespammedPath)
: m_prespammedPath{prespammedPath}
{}

MapCanvasInputState::~MapCanvasInputState() = default;

MapCanvasViewport::~MapCanvasViewport() = default;

const glm::mat4 &MapCanvasViewport::getViewProj() const
Expand Down
68 changes: 56 additions & 12 deletions src/display/MapCanvasData.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "prespammedpath.h"

#include <cassert>
#include <chrono>
#include <map>
#include <memory>
#include <optional>
Expand Down Expand Up @@ -228,11 +229,12 @@ struct NODISCARD AltDragState
QCursor originalCursor;
};

struct NODISCARD DragState
struct NODISCARD PanningState
{
glm::vec3 startWorldPos;
glm::vec2 startScroll;
glm::mat4 startViewProj;
glm::vec2 startScreenPos;
};

struct NODISCARD PinchState
Expand All @@ -241,6 +243,15 @@ struct NODISCARD PinchState
float lastFactor = 1.f;
};

// Tracks a single-finger touch to detect tap-and-hold, the touch equivalent of a
// right-click, since touchscreens have no secondary button to cancel a selection with.
struct NODISCARD TouchHoldState
{
glm::vec2 startPos;
std::chrono::steady_clock::time_point startTime;
bool fired = false;
};

struct NODISCARD MagnificationState
{
float lastValue = 1.f;
Expand All @@ -257,31 +268,49 @@ struct NODISCARD InfomarkSelectionMove
Coordinate2f pos;
};

struct NODISCARD AreaSelectionState
{};
struct NODISCARD AreaSelectionInteraction
{
MouseSel anchorPoint;
bool isSelecting = false;
};

struct NODISCARD ConnectionInteraction
{
// No extra state needed here yet as we use m_connectionSelection,
// but having the struct helps identify the active tool.
};

struct NODISCARD MapCanvasInputState
{
CanvasMouseModeEnum m_canvasMouseMode = CanvasMouseModeEnum::MOVE;

bool m_mouseRightPressed = false;
bool m_mouseLeftPressed = false;
bool m_mouseMiddlePressed = false;
bool m_altPressed = false;
bool m_ctrlPressed = false;
bool m_spacebarPressed = false;

// mouse selection
std::optional<MouseSel> m_sel1;
std::optional<MouseSel> m_sel2;

// Mutually exclusive mouse-based interactions.
std::optional<
std::variant<AltDragState, DragState, RoomSelMove, InfomarkSelectionMove, AreaSelectionState>>
// Panning is independent of tool-based interactions.
std::optional<PanningState> m_panningState;

// Mutually exclusive mouse-based tool interactions.
std::optional<std::variant<AltDragState,
RoomSelMove,
InfomarkSelectionMove,
AreaSelectionInteraction,
ConnectionInteraction>>
m_activeInteraction;

// Gesture states (pinch, magnification) can occur concurrently with mouse interactions
// and each other, so they are managed independently.
std::optional<PinchState> m_pinchState;
std::optional<MagnificationState> m_magnificationState;
std::optional<TouchHoldState> m_touchHoldState;

SharedRoomSelection m_roomSelection;

Expand All @@ -292,8 +321,10 @@ struct NODISCARD MapCanvasInputState
PrespammedPath &m_prespammedPath;

public:
explicit MapCanvasInputState(PrespammedPath &prespammedPath);
~MapCanvasInputState();
explicit MapCanvasInputState(PrespammedPath &prespammedPath)
: m_prespammedPath{prespammedPath}
{}
~MapCanvasInputState() = default;

public:
NODISCARD static MouseSel getMouseSel(const std::optional<MouseSel> &x)
Expand Down Expand Up @@ -344,13 +375,22 @@ struct NODISCARD MapCanvasInputState
{
beginInteraction(AltDragState{pos, cursor});
}
void beginDrag(const glm::vec3 worldPos, const glm::vec2 scroll, const glm::mat4 &viewProj)
void beginPanning(const glm::vec3 worldPos,
const glm::vec2 scroll,
const glm::mat4 &viewProj,
const glm::vec2 screenPos)
{
beginInteraction(DragState{worldPos, scroll, viewProj});
m_panningState.emplace(PanningState{worldPos, scroll, viewProj, screenPos});
}
void endPanning() { m_panningState.reset(); }

void beginRoomMove() { beginInteraction(RoomSelMove{}); }
void beginInfomarkMove() { beginInteraction(InfomarkSelectionMove{}); }
void beginAreaSelection() { beginInteraction(AreaSelectionState{}); }
void beginAreaSelection(const MouseSel &anchor)
{
beginInteraction(AreaSelectionInteraction{anchor, true});
}
void beginConnectionInteraction() { beginInteraction(ConnectionInteraction{}); }
void endInteraction() { m_activeInteraction.reset(); }

public:
Expand Down Expand Up @@ -385,6 +425,10 @@ struct NODISCARD MapCanvasInputState
}
NODISCARD bool hasAreaSelection() const
{
return getInteraction<AreaSelectionState>() != nullptr;
return getInteraction<AreaSelectionInteraction>() != nullptr;
}
NODISCARD bool hasConnectionInteraction() const
{
return getInteraction<ConnectionInteraction>() != nullptr;
}
};
9 changes: 7 additions & 2 deletions src/display/RoomSelections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,17 @@ void MapCanvas::paintSelectedRoom(RoomSelFakeGL &gl, const RawRoom &room)

void MapCanvas::paintSelectedRooms()
{
RoomSelFakeGL gl;

// NOTE: The connection-building anchor room is intentionally not drawn with the
// room-selection texture here; it already has its own distinct highlight (green
// anchor point + ghost line) painted in paintSelectedConnection(), so reusing the
// room-selection graphic would make it look like an actual room selection.

if (!m_roomSelection || m_roomSelection->empty()) {
return;
}

RoomSelFakeGL gl;

for (const RoomId id : deref(m_roomSelection)) {
if (const auto room = m_data.findRoomHandle(id)) {
gl.resetMatrix();
Expand Down
33 changes: 0 additions & 33 deletions src/display/connectionselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,39 +50,6 @@ bool ConnectionSelection::isValid() const
return true;
}

// \NNNNNNNN/
// W\NNNN--/E
// WW\NN|UU|E
// WWW\-|UU|E
// WWW|CC--EE
// WW--CC|EEE
// W|DD|-\EEE
// W|DD|SS\EE
// W/--SSSS\E
// /SSSSSSSS\.
ExitDirEnum ConnectionSelection::computeDirection(const Coordinate2f &c)
{
const glm::vec2 pos = glm::fract(c.to_vec2());
const glm::vec2 upCenter{0.75f, 0.75f};
const glm::vec2 downCenter{0.25f, 0.25f};
const glm::vec2 actualCenter{0.5f, 0.5f};
const float upDownRadius = 0.15f;
const float centerRadius = 0.15f;

if (glm::distance(pos, upCenter) <= upDownRadius) {
return ExitDirEnum::UP;
} else if (glm::distance(pos, downCenter) <= upDownRadius) {
return ExitDirEnum::DOWN;
} else if (glm::distance(pos, actualCenter) <= centerRadius) {
return ExitDirEnum::UNKNOWN;
}

const bool ne = pos.x >= 1.f - pos.y;
const bool nw = pos.x <= pos.y;
return ne ? (nw ? ExitDirEnum::NORTH : ExitDirEnum::EAST)
: (nw ? ExitDirEnum::WEST : ExitDirEnum::SOUTH);
}

void ConnectionSelection::setFirst(const RoomId id, const ExitDirEnum dir)
{
m_first = true;
Expand Down
Loading
Loading