text: /*
   * PatternCursor.h
   * ---------------
   * Purpose: Class for storing pattern cursor information.
   * Notes  : (currently none)
   * Authors: OpenMPT Devs
   * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
   */
  
  
  #pragma once
  
  #include "openmpt/all/BuildSettings.hpp"
  
  #include "../soundlib/Snd_defs.h"
  
  OPENMPT_NAMESPACE_BEGIN
  
  class PatternCursor
  {
  	friend class PatternRect;
  
  public:
  
  	enum Columns
  	{
  		firstColumn = 0,
  		noteColumn = firstColumn,
  		instrColumn,
  		volumeColumn,
  		effectColumn,
  		paramColumn,
  		lastColumn = paramColumn,
  	};
  
  protected:
  	// Pattern cursor structure (MSB to LSB):
  	// | 16 bits - row | 13 bits - channel | 3 bits - channel component |
  	// As you can see, the highest 16 bits contain a row index.
  	// It is followed by a channel index, which is 13 bits wide.
  	// The lowest 3 bits are used for addressing the components of a channel.
  	// They are *not* used as a bit set, but treated as one of the Columns enum entries that can be found above.
  	uint32 cursor;
  
  	static_assert(MAX_BASECHANNELS <= 0x1FFF, "Check: Channel index in class PatternCursor is only 13 bits wide!");
  	static_assert(MAX_PATTERN_ROWS <= 0xFFFF, "Check: Row index in class PatternCursor is only 16 bits wide!");
  
  public:
  
  	// Construct cursor from given coordinates.
  	PatternCursor(ROWINDEX row = 0, CHANNELINDEX channel = 0, Columns column = firstColumn)
  	{
  		Set(row, channel, column);
  	};
  
  	// Construct cursor from a given row and another cursor's horizontal position (channel + column).
  	PatternCursor(ROWINDEX row, const PatternCursor &other)
  	{
  		Set(row, other);
  	};
  
  	// Get the pattern row the cursor is located in.
  	ROWINDEX GetRow() const
  	{
  		return static_cast<ROWINDEX>(cursor >> 16);
  	};
  
  	// Get the pattern channel the cursor is located in.
  	CHANNELINDEX GetChannel() const
  	{
  		return static_cast<CHANNELINDEX>((cursor & 0xFFFF) >> 3);
  	};
  
  	// Get the pattern channel column the cursor is located in.
  	Columns GetColumnType() const
  	{
  		return static_cast<Columns>((cursor & 0x07));
  	};
  
  	// Set the cursor to given coordinates.
  	void Set(ROWINDEX row, CHANNELINDEX channel, Columns column = firstColumn)
  	{
  		cursor = (row << 16) | ((channel << 3) & 0x1FFF) | (column & 0x07);
  	};
  
  	// Set the cursor to the same position as another curosr.
  	void Set(const PatternCursor &other)
  	{
  		*this = other;
  	};
  
  	// Set the cursor to a given row and another cursor's horizontal position (channel + column).
  	void Set(ROWINDEX row, const PatternCursor &other)
  	{
  		cursor = (row << 16) | (other.cursor & 0xFFFF);
  	};
  
  	// Only update the row of a cursor.
  	void SetRow(ROWINDEX row)
  	{
  		Set(row, *this);
  	};
  
  	// Only update the horizontal position of a cursor.
  	void SetColumn(CHANNELINDEX chn, Columns col)
  	{
  		Set(GetRow(), chn, col);
  	};
  
  	// Move the cursor relatively.
  	void Move(int rows, int channels, int columns)
  	{
  		int row = std::max(int(0), static_cast<int>(GetRow()) + rows);
  		int chn = static_cast<int>(GetChannel()) + channels;
  		int col = static_cast<int>(GetColumnType() + columns);
  
  		// Boundary checking
  		if(chn < 0)
  		{
  			// Moving too far left
  			chn = 0;
  			col = firstColumn;
  		}
  
  		if(col < firstColumn)
  		{
  			// Extending beyond first column
  			col = lastColumn;
  			chn--;
  		} else if(col > lastColumn)
  		{
  			// Extending beyond last column
  			col = firstColumn;
  			chn++;
  		}
  
  		Set(static_cast<ROWINDEX>(row), static_cast<CHANNELINDEX>(chn), static_cast<Columns>(col));
  	}
  
  
  	// Remove the channel column type from the cursor position.
  	void RemoveColType()
  	{
  		cursor &= ~0x07;
  	}
  
  	// Compare the row of two cursors.
  	// Returns -1 if this cursor is above of the other cursor,
  	// 1 if it is below of the other cursor and 0 if they are at the same vertical position.
  	int CompareRow(const PatternCursor &other) const
  	{
  		if(GetRow() < other.GetRow()) return -1;
  		if(GetRow() > other.GetRow()) return 1;
  		return 0;
  	}
  
  	// Compare the column (channel + sub column) of two cursors.
  	// Returns -1 if this cursor is left of the other cursor,
  	// 1 if it is right of the other cursor and 0 if they are at the same horizontal position.
  	int CompareColumn(const PatternCursor &other) const
  	{
  		if((cursor & 0xFFFF) < (other.cursor & 0xFFFF)) return -1;
  		if((cursor & 0xFFFF) > (other.cursor & 0xFFFF)) return 1;
  		return 0;
  	}
  
  
  	// Check whether the cursor is placed on the first channel and first column.
  	bool IsInFirstColumn() const
  	{
  		return (cursor & 0xFFFF) == 0;
  	}
  
  
  	// Ensure that the point lies within a given pattern size.
  	void Sanitize(ROWINDEX maxRows, CHANNELINDEX maxChans)
  	{
  		ROWINDEX row = std::min(GetRow(), static_cast<ROWINDEX>(maxRows - 1));
  		CHANNELINDEX chn = GetChannel();
  		Columns col = GetColumnType();
  
  		if(chn >= maxChans)
  		{
  			chn = maxChans - 1;
  			col = lastColumn;
  		} else if(col > lastColumn)
  		{
  			col = lastColumn;
  		}
  		Set(row, chn, col);
  	};
  
  	bool operator == (const PatternCursor &other) const
  	{
  		return cursor == other.cursor;
  	}
  
  	bool operator != (const PatternCursor &other) const
  	{
  		return !(*this == other);
  	}
  
  };
  
  
  class PatternRect
  {
  protected:
  	PatternCursor upperLeft, lowerRight;
  
  public:
  
  	// Construct selection from two pattern cursors.
  	PatternRect(const PatternCursor &p1, const PatternCursor &p2)
  	{
  		if(p1.CompareColumn(p2) <= 0)
  		{
  			upperLeft.cursor = (p1.cursor & 0xFFFF);
  			lowerRight.cursor = (p2.cursor & 0xFFFF);
  		} else
  		{
  			upperLeft.cursor = (p2.cursor & 0xFFFF);
  			lowerRight.cursor = (p1.cursor & 0xFFFF);
  		}
  
  		if(p1.CompareRow(p2) <= 0)
  		{
  			upperLeft.cursor |= (p1.GetRow() << 16);
  			lowerRight.cursor |= (p2.GetRow() << 16);
  		} else
  		{
  			upperLeft.cursor |= (p2.GetRow() << 16);
  			lowerRight.cursor |= (p1.GetRow() << 16);
  		}
  	};
  
  	// Construct empty rect.
  	PatternRect()
  	{
  		upperLeft.Set(0);
  		lowerRight.Set(0);
  	}
  
  	// Return upper-left corner of selection.
  	PatternCursor GetUpperLeft() const
  	{
  		return upperLeft;
  	};
  
  	// Return lower-right corner of selection.
  	PatternCursor GetLowerRight() const
  	{
  		return lowerRight;
  	};
  
  	// Check if a given point is within the horizontal boundaries of the rect
  	bool ContainsHorizontal(const PatternCursor &point) const
  	{
  		return point.CompareColumn(upperLeft) >= 0 && point.CompareColumn(lowerRight) <= 0;
  	}
  
  	// Check if a given point is within the vertical boundaries of the rect
  	bool ContainsVertical(const PatternCursor &point) const
  	{
  		return point.CompareRow(upperLeft) >= 0 && point.CompareRow(lowerRight) <= 0;
  	}
  
  	// Check if a given point is within the rect
  	bool Contains(const PatternCursor &point) const
  	{
  		return ContainsHorizontal(point) && ContainsVertical(point);
  	}
  
  	// Ensure that the selection doesn't exceed a given pattern size.
  	void Sanitize(ROWINDEX maxRows, CHANNELINDEX maxChans)
  	{
  		upperLeft.Sanitize(maxRows, maxChans);
  		lowerRight.Sanitize(maxRows, maxChans);
  	};
  
  
  	// Get first row of selection
  	ROWINDEX GetStartRow() const
  	{
  		ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
  		return upperLeft.GetRow();
  	}
  
  	// Get last row of selection
  	ROWINDEX GetEndRow() const
  	{
  		ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
  		return lowerRight.GetRow();
  	}
  
  	// Get first channel of selection
  	CHANNELINDEX GetStartChannel() const
  	{
  		ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
  		return upperLeft.GetChannel();
  	}
  
  	// Get last channel of selection
  	CHANNELINDEX GetEndChannel() const
  	{
  		ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
  		return lowerRight.GetChannel();
  	}
  
  	PatternCursor::Columns GetStartColumn() const
  	{
  		ASSERT((upperLeft.cursor & 0xFFFF) <= (lowerRight.cursor & 0xFFFF));
  		return upperLeft.GetColumnType();
  	}
  
  	PatternCursor::Columns GetEndColumn() const
  	{
  		ASSERT((upperLeft.cursor & 0xFFFF) <= (lowerRight.cursor & 0xFFFF));
  		return lowerRight.GetColumnType();
  	}
  
  	// Create a bitset of the selected columns of a channel. If a column is selected, the corresponding bit is set.
  	// Example: If the first and second column of the channel are selected, the bits 00000011 would be returned.
  	uint8 GetSelectionBits(CHANNELINDEX chn) const
  	{
  		const CHANNELINDEX startChn = GetStartChannel(), endChn = GetEndChannel();
  		uint8 bits = 0;
  
  		if(chn >= startChn && chn <= endChn)
  		{
  			// All columns could be selected (unless this is the first or last channel).
  			bits = uint8_max;
  
  			if(chn == startChn)
  			{
  				// First channel: Remove columns left of the start column type.
  				bits <<= GetUpperLeft().GetColumnType();
  			}
  			if(chn == endChn)
  			{
  				// Last channel: Remove columns right of the end column type.
  				bits &= (2 << GetLowerRight().GetColumnType()) - 1;
  			}
  		}
  		return (bits & 0x1F);
  	}
  
  	// Get number of rows in selection
  	ROWINDEX GetNumRows() const
  	{
  		ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
  		return 1 + GetEndRow() - GetStartRow();
  	}
  
  	// Get number of channels in selection
  	CHANNELINDEX GetNumChannels() const
  	{
  		ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
  		return 1 + GetEndChannel() - GetStartChannel();
  	}
  
  	bool operator == (const PatternRect &other) const
  	{
  		return upperLeft == other.upperLeft && lowerRight == other.lowerRight;
  	}
  
  };
  
  OPENMPT_NAMESPACE_END
  