Skip to content

Commit

Permalink
Added first version of text popup map events
Browse files Browse the repository at this point in the history
  • Loading branch information
Pyrdacor committed Sep 10, 2020
1 parent 06824b4 commit eec240d
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 15 deletions.
5 changes: 5 additions & 0 deletions Ambermoon.Common/Rect.cs
Expand Up @@ -71,6 +71,11 @@ public static Rect Create(Position center, Size size)
return new Rect(center.X - size.Width / 2, center.Y - size.Height / 2, size.Width, size.Height);
}

public static Rect GetCentered(Rect rect, Rect outerRect)
{
return Create(outerRect.Center, rect.Size);
}

public void Clip(int left, int top, int right, int bottom)
{
if (Left < left)
Expand Down
39 changes: 37 additions & 2 deletions Ambermoon.Core/Game.cs
Expand Up @@ -94,10 +94,26 @@ class TimedGameEvent
PartyMember CurrentCaster { get; set; } = null;
public Map Map => !ingame ? null : is3D ? renderMap3D?.Map : renderMap2D?.Map;
readonly bool[] keys = new bool[Enum.GetValues<Key>().Length];
bool inputEnable = true;
/// <summary>
/// The 3x3 buttons will always be enabled!
/// </summary>
public bool InputEnable { get; set; } = true;
public bool InputEnable
{
get => inputEnable;
set
{
if (inputEnable == value)
return;

inputEnable = value;
clickMoveActive = false;
UntrapMouse();

if (!inputEnable)
ResetMoveKeys();
}
}
bool leftMouseDown = false;
bool clickMoveActive = false;
Rect trapMouseArea = null;
Expand Down Expand Up @@ -856,7 +872,7 @@ void UpdateCursor(Position cursorPosition, MouseButtons buttons)
{
if (layout.PopupActive)
{
var cursorType = CursorType.Sword;
var cursorType = layout.PopupClickToClose ? CursorType.Click : CursorType.Sword;
layout.Hover(renderView.ScreenToGame(cursorPosition), ref cursorType);
CursorType = cursorType;
}
Expand Down Expand Up @@ -1262,6 +1278,25 @@ internal void ShowChest(ChestMapEvent chestMapEvent)
});
}

internal void ShowTextPopup(PopupTextEvent popupTextEvent)
{
if (popupTextEvent.HasImage)
{
// Those always use a custom layout
}
else
{
// Simple text popup
/*var popup = layout.OpenPopup(mapViewArea.Position, (mapViewArea.Width - 32) / 7, 10, true, true); // TODO: sizes and position
popup.AddText(new Rect(mapViewArea.Position + new Position(16, 16), new Size(mapViewArea.Width - 32, 10 * 7)),
Map.Texts[(int)popupTextEvent.TextIndex], TextColor.Gray);*/
var text = renderView.TextProcessor.ProcessText(Map.Texts[(int)popupTextEvent.TextIndex], nameProvider, dictionary);
layout.OpenTextPopup(text, () => InputEnable = true, true, true);
InputEnable = false;
CursorType = CursorType.Click;
}
}

