mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-29 13:04:59 +00:00
TUI support for more container
This commit is contained in:
parent
ef133f3d6d
commit
0194380fbc
17 changed files with 2045 additions and 822 deletions
|
|
@ -1,304 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using MinecraftClient.Inventory;
|
||||
|
||||
namespace MinecraftClient.Tui
|
||||
{
|
||||
public class InventoryMainView : UserControl
|
||||
public class PlayerInventoryView : ContainerViewBase
|
||||
{
|
||||
private static readonly IBrush BrSlotEmptyA = new SolidColorBrush(Color.FromRgb(40, 40, 40));
|
||||
private static readonly IBrush BrSlotEmptyB = new SolidColorBrush(Color.FromRgb(55, 55, 55));
|
||||
private static readonly IBrush BrSlotFillA = new SolidColorBrush(Color.FromRgb(60, 60, 75));
|
||||
private static readonly IBrush BrSlotFillB = new SolidColorBrush(Color.FromRgb(75, 75, 90));
|
||||
private static readonly IBrush BrSlotHover = new SolidColorBrush(Color.FromRgb(100, 100, 140));
|
||||
private static readonly IBrush BrName = Brushes.White;
|
||||
private static readonly IBrush BrCount = Brushes.Yellow;
|
||||
private static readonly IBrush BrDim = new SolidColorBrush(Color.FromRgb(80, 80, 80));
|
||||
private static readonly IBrush BrEquipLbl = Brushes.DarkCyan;
|
||||
private static readonly IBrush BrInfoHighlight = new SolidColorBrush(Color.FromRgb(40, 40, 60));
|
||||
private static readonly IBrush BrHeldItemBg = new SolidColorBrush(Color.FromRgb(60, 50, 80));
|
||||
private static readonly IBrush BrHeldItemBorder = Brushes.Yellow;
|
||||
|
||||
private int _slotW;
|
||||
private int _slotH;
|
||||
private int _nameMaxLen;
|
||||
private int _nameLines;
|
||||
private readonly PlayerInventoryViewModel _playerVm;
|
||||
private int _topGap;
|
||||
private int _termW;
|
||||
|
||||
private readonly InventoryViewModel _vm;
|
||||
private TextBlock _titleText = null!;
|
||||
private Border _infoDetailBorder = null!;
|
||||
private TextBlock _infoDetailText = null!;
|
||||
private TextBlock _cursorItemText = null!;
|
||||
private TextBlock _helpText = null!;
|
||||
|
||||
private TextBlock[] _hotbarIndicators = new TextBlock[9];
|
||||
private int _currentHotbarSlot = -1;
|
||||
|
||||
private Border? _lastHoveredSlotBorder;
|
||||
|
||||
private Canvas _overlayCanvas = null!;
|
||||
private Border _heldItemFloater = null!;
|
||||
private TextBlock _heldItemFloaterName = null!;
|
||||
private TextBlock _heldItemFloaterCount = null!;
|
||||
|
||||
private ScrollViewer _chatScrollViewer = null!;
|
||||
private ObservableCollection<string>? _chatLines;
|
||||
private int _lastTermW;
|
||||
private int _lastTermH;
|
||||
|
||||
public InventoryMainView()
|
||||
public PlayerInventoryView(McClient handler, int windowId)
|
||||
: base(new PlayerInventoryViewModel(handler, windowId))
|
||||
{
|
||||
var handler = InventoryTuiHost.ActiveHandler
|
||||
?? throw new InvalidOperationException("No active McClient");
|
||||
int windowId = InventoryTuiHost.ActiveWindowId;
|
||||
|
||||
_vm = new InventoryViewModel(handler, windowId);
|
||||
_currentHotbarSlot = handler.GetCurrentSlot();
|
||||
|
||||
_chatLines = TuiConsoleBackend.Instance?.GetView()?.GetRecentLogLines(50)
|
||||
?? new ObservableCollection<string>();
|
||||
|
||||
RebuildUi();
|
||||
_playerVm = (PlayerInventoryViewModel)_vm;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void RebuildUi()
|
||||
protected override int GetTotalSlotRows()
|
||||
{
|
||||
int termH;
|
||||
try
|
||||
{
|
||||
_termW = System.Console.WindowWidth;
|
||||
termH = System.Console.WindowHeight;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_termW = 120;
|
||||
termH = 40;
|
||||
}
|
||||
|
||||
_lastTermW = _termW;
|
||||
_lastTermH = termH;
|
||||
|
||||
int availW = _termW - 26;
|
||||
_slotW = Math.Clamp(availW / 9, 8, 18);
|
||||
_nameMaxLen = _slotW;
|
||||
|
||||
int topUsedW = _slotW * 4 + 8 + _slotW * 2 + 4 + _slotW;
|
||||
_topGap = Math.Max(2, (_slotW * 9 - topUsedW) / 2);
|
||||
|
||||
_slotH = Math.Clamp((termH - 8) / 6, 2, 5);
|
||||
_nameLines = _slotH;
|
||||
|
||||
_vm.SetSlotDisplayParams(_nameMaxLen, _nameLines);
|
||||
|
||||
_lastHoveredSlotBorder = null;
|
||||
|
||||
_titleText = new TextBlock
|
||||
{
|
||||
FontWeight = FontWeight.Bold,
|
||||
Foreground = Brushes.Cyan,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
};
|
||||
|
||||
_infoDetailText = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Foreground = Brushes.White,
|
||||
};
|
||||
|
||||
_infoDetailBorder = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
Padding = new Thickness(0),
|
||||
Child = _infoDetailText,
|
||||
};
|
||||
|
||||
_cursorItemText = new TextBlock
|
||||
{
|
||||
Foreground = Brushes.Yellow,
|
||||
FontWeight = FontWeight.Bold,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
};
|
||||
|
||||
_helpText = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(140, 140, 140)),
|
||||
Text = Translations.tui_inventory_controls_help,
|
||||
};
|
||||
|
||||
_heldItemFloaterName = new TextBlock
|
||||
{
|
||||
Foreground = Brushes.White,
|
||||
FontWeight = FontWeight.Bold,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
};
|
||||
_heldItemFloaterCount = new TextBlock
|
||||
{
|
||||
Foreground = BrCount,
|
||||
FontWeight = FontWeight.Bold,
|
||||
};
|
||||
_heldItemFloater = new Border
|
||||
{
|
||||
Background = BrHeldItemBg,
|
||||
BorderBrush = BrHeldItemBorder,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(1, 0),
|
||||
IsVisible = false,
|
||||
MaxWidth = 24,
|
||||
Child = new StackPanel
|
||||
{
|
||||
Children = { _heldItemFloaterName, _heldItemFloaterCount },
|
||||
},
|
||||
};
|
||||
|
||||
_overlayCanvas = new Canvas { IsHitTestVisible = false };
|
||||
_overlayCanvas.Children.Add(_heldItemFloater);
|
||||
|
||||
var chatLines = _chatLines!;
|
||||
chatLines.CollectionChanged += (_, _) =>
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
var sv = _chatScrollViewer;
|
||||
if (sv.Extent.Height > sv.Viewport.Height)
|
||||
sv.Offset = new Vector(0, sv.Extent.Height - sv.Viewport.Height);
|
||||
}, Avalonia.Threading.DispatcherPriority.Background);
|
||||
};
|
||||
var chatItemsControl = new ItemsControl
|
||||
{
|
||||
ItemsSource = chatLines,
|
||||
Focusable = false,
|
||||
ItemTemplate = new FuncDataTemplate<string>((s, _) =>
|
||||
new TextBlock
|
||||
{
|
||||
Text = s,
|
||||
Foreground = Brushes.Gray,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
}),
|
||||
};
|
||||
_chatScrollViewer = new ScrollViewer
|
||||
{
|
||||
Content = chatItemsControl,
|
||||
Background = Brushes.Black,
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
|
||||
Padding = new Thickness(0),
|
||||
};
|
||||
|
||||
_hotbarIndicators = new TextBlock[9];
|
||||
|
||||
Content = BuildRootLayout();
|
||||
UpdateTitle();
|
||||
UpdateInfoPanel();
|
||||
|
||||
_chatScrollToBottom = true;
|
||||
_chatScrollViewer.ScrollChanged += OnChatScrollChanged;
|
||||
return 6;
|
||||
}
|
||||
|
||||
private bool _chatScrollToBottom = true;
|
||||
|
||||
private void OnChatScrollChanged(object? sender, ScrollChangedEventArgs e)
|
||||
protected override void RebuildUi()
|
||||
{
|
||||
if (!_chatScrollToBottom) return;
|
||||
var sv = _chatScrollViewer;
|
||||
if (sv.Extent.Height > sv.Viewport.Height)
|
||||
{
|
||||
sv.Offset = new Vector(0, sv.Extent.Height - sv.Viewport.Height);
|
||||
_chatScrollToBottom = false;
|
||||
}
|
||||
int availW = 0;
|
||||
try { availW = System.Console.WindowWidth - 26; } catch { availW = 94; }
|
||||
int slotW = System.Math.Clamp(availW / 9, 8, 18);
|
||||
int topUsedW = slotW * 4 + 8 + slotW * 2 + 4 + slotW;
|
||||
_topGap = System.Math.Max(2, (slotW * 9 - topUsedW) / 2);
|
||||
|
||||
base.RebuildUi();
|
||||
}
|
||||
|
||||
private Control BuildRootLayout()
|
||||
{
|
||||
// Layout (top-down):
|
||||
// Title
|
||||
// [InfoPanel(right)] [InventoryGrid(left)] <-- inventory area
|
||||
// ChatScrollViewer (full width, fills remaining)
|
||||
|
||||
var inventoryArea = BuildMainArea();
|
||||
DockPanel.SetDock(_titleText, Dock.Top);
|
||||
DockPanel.SetDock(inventoryArea, Dock.Top);
|
||||
|
||||
var mainContent = new DockPanel
|
||||
{
|
||||
Children = { _titleText, inventoryArea, _chatScrollViewer }
|
||||
};
|
||||
|
||||
return new Panel
|
||||
{
|
||||
Background = Brushes.Black,
|
||||
Children = { mainContent, _overlayCanvas }
|
||||
};
|
||||
}
|
||||
|
||||
private Control BuildMainArea()
|
||||
{
|
||||
var infoPanel = BuildInfoPanel();
|
||||
DockPanel.SetDock(infoPanel, Dock.Right);
|
||||
|
||||
return new DockPanel
|
||||
{
|
||||
Children = { infoPanel, BuildInventoryPanel() }
|
||||
};
|
||||
}
|
||||
|
||||
private Control BuildInfoPanel()
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = Brushes.Gray,
|
||||
Padding = new Thickness(1),
|
||||
Width = 24,
|
||||
Child = new StackPanel
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new TextBlock { Text = Translations.tui_inventory_item_info, FontWeight = FontWeight.Bold, Foreground = Brushes.Cyan },
|
||||
_infoDetailBorder,
|
||||
new TextBlock { Text = Translations.tui_inventory_held_item, FontWeight = FontWeight.Bold, Foreground = Brushes.Yellow, Margin = new Thickness(0, 1, 0, 0) },
|
||||
_cursorItemText,
|
||||
new TextBlock { Text = Translations.tui_inventory_controls, FontWeight = FontWeight.Bold, Foreground = Brushes.Green, Margin = new Thickness(0, 1, 0, 0) },
|
||||
_helpText,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Control BuildInventoryPanel()
|
||||
{
|
||||
var root = new StackPanel
|
||||
{
|
||||
Spacing = 0,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
};
|
||||
|
||||
root.Children.Add(BuildTopSection());
|
||||
root.Children.Add(new Border { Height = 1 });
|
||||
root.Children.Add(BuildSlotGrid(_vm.MainInventorySlots, 9));
|
||||
root.Children.Add(BuildHotbarSection());
|
||||
|
||||
return new Border
|
||||
{
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = Brushes.Gray,
|
||||
Child = root,
|
||||
};
|
||||
}
|
||||
|
||||
private Control BuildTopSection()
|
||||
protected override Control BuildContainerSpecificArea()
|
||||
{
|
||||
var row = new StackPanel
|
||||
{
|
||||
|
|
@ -318,7 +53,7 @@ namespace MinecraftClient.Tui
|
|||
FontWeight = FontWeight.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
});
|
||||
offPanel.Children.Add(CreateSlotCell(_vm.OffhandSlot, 0, 0));
|
||||
offPanel.Children.Add(CreateSlotCell(_playerVm.OffhandSlot, 0, 0));
|
||||
row.Children.Add(offPanel);
|
||||
|
||||
var equipGrid = new Grid
|
||||
|
|
@ -332,7 +67,7 @@ namespace MinecraftClient.Tui
|
|||
var lbl = MakeLabel(label);
|
||||
Grid.SetRow(lbl, r); Grid.SetColumn(lbl, gc);
|
||||
equipGrid.Children.Add(lbl);
|
||||
var btn = CreateSlotCell(_vm.EquipmentSlots[eqIdx], r, gc / 2);
|
||||
var btn = CreateSlotCell(_playerVm.EquipmentSlots[eqIdx], r, gc / 2);
|
||||
Grid.SetRow(btn, r); Grid.SetColumn(btn, gc + 1);
|
||||
equipGrid.Children.Add(btn);
|
||||
}
|
||||
|
|
@ -354,7 +89,7 @@ namespace MinecraftClient.Tui
|
|||
for (int ci = 0; ci < 4; ci++)
|
||||
{
|
||||
int cr = ci / 2, cc = ci % 2;
|
||||
var cs = CreateSlotCell(_vm.CraftingInputSlots[ci], cr, cc);
|
||||
var cs = CreateSlotCell(_playerVm.CraftingInputSlots[ci], cr, cc);
|
||||
Grid.SetRow(cs, cr);
|
||||
Grid.SetColumn(cs, cc);
|
||||
craftGrid.Children.Add(cs);
|
||||
|
|
@ -382,7 +117,7 @@ namespace MinecraftClient.Tui
|
|||
FontWeight = FontWeight.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
});
|
||||
craftOutPanel.Children.Add(CreateSlotCell(_vm.CraftingOutputSlot, 0, 1));
|
||||
craftOutPanel.Children.Add(CreateSlotCell(_playerVm.CraftingOutputSlot, 0, 1));
|
||||
Grid.SetRow(craftOutPanel, 0); Grid.SetColumn(craftOutPanel, 3);
|
||||
Grid.SetRowSpan(craftOutPanel, 2);
|
||||
craftGrid.Children.Add(craftOutPanel);
|
||||
|
|
@ -390,363 +125,5 @@ namespace MinecraftClient.Tui
|
|||
row.Children.Add(craftGrid);
|
||||
return row;
|
||||
}
|
||||
|
||||
private Control BuildHotbarSection()
|
||||
{
|
||||
var panel = new StackPanel { Spacing = 0 };
|
||||
|
||||
var numberRow = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
};
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
bool active = i == _currentHotbarSlot;
|
||||
string label = active ? $"{i + 1} \u25bc" : $" {i + 1} ";
|
||||
|
||||
var tb = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
Width = _slotW,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Foreground = active ? Brushes.LightGreen : Brushes.DarkCyan,
|
||||
FontWeight = FontWeight.Bold,
|
||||
};
|
||||
_hotbarIndicators[i] = tb;
|
||||
numberRow.Children.Add(tb);
|
||||
}
|
||||
panel.Children.Add(numberRow);
|
||||
panel.Children.Add(BuildSlotGrid(_vm.HotbarSlots, 9));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private TextBlock MakeLabel(string text)
|
||||
{
|
||||
return new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
Foreground = BrEquipLbl,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(1, 0, 0, 0),
|
||||
FontWeight = FontWeight.Bold,
|
||||
};
|
||||
}
|
||||
|
||||
private Control BuildSlotGrid(ObservableCollection<SlotViewModel> slots, int columns)
|
||||
{
|
||||
var grid = new Grid();
|
||||
int rows = (slots.Count + columns - 1) / columns;
|
||||
|
||||
for (int r = 0; r < rows; r++)
|
||||
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
for (int c = 0; c < columns; c++)
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
||||
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
int row = i / columns;
|
||||
int col = i % columns;
|
||||
var cell = CreateSlotCell(slots[i], row, col);
|
||||
Grid.SetRow(cell, row);
|
||||
Grid.SetColumn(cell, col);
|
||||
grid.Children.Add(cell);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static IBrush GetSlotBg(bool isEmpty, int row, int col)
|
||||
{
|
||||
bool isA = (row + col) % 2 == 0;
|
||||
return isEmpty
|
||||
? (isA ? BrSlotEmptyA : BrSlotEmptyB)
|
||||
: (isA ? BrSlotFillA : BrSlotFillB);
|
||||
}
|
||||
|
||||
private Border CreateSlotCell(SlotViewModel slot, int row = 0, int col = 0)
|
||||
{
|
||||
var nameTb = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
};
|
||||
|
||||
var countTb = new TextBlock
|
||||
{
|
||||
Foreground = BrCount,
|
||||
FontWeight = FontWeight.Bold,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Bottom,
|
||||
};
|
||||
|
||||
ApplySlotVisual(slot, nameTb, countTb);
|
||||
|
||||
int r = row, c = col;
|
||||
var border = new Border
|
||||
{
|
||||
Width = _slotW,
|
||||
Height = _slotH,
|
||||
Background = GetSlotBg(slot.IsEmpty, r, c),
|
||||
Child = new Panel
|
||||
{
|
||||
Children = { nameTb, countTb },
|
||||
},
|
||||
Tag = (slot, r, c),
|
||||
};
|
||||
|
||||
border.PointerPressed += OnSlotPointerPressed;
|
||||
border.PointerEntered += OnSlotPointerEnter;
|
||||
border.PointerExited += OnSlotPointerExit;
|
||||
border.PointerMoved += OnSlotPointerMoved;
|
||||
|
||||
slot.PropertyChanged += (_, _) =>
|
||||
{
|
||||
ApplySlotVisual(slot, nameTb, countTb);
|
||||
border.Background = GetSlotBg(slot.IsEmpty, r, c);
|
||||
};
|
||||
|
||||
return border;
|
||||
}
|
||||
|
||||
private void ApplySlotVisual(SlotViewModel slot, TextBlock nameTb, TextBlock countTb)
|
||||
{
|
||||
if (slot.IsEmpty)
|
||||
{
|
||||
nameTb.Text = "";
|
||||
nameTb.Foreground = BrDim;
|
||||
countTb.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
nameTb.Text = slot.ItemDisplayText;
|
||||
nameTb.Foreground = BrName;
|
||||
countTb.Text = slot.CountDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlotPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (sender is not Border border || border.Tag is not (SlotViewModel slot, int, int))
|
||||
return;
|
||||
|
||||
SetHover(border, slot);
|
||||
|
||||
var point = e.GetCurrentPoint(border);
|
||||
bool isShift = (e.KeyModifiers & KeyModifiers.Shift) != 0;
|
||||
|
||||
WindowActionType action;
|
||||
if (point.Properties.IsRightButtonPressed)
|
||||
action = isShift ? WindowActionType.ShiftRightClick : WindowActionType.RightClick;
|
||||
else
|
||||
action = isShift ? WindowActionType.ShiftClick : WindowActionType.LeftClick;
|
||||
|
||||
_vm.PerformAction(slot.SlotId, action);
|
||||
UpdateInfoPanel();
|
||||
UpdateHeldItemFloater(e);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSlotPointerEnter(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Border b && b.Tag is (SlotViewModel slot, int, int))
|
||||
{
|
||||
SetHover(b, slot);
|
||||
UpdateHeldItemFloater(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlotPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Border b && b.Tag is (SlotViewModel slot, int, int))
|
||||
{
|
||||
SetHover(b, slot);
|
||||
UpdateHeldItemFloater(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlotPointerExit(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Border b && b.Tag is (SlotViewModel slot, int row, int col))
|
||||
b.Background = GetSlotBg(slot.IsEmpty, row, col);
|
||||
}
|
||||
|
||||
private void SetHover(Border border, SlotViewModel slot)
|
||||
{
|
||||
if (_lastHoveredSlotBorder != null && _lastHoveredSlotBorder != border)
|
||||
{
|
||||
if (_lastHoveredSlotBorder.Tag is (SlotViewModel oldSlot, int or, int oc))
|
||||
_lastHoveredSlotBorder.Background = GetSlotBg(oldSlot.IsEmpty, or, oc);
|
||||
}
|
||||
|
||||
_lastHoveredSlotBorder = border;
|
||||
border.Background = BrSlotHover;
|
||||
_vm.HoveredSlot = slot;
|
||||
UpdateInfoPanel();
|
||||
}
|
||||
|
||||
private void UpdateHeldItemFloater(PointerEventArgs e)
|
||||
{
|
||||
if (!_vm.HasCursorItem)
|
||||
{
|
||||
_heldItemFloater.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_heldItemFloaterName.Text = _vm.CursorItemInfo;
|
||||
_heldItemFloaterCount.Text = "";
|
||||
|
||||
try
|
||||
{
|
||||
var pos = e.GetPosition(_overlayCanvas);
|
||||
double left = pos.X + 2;
|
||||
double remainingW = _termW - left - 2;
|
||||
int maxW = Math.Max(8, (int)remainingW);
|
||||
_heldItemFloater.MaxWidth = maxW;
|
||||
Canvas.SetLeft(_heldItemFloater, left);
|
||||
Canvas.SetTop(_heldItemFloater, pos.Y);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_heldItemFloater.MaxWidth = 24;
|
||||
Canvas.SetLeft(_heldItemFloater, 0);
|
||||
Canvas.SetTop(_heldItemFloater, 0);
|
||||
}
|
||||
|
||||
_heldItemFloater.IsVisible = true;
|
||||
}
|
||||
|
||||
private void UpdateInfoPanel()
|
||||
{
|
||||
_infoDetailText.Text = _vm.HoveredSlotDetailText;
|
||||
|
||||
bool hasHoveredItem = _vm.HoveredSlot != null && !_vm.HoveredSlot.IsEmpty;
|
||||
_infoDetailBorder.Background = hasHoveredItem ? BrInfoHighlight : Brushes.Transparent;
|
||||
|
||||
if (_vm.HasCursorItem)
|
||||
{
|
||||
_cursorItemText.Text = _vm.CursorItemInfo;
|
||||
_cursorItemText.Foreground = Brushes.Yellow;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cursorItemText.Text = Translations.tui_inventory_cursor_empty;
|
||||
_cursorItemText.Foreground = BrDim;
|
||||
_heldItemFloater.IsVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitle()
|
||||
{
|
||||
_titleText.Text = _vm.Title;
|
||||
}
|
||||
|
||||
private void CloseInventory()
|
||||
{
|
||||
if (ConsoleIO.Backend is TuiConsoleBackend tuiBackend)
|
||||
tuiBackend.GetView()?.HideOverlay();
|
||||
else
|
||||
(Application.Current?.ApplicationLifetime as IControlledApplicationLifetime)?.Shutdown();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Escape:
|
||||
case Key.E:
|
||||
CloseInventory();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.C:
|
||||
if ((e.KeyModifiers & KeyModifiers.Shift) != 0 &&
|
||||
_vm.HoveredSlot != null && !_vm.HoveredSlot.IsEmpty)
|
||||
{
|
||||
_vm.PerformAction(_vm.HoveredSlot.SlotId, WindowActionType.ShiftClick);
|
||||
UpdateInfoPanel();
|
||||
}
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Q:
|
||||
if (_vm.HoveredSlot != null && !_vm.HoveredSlot.IsEmpty)
|
||||
{
|
||||
var action = (e.KeyModifiers & KeyModifiers.Control) != 0
|
||||
? WindowActionType.DropItemStack
|
||||
: WindowActionType.DropItem;
|
||||
_vm.PerformAction(_vm.HoveredSlot.SlotId, action);
|
||||
UpdateInfoPanel();
|
||||
}
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.R:
|
||||
_vm.RefreshFromContainer();
|
||||
_currentHotbarSlot = _vm.Handler.GetCurrentSlot();
|
||||
UpdateHotbarIndicators();
|
||||
UpdateInfoPanel();
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHotbarIndicators()
|
||||
{
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
bool active = i == _currentHotbarSlot;
|
||||
_hotbarIndicators[i].Text = active ? $"{i + 1} \u25bc" : $" {i + 1} ";
|
||||
_hotbarIndicators[i].Foreground = active ? Brushes.LightGreen : Brushes.DarkCyan;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
Focusable = true;
|
||||
Focus();
|
||||
AddHandler(KeyDownEvent, OnTunnelKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
||||
SizeChanged += OnViewSizeChanged;
|
||||
}
|
||||
|
||||
private void OnTunnelKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
CloseInventory();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnViewSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
{
|
||||
int newW, newH;
|
||||
try
|
||||
{
|
||||
newW = System.Console.WindowWidth;
|
||||
newH = System.Console.WindowHeight;
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (newW == _lastTermW && newH == _lastTermH) return;
|
||||
|
||||
_vm.RefreshFromContainer();
|
||||
_currentHotbarSlot = _vm.Handler.GetCurrentSlot();
|
||||
RebuildUi();
|
||||
Focus();
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||
{
|
||||
base.OnGotFocus(e);
|
||||
Focusable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue