From 43752348bff8c6c33bebd28ad14d81f23f34db5d Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 14 Oct 2017 23:24:01 +1100 Subject: [PATCH] Break up the giant InventoryScreen class to be more modular. (separate table widget, and scrollbar widget) --- .../Inventory/InventoryScreen.Input.cs | 103 ------ .../2D/Screens/Inventory/InventoryScreen.cs | 257 -------------- ClassicalSharp/2D/Screens/InventoryScreen.cs | 109 ++++++ .../ScrollbarWidget.cs} | 89 +++-- ClassicalSharp/2D/Widgets/TableWidget.cs | 329 ++++++++++++++++++ ClassicalSharp/ClassicalSharp.csproj | 7 +- src/Client/WordWrap.h | 4 +- 7 files changed, 494 insertions(+), 404 deletions(-) delete mode 100644 ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Input.cs delete mode 100644 ClassicalSharp/2D/Screens/Inventory/InventoryScreen.cs create mode 100644 ClassicalSharp/2D/Screens/InventoryScreen.cs rename ClassicalSharp/2D/{Screens/Inventory/InventoryScreen.Scrolling.cs => Widgets/ScrollbarWidget.cs} (51%) create mode 100644 ClassicalSharp/2D/Widgets/TableWidget.cs diff --git a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Input.cs b/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Input.cs deleted file mode 100644 index 70a5f039c..000000000 --- a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Input.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using System.Drawing; -using ClassicalSharp.GraphicsAPI; -using OpenTK.Input; - -namespace ClassicalSharp.Gui.Screens { - public partial class InventoryScreen : Screen { - - public override bool HandlesMouseMove(int mouseX, int mouseY) { - if (draggingMouse) { - mouseY -= TableY; - scrollY = (int)((mouseY - mouseOffset) / ScrollbarScale); - ClampScrollY(); - return true; - } - - selIndex = -1; - if (Contains(startX, startY + 3, blocksPerRow * blockSize, - maxRows * blockSize - 3 * 2, mouseX, mouseY)) { - for (int i = 0; i < blocksTable.Length; i++) { - int x, y; - GetCoords(i, out x, out y); - - if (Contains(x, y, blockSize, blockSize, mouseX, mouseY)) { - selIndex = i; - break; - } - } - } - RecreateBlockInfoTexture(); - return true; - } - - public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) { - if (draggingMouse || game.Gui.hudScreen.hotbar.HandlesMouseClick(mouseX, mouseY, button)) - return true; - - int scrollX = TableX + TableWidth; - if (button == MouseButton.Left && mouseX >= scrollX && mouseX < scrollX + scrollWidth) { - ScrollbarClick(mouseY); - } else if (button == MouseButton.Left) { - if (selIndex != -1) { - game.Inventory.Selected = blocksTable[selIndex]; - } else if (Contains(TableX, TableY, TableWidth, TableHeight, mouseX, mouseY)) { - return true; - } - - bool hotbar = game.Input.ControlDown; - if (!hotbar) - game.Gui.SetNewScreen(null); - } - return true; - } - - // We want the user to be able to press B to exit the inventory menu - // however since we use KeyRepeat = true, we must wait until the first time they released B - // before marking the next press as closing the menu - bool releasedInv; - public override bool HandlesKeyDown(Key key) { - if (key == game.Mapping(KeyBind.PauseOrExit)) { - game.Gui.SetNewScreen(null); - } else if (key == game.Mapping(KeyBind.Inventory) && releasedInv) { - game.Gui.SetNewScreen(null); - } else if (key == Key.Enter && selIndex != -1) { - game.Inventory.Selected = blocksTable[selIndex]; - game.Gui.SetNewScreen(null); - } else if ((key == Key.Left || key == Key.Keypad4) && selIndex != -1) { - ArrowKeyMove(-1); - } else if ((key == Key.Right || key == Key.Keypad6) && selIndex != -1) { - ArrowKeyMove(1); - } else if ((key == Key.Up || key == Key.Keypad8) && selIndex != -1) { - ArrowKeyMove(-blocksPerRow); - } else if ((key == Key.Down || key == Key.Keypad2) && selIndex != -1) { - ArrowKeyMove(blocksPerRow); - } else if (game.Gui.hudScreen.hotbar.HandlesKeyDown(key)) { - } - return true; - } - - public override bool HandlesKeyUp(Key key) { - if (key == game.Mapping(KeyBind.Inventory)) { - releasedInv = true; return true; - } - return game.Gui.hudScreen.hotbar.HandlesKeyUp(key); - } - - void ArrowKeyMove(int delta) { - int startIndex = selIndex; - selIndex += delta; - if (selIndex < 0) - selIndex -= delta; - if (selIndex >= blocksTable.Length) - selIndex -= delta; - - int scrollDelta = (selIndex / blocksPerRow) - (startIndex / blocksPerRow); - scrollY += scrollDelta; - ClampScrollY(); - RecreateBlockInfoTexture(); - MoveCursorToSelected(); - } - } -} diff --git a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.cs b/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.cs deleted file mode 100644 index a9cdce8cb..000000000 --- a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using System.Drawing; -using ClassicalSharp.GraphicsAPI; -using OpenTK.Input; - -#if USE16_BIT -using BlockID = System.UInt16; -#else -using BlockID = System.Byte; -#endif - -namespace ClassicalSharp.Gui.Screens { - public partial class InventoryScreen : Screen { - - public InventoryScreen(Game game) : base(game) { - font = new Font(game.FontName, 16); - HandlesAllInput = true; - } - - BlockID[] blocksTable; - Texture blockInfoTexture; - const int maxRows = 8; - int blocksPerRow { - get { return game.ClassicMode && !game.ClassicHacks ? 9 : 10; } - } - - int selIndex, rows; - int startX, startY, blockSize; - float selBlockExpand; - readonly Font font; - StringBuffer buffer = new StringBuffer(128); - IsometricBlockDrawer drawer = new IsometricBlockDrawer(); - - int TableX { get { return startX - 5 - 10; } } - int TableY { get { return startY - 5 - 30; } } - int TableWidth { get { return blocksPerRow * blockSize + 10 + 20; } } - int TableHeight { get { return Math.Min(rows, maxRows) * blockSize + 10 + 40; } } - - // These were sourced by taking a screenshot of vanilla - // Then using paint to extract the colour components - // Then using wolfram alpha to solve the glblendfunc equation - static FastColour topCol = new FastColour(34, 34, 34, 168); - static FastColour bottomCol = new FastColour(57, 57, 104, 202); - static FastColour topSelCol = new FastColour(255, 255, 255, 142); - static FastColour bottomSelCol = new FastColour(255, 255, 255, 192); - - static VertexP3fT2fC4b[] vertices = new VertexP3fT2fC4b[8 * 10 * (4 * 4)]; - int vb; - public override void Render(double delta) { - gfx.Draw2DQuad(TableX, TableY, TableWidth, TableHeight, topCol, bottomCol); - if (rows > maxRows) - DrawScrollbar(); - - if (selIndex != -1 && game.ClassicMode) { - int x, y; - GetCoords(selIndex, out x, out y); - float off = blockSize * 0.1f; - gfx.Draw2DQuad(x - off, y - off, blockSize + off * 2, - blockSize + off * 2, topSelCol, bottomSelCol); - } - gfx.Texturing = true; - gfx.SetBatchFormat(VertexFormat.P3fT2fC4b); - - drawer.BeginBatch(game, vertices, vb); - for (int i = 0; i < blocksTable.Length; i++) { - int x, y; - if (!GetCoords(i, out x, out y)) continue; - - // We want to always draw the selected block on top of others - if (i == selIndex) continue; - drawer.DrawBatch(blocksTable[i], blockSize * 0.7f / 2f, - x + blockSize / 2, y + blockSize / 2); - } - - if (selIndex != -1) { - int x, y; - GetCoords(selIndex, out x, out y); - drawer.DrawBatch(blocksTable[selIndex], (blockSize + selBlockExpand) * 0.7f / 2, - x + blockSize / 2, y + blockSize / 2); - } - drawer.EndBatch(); - - if (blockInfoTexture.IsValid) - blockInfoTexture.Render(gfx); - gfx.Texturing = false; - } - - bool GetCoords(int i, out int x, out int y) { - int col = i % blocksPerRow; - int row = i / blocksPerRow; - x = startX + blockSize * col; - y = startY + blockSize * row + 3; - y -= scrollY * blockSize; - row -= scrollY; - return row >= 0 && row < maxRows; - } - - Point GetMouseCoords(int i) { - int x, y; - GetCoords(i, out x, out y); - x += blockSize / 2; y += blockSize / 2; - - Point topLeft = game.PointToScreen(Point.Empty); - x += topLeft.X; y += topLeft.Y; - return new Point(x, y); - } - - public override void Dispose() { - font.Dispose(); - game.Events.BlockPermissionsChanged -= BlockPermissionsChanged; - game.Keyboard.KeyRepeat = false; - - ContextLost(); - game.Graphics.ContextLost -= ContextLost; - game.Graphics.ContextRecreated -= ContextRecreated; - } - - public override void OnResize(int width, int height) { - blockSize = (int)(50 * Math.Sqrt(game.GuiInventoryScale)); - selBlockExpand = (float)(25 * Math.Sqrt(game.GuiInventoryScale)); - - int rowsUsed = Math.Min(maxRows, rows); - startX = game.Width / 2 - (blockSize * blocksPerRow) / 2; - startY = game.Height / 2 - (rowsUsed * blockSize) / 2; - blockInfoTexture.X1 = startX + (blockSize * blocksPerRow) / 2 - blockInfoTexture.Width / 2; - blockInfoTexture.Y1 = startY - blockInfoTexture.Height - 5; - } - - public override void Init() { - blockSize = (int)(50 * Math.Sqrt(game.GuiInventoryScale)); - selBlockExpand = (float)(25 * Math.Sqrt(game.GuiInventoryScale)); - game.Events.BlockPermissionsChanged += BlockPermissionsChanged; - - ContextRecreated(); - game.Graphics.ContextLost += ContextLost; - game.Graphics.ContextRecreated += ContextRecreated; - - RecreateBlockTable(); - SetBlockTo(game.Inventory.Selected); - game.Keyboard.KeyRepeat = true; - } - - public void SetBlockTo(BlockID block) { - selIndex = IndexOfBlock(block); - scrollY = (selIndex / blocksPerRow) - (maxRows - 1); - ClampScrollY(); - MoveCursorToSelected(); - RecreateBlockInfoTexture(); - } - - int IndexOfBlock(BlockID block) { - for (int i = 0; i < blocksTable.Length; i++) { - if (blocksTable[i] == block) return i; - } - return -1; - } - - void MoveCursorToSelected() { - if (selIndex == -1) return; - game.DesktopCursorPos = GetMouseCoords(selIndex); - } - - void BlockPermissionsChanged(object sender, EventArgs e) { - RecreateBlockTable(); - if (selIndex >= blocksTable.Length) - selIndex = blocksTable.Length - 1; - - scrollY = selIndex / blocksPerRow; - ClampScrollY(); - RecreateBlockInfoTexture(); - } - - void UpdateBlockInfoString(BlockID block) { - int index = 0; - buffer.Clear(); - if (game.PureClassic) { buffer.Append(ref index, "Select block"); return; } - - buffer.Append(ref index, "&f"); - string value = BlockInfo.Name[block]; - buffer.Append(ref index, value); - if (game.ClassicMode) return; - - buffer.Append(ref index, " (ID "); - buffer.AppendNum(ref index, block); - buffer.Append(ref index, "&f, place "); - buffer.Append(ref index, BlockInfo.CanPlace[block] ? "&aYes" : "&cNo"); - buffer.Append(ref index, "&f, delete "); - buffer.Append(ref index, BlockInfo.CanDelete[block] ? "&aYes" : "&cNo"); - buffer.Append(ref index, "&f)"); - } - - int lastCreatedIndex = -1000; - void RecreateBlockInfoTexture() { - if (selIndex == lastCreatedIndex || blocksTable == null) return; - lastCreatedIndex = selIndex; - - gfx.DeleteTexture(ref blockInfoTexture); - if (selIndex == -1) return; - - BlockID block = blocksTable[selIndex]; - UpdateBlockInfoString(block); - string value = buffer.ToString(); - - DrawTextArgs args = new DrawTextArgs(value, font, true); - Size size = game.Drawer2D.MeasureSize(ref args); - int x = startX + (blockSize * blocksPerRow) / 2 - size.Width / 2; - int y = startY - size.Height - 5; - - args.SkipPartsCheck = true; - blockInfoTexture = game.Drawer2D.MakeTextTexture(ref args, x, y); - } - - void RecreateBlockTable() { - int blocksCount = 0; - int count = game.UseCPE ? Block.Count : Block.OriginalCount; - for (int i = 0; i < count; i++) { - BlockID block = game.Inventory.Map[i]; - if (Show(block)) blocksCount++; - } - - rows = Utils.CeilDiv(blocksCount, blocksPerRow); - int rowsUsed = Math.Min(maxRows, rows); - startX = game.Width / 2 - (blockSize * blocksPerRow) / 2; - startY = game.Height / 2 - (rowsUsed * blockSize) / 2; - blocksTable = new BlockID[blocksCount]; - - int index = 0; - for (int i = 0; i < count; i++) { - BlockID block = game.Inventory.Map[i]; - if (Show(block)) blocksTable[index++] = block; - } - } - - bool Show(BlockID block) { - if (block == Block.Invalid) return false; - - if (block < Block.CpeCount) { - int count = game.UseCPEBlocks ? Block.CpeCount : Block.OriginalCount; - return block < count; - } - return true; - } - - - protected override void ContextLost() { - gfx.DeleteVb(ref vb); - gfx.DeleteTexture(ref blockInfoTexture); - lastCreatedIndex = -1000; - } - - protected override void ContextRecreated() { - vb = gfx.CreateDynamicVb(VertexFormat.P3fT2fC4b, vertices.Length); - RecreateBlockInfoTexture(); - } - } -} diff --git a/ClassicalSharp/2D/Screens/InventoryScreen.cs b/ClassicalSharp/2D/Screens/InventoryScreen.cs new file mode 100644 index 000000000..fb4b01a9d --- /dev/null +++ b/ClassicalSharp/2D/Screens/InventoryScreen.cs @@ -0,0 +1,109 @@ +// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +using System; +using System.Drawing; +using ClassicalSharp.Gui.Widgets; +using OpenTK.Input; + +namespace ClassicalSharp.Gui.Screens { + public partial class InventoryScreen : Screen { + + TableWidget table; + Font font; + public InventoryScreen(Game game) : base(game) { + font = new Font(game.FontName, 16); + HandlesAllInput = true; + } + + public override void Init() { + InitTableWidget(); + game.Events.BlockPermissionsChanged += OnBlockChanged; + game.Events.BlockDefinitionChanged += OnBlockChanged; + game.Keyboard.KeyRepeat = true; + game.Graphics.ContextLost += ContextLost; + game.Graphics.ContextRecreated += ContextRecreated; + } + + void InitTableWidget() { + table = new TableWidget(game); + table.font = font; + table.ElementsPerRow = game.ClassicMode && !game.ClassicHacks ? 9 : 10; + table.Init(); + } + + public override void Render(double delta) { table.Render(delta); } + + public override void OnResize(int width, int height) { table.Reposition(); } + + public override void Dispose() { + font.Dispose(); + table.Dispose(); + game.Events.BlockPermissionsChanged -= OnBlockChanged; + game.Events.BlockDefinitionChanged -= OnBlockChanged; + game.Keyboard.KeyRepeat = false; + game.Graphics.ContextLost -= ContextLost; + game.Graphics.ContextRecreated -= ContextRecreated; + } + + void OnBlockChanged(object sender, EventArgs e) { + table.OnInventoryChanged(); + } + + protected override void ContextLost() { table.Dispose(); } + + protected override void ContextRecreated() { table.Recreate(); } + + + public override bool HandlesMouseMove(int mouseX, int mouseY) { + return table.HandlesMouseMove(mouseX, mouseY); + } + + public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) { + if (table.scroll.draggingMouse || game.Gui.hudScreen.hotbar.HandlesMouseClick(mouseX, mouseY, button)) + return true; + + bool handled = table.HandlesMouseClick(mouseX, mouseY, button); + if ((!handled || table.PendingClose) && button == MouseButton.Left) { + bool hotbar = game.Input.ControlDown; + if (!hotbar) game.Gui.SetNewScreen(null); + } + return true; + } + + public override bool HandlesMouseScroll(float delta) { + bool hotbar = game.Input.AltDown || game.Input.ControlDown || game.Input.ShiftDown; + if (hotbar) return false; + return table.HandlesMouseScroll(delta); + } + + public override bool HandlesMouseUp(int mouseX, int mouseY, MouseButton button) { + return table.HandlesMouseUp(mouseX, mouseY, button); + } + + // We want the user to be able to press B to exit the inventory menu + // however since we use KeyRepeat = true, we must wait until the first time they released B + // before marking the next press as closing the menu + bool releasedInv; + + public override bool HandlesKeyDown(Key key) { + if (key == game.Mapping(KeyBind.PauseOrExit)) { + game.Gui.SetNewScreen(null); + } else if (key == game.Mapping(KeyBind.Inventory) && releasedInv) { + game.Gui.SetNewScreen(null); + } else if (key == Key.Enter && table.SelectedIndex != -1) { + game.Inventory.Selected = table.Elements[table.SelectedIndex]; + game.Gui.SetNewScreen(null); + } else if (table.HandlesKeyDown(key)) { + } else { + game.Gui.hudScreen.hotbar.HandlesKeyDown(key); + } + return true; + } + + public override bool HandlesKeyUp(Key key) { + if (key == game.Mapping(KeyBind.Inventory)) { + releasedInv = true; return true; + } + return game.Gui.hudScreen.hotbar.HandlesKeyUp(key); + } + } +} diff --git a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Scrolling.cs b/ClassicalSharp/2D/Widgets/ScrollbarWidget.cs similarity index 51% rename from ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Scrolling.cs rename to ClassicalSharp/2D/Widgets/ScrollbarWidget.cs index 6ea1bca45..aa97f7b64 100644 --- a/ClassicalSharp/2D/Screens/Inventory/InventoryScreen.Scrolling.cs +++ b/ClassicalSharp/2D/Widgets/ScrollbarWidget.cs @@ -1,24 +1,38 @@ // Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; -using System.Drawing; using OpenTK.Input; -namespace ClassicalSharp.Gui.Screens { - public partial class InventoryScreen : Screen { +namespace ClassicalSharp.Gui.Widgets { + public sealed class ScrollbarWidget : Widget { + public ScrollbarWidget(Game game) : base(game) { + Width = scrollWidth; + } + + public int TotalRows, ScrollY; + + public void ClampScrollY() { + int maxRows = TotalRows - TableWidget.MaxRowsDisplayed; + if (ScrollY >= maxRows) ScrollY = maxRows; + if (ScrollY < 0) ScrollY = 0; + } + + public override void Init() { } + public override void Dispose() { } + const int scrollWidth = 22, scrollBorder = 2, nubsWidth = 3; static FastColour scrollBackCol = new FastColour(10, 10, 10, 220); static FastColour scrollBarCol = new FastColour(100, 100, 100, 220); static FastColour scrollHoverCol = new FastColour(122, 122, 122, 220); - float ScrollbarScale { get { return (TableHeight - scrollBorder * 2) / (float)rows; } } + float ScrollbarScale { get { return (Height - scrollBorder * 2) / (float)TotalRows; } } - void DrawScrollbar() { - int x = TableX + TableWidth, width = scrollWidth; - gfx.Draw2DQuad(x, TableY, width, TableHeight, scrollBackCol); + public override void Render(double delta) { + int x = X, width = Width; + gfx.Draw2DQuad(x, Y, width, Height, scrollBackCol); int y, height; GetScrollbarCoords(out y, out height); - x += scrollBorder; width -= scrollBorder * 2; y += TableY; + x += scrollBorder; width -= scrollBorder * 2; y += Y; bool hovered = game.Mouse.Y >= y && game.Mouse.Y < (y + height) && game.Mouse.X >= x && game.Mouse.X < (x + width); @@ -35,50 +49,50 @@ namespace ClassicalSharp.Gui.Screens { void GetScrollbarCoords(out int y, out int height) { float scale = ScrollbarScale; - y = (int)Math.Ceiling(scrollY * scale) + scrollBorder; - height = (int)Math.Ceiling(maxRows * scale); - height = Math.Min(y + height, TableHeight - scrollBorder) - y; + y = (int)Math.Ceiling(ScrollY * scale) + scrollBorder; + height = (int)Math.Ceiling(TableWidget.MaxRowsDisplayed * scale); + height = Math.Min(y + height, Height - scrollBorder) - y; } + float invAcc; public override bool HandlesMouseScroll(float delta) { - bool bounds = Contains(TableX - scrollWidth, TableY, TableWidth + scrollWidth, - TableHeight, game.Mouse.X, game.Mouse.Y); - bool hotbar = game.Input.AltDown || game.Input.ControlDown || game.Input.ShiftDown; - if (!bounds || hotbar) return false; - - int rowY = (selIndex / blocksPerRow) - scrollY; int steps = Utils.AccumulateWheelDelta(ref invAcc, delta); - scrollY -= steps; + ScrollY -= steps; ClampScrollY(); - if (selIndex == - 1) return true; - - selIndex = scrollY * blocksPerRow + (selIndex % blocksPerRow); - for (int row = 0; row < rowY; row++) { - selIndex += blocksPerRow; - } - - if (selIndex >= blocksTable.Length) - selIndex = -1; - RecreateBlockInfoTexture(); return true; } - int scrollY; - void ClampScrollY() { - if (scrollY >= rows - maxRows) scrollY = rows - maxRows; - if (scrollY < 0) scrollY = 0; + public override bool HandlesMouseMove(int mouseX, int mouseY) { + if (draggingMouse) { + mouseY -= Y; + ScrollY = (int)((mouseY - mouseOffset) / ScrollbarScale); + ClampScrollY(); + return true; + } + return false; } + public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) { + if (draggingMouse) return true; + if (button != MouseButton.Left) return false; + + if (mouseX >= X && mouseX < X + Width) { + ScrollbarClick(mouseY); + return true; + } + return false; + } + void ScrollbarClick(int mouseY) { - mouseY -= TableY; + mouseY -= Y; int y, height; GetScrollbarCoords(out y, out height); if (mouseY < y) { - scrollY -= maxRows; + ScrollY -= TableWidget.MaxRowsDisplayed; } else if (mouseY >= y + height) { - scrollY += maxRows; + ScrollY += TableWidget.MaxRowsDisplayed; } else { draggingMouse = true; mouseOffset = mouseY - y; @@ -86,9 +100,8 @@ namespace ClassicalSharp.Gui.Screens { ClampScrollY(); } - bool draggingMouse = false; - int mouseOffset = 0; - + internal bool draggingMouse = false; + int mouseOffset = 0; public override bool HandlesMouseUp(int mouseX, int mouseY, MouseButton button) { draggingMouse = false; mouseOffset = 0; diff --git a/ClassicalSharp/2D/Widgets/TableWidget.cs b/ClassicalSharp/2D/Widgets/TableWidget.cs new file mode 100644 index 000000000..239a9d6b7 --- /dev/null +++ b/ClassicalSharp/2D/Widgets/TableWidget.cs @@ -0,0 +1,329 @@ +// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +using System; +using System.Drawing; +using ClassicalSharp.GraphicsAPI; +using OpenTK.Input; + +#if USE16_BIT +using BlockID = System.UInt16; +#else +using BlockID = System.Byte; +#endif + +namespace ClassicalSharp.Gui.Widgets { + public sealed class TableWidget : Widget { + + public TableWidget(Game game) : base(game) { } + + public const int MaxRowsDisplayed = 8; + public BlockID[] Elements; + public int ElementsPerRow; + public int SelectedIndex; + public Font font; + internal ScrollbarWidget scroll; + public bool PendingClose; + + Texture blockInfoTexture; + int totalRows, blockSize; + float selBlockExpand; + StringBuffer buffer = new StringBuffer(128); + IsometricBlockDrawer drawer = new IsometricBlockDrawer(); + + int TableX { get { return X - 5 - 10; } } + int TableY { get { return Y - 5 - 30; } } + int TableWidth { get { return ElementsPerRow * blockSize + 10 + 20; } } + int TableHeight { get { return Math.Min(totalRows, MaxRowsDisplayed) * blockSize + 10 + 40; } } + + // These were sourced by taking a screenshot of vanilla + // Then using paint to extract the colour components + // Then using wolfram alpha to solve the glblendfunc equation + static FastColour topCol = new FastColour(34, 34, 34, 168); + static FastColour bottomCol = new FastColour(57, 57, 104, 202); + static FastColour topSelCol = new FastColour(255, 255, 255, 142); + static FastColour bottomSelCol = new FastColour(255, 255, 255, 192); + + static VertexP3fT2fC4b[] vertices = new VertexP3fT2fC4b[8 * 10 * (4 * 4)]; + int vb; + public override void Render(double delta) { + gfx.Draw2DQuad(TableX, TableY, TableWidth, TableHeight, topCol, bottomCol); + if (totalRows > MaxRowsDisplayed) scroll.Render(delta); + + if (SelectedIndex != -1 && game.ClassicMode) { + int x, y; + GetCoords(SelectedIndex, out x, out y); + float off = blockSize * 0.1f; + gfx.Draw2DQuad(x - off, y - off, blockSize + off * 2, + blockSize + off * 2, topSelCol, bottomSelCol); + } + gfx.Texturing = true; + gfx.SetBatchFormat(VertexFormat.P3fT2fC4b); + + drawer.BeginBatch(game, vertices, vb); + for (int i = 0; i < Elements.Length; i++) { + int x, y; + if (!GetCoords(i, out x, out y)) continue; + + // We want to always draw the selected block on top of others + if (i == SelectedIndex) continue; + drawer.DrawBatch(Elements[i], blockSize * 0.7f / 2f, + x + blockSize / 2, y + blockSize / 2); + } + + if (SelectedIndex != -1) { + int x, y; + GetCoords(SelectedIndex, out x, out y); + drawer.DrawBatch(Elements[SelectedIndex], (blockSize + selBlockExpand) * 0.7f / 2, + x + blockSize / 2, y + blockSize / 2); + } + drawer.EndBatch(); + + if (blockInfoTexture.IsValid) + blockInfoTexture.Render(gfx); + gfx.Texturing = false; + } + + bool GetCoords(int i, out int screenX, out int screenY) { + int x = i % ElementsPerRow, y = i / ElementsPerRow; + screenX = X + blockSize * x; + screenY = Y + blockSize * y + 3; + screenY -= scroll.ScrollY * blockSize; + y -= scroll.ScrollY; + return y >= 0 && y < MaxRowsDisplayed; + } + + Point GetMouseCoords(int i) { + int x, y; + GetCoords(i, out x, out y); + x += blockSize / 2; y += blockSize / 2; + + Point topLeft = game.PointToScreen(Point.Empty); + x += topLeft.X; y += topLeft.Y; + return new Point(x, y); + } + + public override void Init() { + scroll = new ScrollbarWidget(game); + RecreateElements(); + Reposition(); + SetBlockTo(game.Inventory.Selected); + Recreate(); + } + + public override void Dispose() { + gfx.DeleteVb(ref vb); + gfx.DeleteTexture(ref blockInfoTexture); + lastCreatedIndex = -1000; + } + + public override void Recreate() { + Dispose(); + vb = gfx.CreateDynamicVb(VertexFormat.P3fT2fC4b, vertices.Length); + RecreateBlockInfoTexture(); + } + + public override void Reposition() { + blockSize = (int)(50 * Math.Sqrt(game.GuiInventoryScale)); + selBlockExpand = (float)(25 * Math.Sqrt(game.GuiInventoryScale)); + int rowsDisplayed = Math.Min(MaxRowsDisplayed, totalRows); + X = game.Width / 2 - (blockSize * ElementsPerRow) / 2; + Y = game.Height / 2 - (rowsDisplayed * blockSize) / 2; + + blockInfoTexture.X1 = X + (blockSize * ElementsPerRow) / 2 - blockInfoTexture.Width / 2; + blockInfoTexture.Y1 = Y - blockInfoTexture.Height - 5; + UpdateScrollbarPosition(); + } + + void UpdateScrollbarPosition() { + scroll.X = TableX + TableWidth; + scroll.Y = TableY; + scroll.Height = TableHeight; + scroll.TotalRows = totalRows; + } + + public void SetBlockTo(BlockID block) { + SelectedIndex = -1; + for (int i = 0; i < Elements.Length; i++) { + if (Elements[i] == block) SelectedIndex = i; + } + + scroll.ScrollY = (SelectedIndex / ElementsPerRow) - (MaxRowsDisplayed - 1); + scroll.ClampScrollY(); + MoveCursorToSelected(); + RecreateBlockInfoTexture(); + } + + public void OnInventoryChanged() { + RecreateElements(); + if (SelectedIndex >= Elements.Length) + SelectedIndex = Elements.Length - 1; + + scroll.ScrollY = SelectedIndex / ElementsPerRow; + scroll.ClampScrollY(); + RecreateBlockInfoTexture(); + } + + void MoveCursorToSelected() { + if (SelectedIndex == -1) return; + game.DesktopCursorPos = GetMouseCoords(SelectedIndex); + } + + void UpdateBlockInfoString(BlockID block) { + int index = 0; + buffer.Clear(); + if (game.PureClassic) { buffer.Append(ref index, "Select block"); return; } + + buffer.Append(ref index, "&f"); + string value = BlockInfo.Name[block]; + buffer.Append(ref index, value); + if (game.ClassicMode) return; + + buffer.Append(ref index, " (ID "); + buffer.AppendNum(ref index, block); + buffer.Append(ref index, "&f, place "); + buffer.Append(ref index, BlockInfo.CanPlace[block] ? "&aYes" : "&cNo"); + buffer.Append(ref index, "&f, delete "); + buffer.Append(ref index, BlockInfo.CanDelete[block] ? "&aYes" : "&cNo"); + buffer.Append(ref index, "&f)"); + } + + int lastCreatedIndex = -1000; + void RecreateBlockInfoTexture() { + if (SelectedIndex == lastCreatedIndex || Elements == null) return; + lastCreatedIndex = SelectedIndex; + + gfx.DeleteTexture(ref blockInfoTexture); + if (SelectedIndex == -1) return; + + BlockID block = Elements[SelectedIndex]; + UpdateBlockInfoString(block); + string value = buffer.ToString(); + + DrawTextArgs args = new DrawTextArgs(value, font, true); + Size size = game.Drawer2D.MeasureSize(ref args); + int x = X + (blockSize * ElementsPerRow) / 2 - size.Width / 2; + int y = Y - size.Height - 5; + + args.SkipPartsCheck = true; + blockInfoTexture = game.Drawer2D.MakeTextTexture(ref args, x, y); + } + + void RecreateElements() { + int totalElements = 0; + int count = game.UseCPE ? Block.Count : Block.OriginalCount; + for (int i = 0; i < count; i++) { + BlockID block = game.Inventory.Map[i]; + if (Show(block)) totalElements++; + } + + totalRows = Utils.CeilDiv(totalElements, ElementsPerRow); + UpdateScrollbarPosition(); + + int rowsDisplayed = Math.Min(MaxRowsDisplayed, totalRows); + X = game.Width / 2 - (blockSize * ElementsPerRow) / 2; + Y = game.Height / 2 - (rowsDisplayed * blockSize) / 2; + Elements = new BlockID[totalElements]; + + int index = 0; + for (int i = 0; i < count; i++) { + BlockID block = game.Inventory.Map[i]; + if (Show(block)) Elements[index++] = block; + } + } + + bool Show(BlockID block) { + if (block == Block.Invalid) return false; + + if (block < Block.CpeCount) { + int count = game.UseCPEBlocks ? Block.CpeCount : Block.OriginalCount; + return block < count; + } + return true; + } + + public override bool HandlesMouseMove(int mouseX, int mouseY) { + if (scroll.HandlesMouseMove(mouseX, mouseY)) return true; + + SelectedIndex = -1; + if (Contains(X, Y + 3, ElementsPerRow * blockSize, + MaxRowsDisplayed * blockSize - 3 * 2, mouseX, mouseY)) { + for (int i = 0; i < Elements.Length; i++) { + int x, y; + GetCoords(i, out x, out y); + + if (Contains(x, y, blockSize, blockSize, mouseX, mouseY)) { + SelectedIndex = i; + break; + } + } + } + RecreateBlockInfoTexture(); + return true; + } + + public override bool HandlesMouseClick(int mouseX, int mouseY, MouseButton button) { + PendingClose = false; + if (button != MouseButton.Left) return false; + + if (scroll.HandlesMouseClick(mouseX, mouseY, button)) { + return true; + } else if (SelectedIndex != -1) { + game.Inventory.Selected = Elements[SelectedIndex]; + PendingClose = true; + return true; + } else if (Contains(TableX, TableY, TableWidth, TableHeight, mouseX, mouseY)) { + return true; + } + return false; + } + + public override bool HandlesKeyDown(Key key) { + if (SelectedIndex == -1) return false; + + if (key == Key.Left || key == Key.Keypad4) { + ScrollRelative(-1); + } else if (key == Key.Right || key == Key.Keypad6) { + ScrollRelative(1); + } else if (key == Key.Up || key == Key.Keypad8) { + ScrollRelative(-ElementsPerRow); + } else if (key == Key.Down || key == Key.Keypad2) { + ScrollRelative(ElementsPerRow); + } else { + return false; + } + return true; + } + + void ScrollRelative(int delta) { + int startIndex = SelectedIndex; + SelectedIndex += delta; + if (SelectedIndex < 0) SelectedIndex -= delta; + if (SelectedIndex >= Elements.Length) SelectedIndex -= delta; + + int scrollDelta = (SelectedIndex / ElementsPerRow) - (startIndex / ElementsPerRow); + scroll.ScrollY += scrollDelta; + scroll.ClampScrollY(); + RecreateBlockInfoTexture(); + MoveCursorToSelected(); + } + + public override bool HandlesMouseScroll(float delta) { + int startScrollY = scroll.ScrollY; + bool bounds = Contains(TableX - scroll.Width, TableY, TableWidth + scroll.Width, + TableHeight, game.Mouse.X, game.Mouse.Y); + if (!bounds) return false; + + scroll.HandlesMouseScroll(delta); + if (SelectedIndex == - 1) return true; + + SelectedIndex += (scroll.ScrollY - startScrollY) * ElementsPerRow; + if (SelectedIndex >= Elements.Length) SelectedIndex = -1; + + RecreateBlockInfoTexture(); + return true; + } + + public override bool HandlesMouseUp(int mouseX, int mouseY, MouseButton button) { + return scroll.HandlesMouseUp(mouseX, mouseY, button); + } + } +} diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 2bb7ae4d2..67ede36a6 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -87,13 +87,11 @@ + - - - @@ -135,7 +133,9 @@ + + @@ -328,7 +328,6 @@ - diff --git a/src/Client/WordWrap.h b/src/Client/WordWrap.h index 20015db30..25e5b78fd 100644 --- a/src/Client/WordWrap.h +++ b/src/Client/WordWrap.h @@ -1,5 +1,5 @@ -#ifndef CS_DATETIME_H -#define CS_DATETIME_H +#ifndef CS_WORDWRAP_H +#define CS_WORDWRAP_H #include "Typedefs.h" #include "String.h" /* Allows wrapping a single line of text into multiple lines.