internal void SetActivePartyMember(int index)
{
var partyMember = GetPartyMember(index);
Expand Down
8 changes: 8 additions & 0 deletions Ambermoon.Core/MapExtensions.cs
Expand Up @@ -43,6 +43,14 @@ public static class MapExtensions
game.ShowChest(chestMapEvent);
break;
}
case MapEventType.PopupText:
{
if (!(mapEvent is PopupTextEvent popupTextEvent))
throw new AmbermoonException(ExceptionScope.Data, "Invalid text popup event.");

game.ShowTextPopup(popupTextEvent);
break;
}
case MapEventType.ChangeTile:
{
if (!(mapEvent is ChangeTileEvent changeTileEvent))
Expand Down
2 changes: 1 addition & 1 deletion Ambermoon.Core/UI/Global.cs
Expand Up @@ -29,6 +29,6 @@ public partial class Global
public static readonly Rect[] ExtendedPartyMemberPortraitAreas = Enumerable.Range(0, 6).Select(index =>
new Rect(15 + index * 48, 0, 48, 36)).ToArray();
public const int GlyphWidth = 6;
public const int GlyphLineHeight = 6;
public const int GlyphLineHeight = 7;
}
}
2 changes: 1 addition & 1 deletion Ambermoon.Core/UI/ItemGrid.cs
Expand Up @@ -409,7 +409,7 @@ public bool Hover(Position position)
hoveredItemName.Text = itemNameText;
hoveredItemName.DisplayLayer = 2;
hoveredItemName.X = Util.Limit(0, position.X - textWidth / 2, Global.VirtualScreenWidth - textWidth);
hoveredItemName.Y = position.Y - Global.GlyphLineHeight - 2;
hoveredItemName.Y = position.Y - Global.GlyphLineHeight - 1;
hoveredItemName.Visible = true;

return true;
Expand Down
31 changes: 30 additions & 1 deletion Ambermoon.Core/UI/Layout.cs
Expand Up @@ -319,6 +319,7 @@ public static DraggedItem FromExternal(ItemGrid itemGrid, int slotIndex, UIItem
Popup activePopup = null;
public bool PopupActive => activePopup != null;
public bool PopupDisableButtons => activePopup?.DisableButtons == true;
public bool PopupClickToClose => activePopup?.CloseOnClick == true;
int buttonGridPage = 0;
uint? ticksPerMovement = null;
internal IRenderView RenderView { get; }
Expand Down Expand Up @@ -482,17 +483,45 @@ void CloseOptionMenu()
game.InputEnable = true;
}

void OpenPopup(Position position, int columns, int rows, bool disableButtons = false, bool closeOnClick = true)
internal Popup OpenPopup(Position position, int columns, int rows, bool disableButtons = false, bool closeOnClick = true)
{
activePopup = new Popup(game, RenderView, position, columns, rows)
{
DisableButtons = disableButtons,
CloseOnClick = closeOnClick
};
return activePopup;
}

internal Popup OpenTextPopup(IText text, Action closeAction, bool disableButtons = false, bool closeOnClick = true)
{
// min text position = 32, 68
// max text size = 256, 112
// max 16 text rows
const int maxTextWidth = 256;
const int maxTextHeight = 112;
var processedText = RenderView.TextProcessor.WrapText(text,
new Rect((Global.VirtualScreenWidth - maxTextWidth) / 2, 0, maxTextWidth, int.MaxValue),
new Size(Global.GlyphWidth, Global.GlyphLineHeight));
var textBounds = new Rect(32, 69, maxTextWidth, Math.Min(processedText.LineCount * Global.GlyphLineHeight, maxTextHeight));
var renderText = RenderView.RenderTextFactory.Create(RenderView.GetLayer(Layer.Text),
processedText, TextColor.Gray, true, textBounds);
int popupColumns = 2 + (textBounds.Width + 15) / 16;
int popupRows = 2 + (textBounds.Height + 15) / 16;
var popupArea = Rect.Create(textBounds.Center, new Size(popupColumns * 16, popupRows * 16));
activePopup = new Popup(game, RenderView, popupArea.Position, popupColumns, popupRows)
{
DisableButtons = disableButtons,
CloseOnClick = closeOnClick
};
activePopup.AddText(renderText);
activePopup.Closed += closeAction;
return activePopup;
}

internal void ClosePopup()
{
activePopup?.OnClosed();
activePopup?.Destroy();
activePopup = null;
}
Expand Down
16 changes: 15 additions & 1 deletion Ambermoon.Core/UI/Popup.cs
Expand Up @@ -4,7 +4,7 @@

namespace Ambermoon.UI
{
class Popup
internal class Popup
{
const byte BaseDisplayLayer = 20;
readonly Game game;
Expand Down Expand Up @@ -69,6 +69,12 @@ void AddBorder(PopupFrame frame, int column, int row)

public bool CloseOnClick { get; set; } = true;
public bool DisableButtons { get; set; } = false;
public event Action Closed;

public void OnClosed()
{
Closed?.Invoke();
}

public void Destroy()
{
Expand Down Expand Up @@ -110,6 +116,14 @@ public IRenderText AddText(Position position, string text, TextColor textColor,
return renderText;
}

public IRenderText AddText(IRenderText renderText, byte displayLayer = 1)
{
renderText.DisplayLayer = (byte)Util.Min(255, BaseDisplayLayer + displayLayer);
renderText.Visible = true;
texts.Add(renderText);
return renderText;
}

public IColoredRect FillArea(Rect area, Color color, byte displayLayer = 1)
{
var filledArea = renderView.ColoredRectFactory.Create(area.Width, area.Height, color,
Expand Down
8 changes: 8 additions & 0 deletions Ambermoon.Data.Common/IText.cs
Expand Up @@ -46,6 +46,14 @@ public interface ITextProcessor
{
IText ProcessText(string text, ITextNameProvider nameProvider, List<string> dictionary);
IText CreateText(string text);
/// <summary>
/// Wraps a given text so it fits into the given bounds.
///
/// Note that the height still can exceed the bound height.
/// In this case the text must be scrolled to view all of it.
/// This only wraps text lines to keep them inside the bound width.
/// </summary>
IText WrapText(IText text, Rect bounds, Size glyphSize);
}

public interface IText
Expand Down
6 changes: 4 additions & 2 deletions Ambermoon.Data.Common/MapEvent.cs
Expand Up @@ -123,12 +123,14 @@ public class PopupTextEvent : MapEvent
/// From event_pix (0-based). 0xff -> no image.
/// </summary>
public uint EventImageIndex { get; set; }
public byte[] Unknown1 { get; set; }
public bool HasImage => EventImageIndex != 0xff;
public byte Unknown1 { get; set; }
public byte[] Unknown2 { get; set; }
public byte[] Unknown3 { get; set; }

public override string ToString()
{
return $"{Type}: Text {TextIndex}, Image {(EventImageIndex == 0xff ? "None" : EventImageIndex.ToString())}, Unknown1 {string.Join(" ", Unknown1.Select(u => u.ToString("x2")))}, Unknown2 {string.Join(" ", Unknown2.Select(u => u.ToString("x2")))}";
return $"{Type}: Text {TextIndex}, Image {(EventImageIndex == 0xff ? "None" : EventImageIndex.ToString())}, Unknown1 {Unknown1}, Unknown2 {string.Join(" ", Unknown2.Select(u => u.ToString("x2")))}, Unknown3 {string.Join(" ", Unknown3.Select(u => u.ToString("x2")))}";
}
}

Expand Down
10 changes: 6 additions & 4 deletions Ambermoon.Data.Legacy/Serialization/MapReader.cs
Expand Up @@ -248,15 +248,17 @@ static MapEvent ParseEvent(IDataReader dataReader)
// 5. byte is the map text index
// 4 unknown bytes
var eventImageIndex = dataReader.ReadByte();
var unknown1 = dataReader.ReadBytes(3);
var unknown1 = dataReader.ReadByte(); // TODO: seen 1 and 3 so far
var unknown2 = dataReader.ReadBytes(2);
var textIndex = dataReader.ReadByte();
var unknown2 = dataReader.ReadBytes(4);
var unknown3 = dataReader.ReadBytes(4);
mapEvent = new PopupTextEvent
{
EventImageIndex = eventImageIndex,
TextIndex = textIndex,
Unknown1 = unknown1,
Unknown2 = unknown2
TextIndex = textIndex,
Unknown2 = unknown2,
Unknown3 = unknown3
};
break;
}
Expand Down
79 changes: 78 additions & 1 deletion Ambermoon.Data.Legacy/Text.cs
Expand Up @@ -3,7 +3,7 @@

namespace Ambermoon.Data.Legacy
{
public class Text : IText
internal class Text : IText
{
public Text(byte[] glyphIndices)
{
Expand Down Expand Up @@ -35,6 +35,13 @@ public Text(byte[] glyphIndices)
MaxLineSize = currentLineSize;
}

public Text(byte[] glyphIndices, int lineCount, int maxLineSize)
{
GlyphIndices = glyphIndices;
LineCount = lineCount;
MaxLineSize = maxLineSize;
}

public byte[] GlyphIndices { get; }
public int LineCount { get; }
public int MaxLineSize { get; }
Expand Down Expand Up @@ -121,6 +128,76 @@ public IText CreateText(string text)
return new Text(text.Select(ch => CharToGlyph(ch, false)).ToArray());
}

public IText WrapText(IText text, Rect bounds, Size glyphSize)
{
int x = bounds.Left;
int y = bounds.Top;
int lastSpaceIndex = -1;
int maxLineWidth = 0;
int height = 0;
var wrappedGlyphs = new List<byte>(text.GlyphIndices.Length);

void NewLine(int newX = 0)
{
if (x > maxLineWidth)
maxLineWidth = x;

lastSpaceIndex = -1;
x = bounds.Left + newX;
y += glyphSize.Height;
height = y;
}

foreach (var glyph in text.GlyphIndices)
{
switch (glyph)
{
case (byte)SpecialGlyph.SoftSpace:
if (wrappedGlyphs.Last() == (byte)SpecialGlyph.NewLine)
continue;
x += glyphSize.Width;
if (x > bounds.Right)
{
wrappedGlyphs.Add((byte)SpecialGlyph.NewLine);
NewLine();
}
else
{
lastSpaceIndex = wrappedGlyphs.Count;
wrappedGlyphs.Add(glyph);
}
break;
case (byte)SpecialGlyph.NewLine:
wrappedGlyphs.Add(glyph);
NewLine();
break;
case (byte)SpecialGlyph.FirstColor:
wrappedGlyphs.Add(glyph);
break;
default:
{
wrappedGlyphs.Add(glyph);
x += glyphSize.Width;
if (x > bounds.Right)
{
if (lastSpaceIndex == -1)
throw new AmbermoonException(ExceptionScope.Data, "Text can not be wrapped inside the given bounds.");

wrappedGlyphs[lastSpaceIndex] = (byte)SpecialGlyph.NewLine;
NewLine((wrappedGlyphs.Count - lastSpaceIndex - 1) * glyphSize.Width);
}
break;
}
}
}

if (wrappedGlyphs.Last() == (byte)SpecialGlyph.NewLine)
wrappedGlyphs.RemoveAt(wrappedGlyphs.Count - 1);

// Note: The added 1 is used as after the last new line character there are always other characters.
return new Text(wrappedGlyphs.ToArray(), 1 + height / glyphSize.Height, maxLineWidth / glyphSize.Width);
}

public IText ProcessText(string text, ITextNameProvider nameProvider, List<string> dictionary)
{
List<byte> glyphIndices = new List<byte>();
Expand Down
4 changes: 2 additions & 2 deletions Ambermoon.Renderer.OpenGL/RenderText.cs
Expand Up @@ -30,7 +30,7 @@ internal class RenderText : RenderNode, IRenderText
{
const int CharacterWidth = 6;
const int CharacterHeight = 6;
const int LineHeight = 8;
const int LineHeight = 7;
const byte ShadowColorIndex = 1;
protected int drawIndex = -1;
byte displayLayer = 0;
Expand Down Expand Up @@ -179,7 +179,7 @@ bool NewLine()
y += LineHeight;
numEmptyCharacterInLine = 0;

return y + CharacterHeight - 1 < bounds.Bottom;
return y + CharacterHeight - 1 <= bounds.Bottom;
}

void AdjustLineAlign(int lineEndGlyphIndex)
Expand Down

0 comments on commit eec240d

Please sign in to comment.