ChivalryServerLauncher/source/view/Forms.Map.pas

405 lines
10 KiB
ObjectPascal

unit Forms.Map;
interface
uses
System.Classes,
System.Generics.Collections,
Vcl.Controls,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.Graphics,
Vcl.StdCtrls,
VirtualTrees,
Frame.MapPreview,
Game.Base,
Game.Intf;
type
TMapForm = class(TForm)
btnCancel: TButton;
btnOK: TButton;
edtMapName: TEdit;
lblMapName: TLabel;
pnlButtons: TPanel;
pnlMapName: TPanel;
vstMap: TVirtualStringTree;
pnlFilter: TPanel;
lblFilter: TLabel;
edtFilter: TEdit;
frmMapPreview: TMapPreviewFrame;
pnlMapPreview: TPanel;
procedure btnOKClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure vstMapNodeDblClick(Sender: TBaseVirtualTree; const HitInfo: THitInfo);
procedure vstMapGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure vstMapPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure vstMapFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
procedure vstMapFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
procedure vstMapChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure vstMapCollapsing(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean);
procedure vstMapCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
procedure edtMapNameChange(Sender: TObject);
procedure edtFilterChange(Sender: TObject);
private
FLockMapChange: Boolean;
function GetMapName: string;
procedure SetMapName(const Value: string);
protected
procedure LoadPredefinedMapList(AGame: IGameMapList);
procedure CreateMaps(AMaps: TList<TGameMap>);
function FindMapNode(const AMapName: string): PVirtualNode;
property MapName: string read GetMapName write SetMapName;
public
class function Insert(AOwner: TComponent; AGame: IGameMapList; AMaps: TList<TGameMap>): Boolean;
end;
implementation
uses
System.StrUtils,
System.SysUtils,
Winapi.Windows;
type
PGameMap = ^TGameMap;
const
EmptyCategory = 'Other';
{$R *.dfm}
{ TMapForm }
class function TMapForm.Insert(AOwner: TComponent; AGame: IGameMapList; AMaps: TList<TGameMap>): Boolean;
begin
with Self.Create(AOwner) do
try
LoadPredefinedMapList(AGame);
Result := (ShowModal = mrOk);
if Result then
CreateMaps(AMaps);
finally
Free;
end;
end;
procedure TMapForm.FormCreate(Sender: TObject);
begin
vstMap.NodeDataSize := SizeOf(PGameMap);
end;
procedure TMapForm.LoadPredefinedMapList(AGame: IGameMapList);
var
map: TGameMap;
categoryNodes: TDictionary<string, PVirtualNode>;
parentNode: PVirtualNode;
node: PVirtualNode;
begin
vstMap.BeginUpdate;
try
vstMap.Clear;
categoryNodes := TDictionary<string, PVirtualNode>.Create;
try
for map in AGame.PredefinedMapList do
begin
if categoryNodes.ContainsKey(map.Category) then
parentNode := categoryNodes[map.Category]
else
begin
parentNode := vstMap.AddChild(nil, map);
categoryNodes.Add(map.Category, parentNode);
end;
vstMap.AddChild(parentNode, map);
end;
finally
FreeAndNil(categoryNodes);
end;
finally
vstMap.FullExpand;
vstMap.EndUpdate;
node := vstMap.GetFirstLevel(1);
if Assigned(node) then
begin
vstMap.FocusedNode := node;
vstMap.Selected[node] := True;
end;
end;
end;
procedure TMapForm.CreateMaps(AMaps: TList<TGameMap>);
var
node: PVirtualNode;
nodeData: PGameMap;
begin
if vstMap.SelectedCount > 1 then
begin
for node in vstMap.SelectedNodes do
begin
if vstMap.GetNodeLevel(node) = 1 then
begin
nodeData := vstMap.GetNodeData(node);
AMaps.Add(TGameMap.Create(nodeData^));
end;
end;
end else
begin
node := FindMapNode(MapName);
if Assigned(node) then
begin
nodeData := vstMap.GetNodeData(node);
AMaps.Add(TGameMap.Create(nodeData^));
end else
AMaps.Add(TGameMap.Create(MapName, '', ''));
end;
end;
function TMapForm.GetMapName: string;
begin
Result := Trim(edtMapName.Text);
end;
procedure TMapForm.SetMapName(const Value: string);
begin
edtMapName.Text := Value;
end;
function TMapForm.FindMapNode(const AMapName: string): PVirtualNode;
begin
Result := vstMap.IterateSubtree(nil,
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
var
nodeData: PGameMap;
begin
nodeData := Sender.GetNodeData(Node);
Abort := (Sender.GetNodeLevel(Node) > 0) and SameText(nodeData^.Name, AMapName);
end,
nil);
end;
procedure TMapForm.btnOKClick(Sender: TObject);
begin
if (vstMap.SelectedCount <= 1) and (Length(MapName) = 0) then
begin
MessageBox(Self.Handle, 'Please enter a map name', 'Error', MB_OK or MB_ICONERROR);
ActiveControl := edtMapName;
exit;
end;
ModalResult := mrOk;
end;
procedure TMapForm.vstMapNodeDblClick(Sender: TBaseVirtualTree; const HitInfo: THitInfo);
begin
btnOK.Click;
end;
procedure TMapForm.vstMapGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
nodeData: PGameMap;
begin
nodeData := Sender.GetNodeData(Node);
if Sender.GetNodeLevel(Node) = 0 then
begin
if Length(nodeData^.Category) > 0 then
CellText := nodeData^.Category
else
CellText := EmptyCategory;
end else
CellText := nodeData^.DisplayName;
end;
procedure TMapForm.vstMapPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
begin
if (TextType = ttNormal) and (Sender.GetNodeLevel(Node) = 0) then
TargetCanvas.Font.Style := [fsBold];
end;
procedure TMapForm.vstMapFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
var
sibling: PVirtualNode;
begin
Allowed := True;
if not Assigned(NewNode) then
exit;
{ Prevent selection of categories while being keyboard-friendly }
if Sender.GetNodeLevel(NewNode) = 0 then
begin
if Assigned(OldNode) then
begin
if Sender.AbsoluteIndex(OldNode) < Sender.AbsoluteIndex(NewNode) then
begin
{ Moving forwards }
Sender.FocusedNode := Sender.GetFirstChild(NewNode);
end else
begin
{ Moving backwards }
sibling := Sender.GetPreviousSibling(NewNode);
if Assigned(sibling) then
Sender.FocusedNode := Sender.GetLastChild(sibling);
end;
end;
Allowed := False;
end;
end;
procedure TMapForm.vstMapFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
var
nodeData: PGameMap;
begin
if FLockMapChange then
exit;
if Assigned(Node) then
begin
nodeData := Sender.GetNodeData(Node);
frmMapPreview.Load(nodeData^.PreviewName);
FLockMapChange := True;
try
edtMapName.Text := nodeData^.Name;
finally
FLockMapChange := False;
end;
end else
frmMapPreview.Clear;
end;
procedure TMapForm.vstMapChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
const
EditColor: array[Boolean] of TColor = (clBtnFace, clWindow);
begin
edtMapName.Enabled := (Sender.selectedCount <= 1);
edtMapName.Color := EditColor[edtMapName.Enabled];
end;
procedure TMapForm.vstMapCollapsing(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean);
begin
Allowed := False;
end;
procedure TMapForm.vstMapCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
nodeData1: PGameMap;
nodeData2: PGameMap;
begin
nodeData1 := Sender.GetNodeData(Node1);
nodeData2 := Sender.GetNodeData(Node2);
if Sender.GetNodeLevel(Node1) = 0 then
Result := CompareText(nodeData1^.Category, nodeData2^.Category)
else
Result := CompareText(nodeData1^.DisplayName, nodeData2^.DisplayName);
end;
procedure TMapForm.edtFilterChange(Sender: TObject);
var
node: PVirtualNode;
begin
vstMap.BeginUpdate;
try
{ First run; set visibility of map nodes }
vstMap.IterateSubtree(nil,
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
var
nodeData: PGameMap;
begin
if Sender.GetNodeLevel(Node) > 0 then
begin
nodeData := Sender.GetNodeData(Node);
Sender.IsFiltered[Node] := (Length(edtFilter.Text) > 0) and (not ContainsText(nodeData^.DisplayName, edtFilter.Text));
end;
end,
nil);
{ Second run; hide empty categories }
node := vstMap.GetFirst;
while Assigned(node) do
begin
vstMap.IsFiltered[Node] := not Assigned(vstMap.IterateSubtree(node,
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
begin
Abort := not Sender.IsFiltered[Node];
end,
nil, [], False, True));
node := vstMap.GetNextSibling(node);
end;
finally
vstMap.EndUpdate;
end;
end;
procedure TMapForm.edtMapNameChange(Sender: TObject);
var
node: PVirtualNode;
begin
if FLockMapChange then
exit;
node := FindMapNode(MapName);
FLockMapChange := True;
try
vstMap.FocusedNode := node;
finally
FLockMapChange := False;
end;
if Assigned(node) then
begin
vstMap.Selected[node] := True;
frmMapPreview.Load(MapName);
end else
begin
vstMap.ClearSelection;
frmMapPreview.Clear;
end;
end;
end.