added in-game SHP viewer with frame slider

This commit is contained in:
Matthias Mailänder
2013-05-15 18:13:33 +02:00
parent ce021a4f62
commit 881fcf1191
10 changed files with 408 additions and 25 deletions

View File

@@ -18,19 +18,21 @@ namespace OpenRA.FileFormats
{ {
public static class FileSystem public static class FileSystem
{ {
static List<IFolder> mountedFolders = new List<IFolder>(); static List<IFolder> MountedFolders = new List<IFolder>();
static Cache<uint, List<IFolder>> allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() ); static Cache<uint, List<IFolder>> allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
public static List<string> FolderPaths = new List<string>();
static void MountInner(IFolder folder) static void MountInner(IFolder folder)
{ {
mountedFolders.Add(folder); MountedFolders.Add(folder);
foreach( var hash in folder.AllFileHashes() ) foreach (var hash in folder.AllFileHashes())
{ {
var l = allFiles[hash]; var l = allFiles[hash];
if( !l.Contains( folder ) ) if (!l.Contains(folder))
l.Add( folder ); l.Add(folder);
} }
} }
@@ -78,6 +80,9 @@ namespace OpenRA.FileFormats
if (name.StartsWith("^")) if (name.StartsWith("^"))
name = Platform.SupportDir+name.Substring(1); name = Platform.SupportDir+name.Substring(1);
if (Directory.Exists(name))
FolderPaths.Add(name);
var a = (Action)(() => FileSystem.MountInner(OpenPackage(name))); var a = (Action)(() => FileSystem.MountInner(OpenPackage(name)));
if (optional) if (optional)
@@ -89,28 +94,29 @@ namespace OpenRA.FileFormats
public static void UnmountAll() public static void UnmountAll()
{ {
mountedFolders.Clear(); MountedFolders.Clear();
FolderPaths.Clear();
allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() ); allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
} }
public static bool Unmount(IFolder mount) public static bool Unmount(IFolder mount)
{ {
return (mountedFolders.RemoveAll(f => f == mount) > 0); return (MountedFolders.RemoveAll(f => f == mount) > 0);
} }
public static void Mount(IFolder mount) public static void Mount(IFolder mount)
{ {
if (!mountedFolders.Contains(mount)) mountedFolders.Add(mount); if (!MountedFolders.Contains(mount)) MountedFolders.Add(mount);
} }
public static void LoadFromManifest( Manifest manifest ) public static void LoadFromManifest(Manifest manifest)
{ {
UnmountAll(); UnmountAll();
foreach (var dir in manifest.Folders) Mount(dir); foreach (var dir in manifest.Folders) Mount(dir);
foreach (var pkg in manifest.Packages) Mount(pkg); foreach (var pkg in manifest.Packages) Mount(pkg);
} }
static Stream GetFromCache( Cache<uint, List<IFolder>> index, string filename ) static Stream GetFromCache(Cache<uint, List<IFolder>> index, string filename)
{ {
var folder = index[PackageEntry.HashFilename(filename)] var folder = index[PackageEntry.HashFilename(filename)]
.Where(x => x.Exists(filename)) .Where(x => x.Exists(filename))
@@ -125,21 +131,21 @@ namespace OpenRA.FileFormats
public static Stream Open(string filename) { return OpenWithExts(filename, ""); } public static Stream Open(string filename) { return OpenWithExts(filename, ""); }
public static Stream OpenWithExts( string filename, params string[] exts ) public static Stream OpenWithExts(string filename, params string[] exts)
{ {
if( filename.IndexOfAny( new char[] { '/', '\\' } ) == -1 ) if( filename.IndexOfAny( new char[] { '/', '\\' } ) == -1 )
{ {
foreach( var ext in exts ) foreach( var ext in exts )
{ {
var s = GetFromCache( allFiles, filename + ext ); var s = GetFromCache(allFiles, filename + ext);
if( s != null ) if (s != null)
return s; return s;
} }
} }
foreach( var ext in exts ) foreach (var ext in exts)
{ {
var folder = mountedFolders var folder = MountedFolders
.Where(x => x.Exists(filename + ext)) .Where(x => x.Exists(filename + ext))
.OrderByDescending(x => x.Priority) .OrderByDescending(x => x.Priority)
.FirstOrDefault(); .FirstOrDefault();
@@ -151,7 +157,7 @@ namespace OpenRA.FileFormats
throw new FileNotFoundException("File not found: {0}".F(filename), filename); throw new FileNotFoundException("File not found: {0}".F(filename), filename);
} }
public static bool Exists(string filename) { return mountedFolders.Any(f => f.Exists(filename)); } public static bool Exists(string filename) { return MountedFolders.Any(f => f.Exists(filename)); }
static Dictionary<string, Assembly> assemblyCache = new Dictionary<string, Assembly>(); static Dictionary<string, Assembly> assemblyCache = new Dictionary<string, Assembly>();

View File

@@ -16,11 +16,11 @@ namespace OpenRA.Graphics
{ {
public class SpriteLoader public class SpriteLoader
{ {
public SpriteLoader( string[] exts, SheetBuilder sheetBuilder ) public SpriteLoader(string[] exts, SheetBuilder sheetBuilder)
{ {
SheetBuilder = sheetBuilder; SheetBuilder = sheetBuilder;
this.exts = exts; this.exts = exts;
sprites = new Cache<string, Sprite[]>( LoadSprites ); sprites = new Cache<string, Sprite[]>(LoadSprites);
} }
readonly SheetBuilder SheetBuilder; readonly SheetBuilder SheetBuilder;

View File

@@ -18,6 +18,7 @@ namespace OpenRA.Widgets
public string Image = ""; public string Image = "";
public int Frame = 0; public int Frame = 0;
public string Palette = "chrome"; public string Palette = "chrome";
public bool LoopAnimation = false;
public Func<string> GetImage; public Func<string> GetImage;
public Func<int> GetFrame; public Func<int> GetFrame;
@@ -26,12 +27,13 @@ namespace OpenRA.Widgets
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
public ShpImageWidget( WorldRenderer worldRenderer) public ShpImageWidget(WorldRenderer worldRenderer)
: base() : base()
{ {
GetImage = () => { return Image; }; GetImage = () => { return Image; };
GetFrame = () => { return Frame; }; GetFrame = () => { return Frame; };
GetPalette = () => { return Palette; }; GetPalette = () => { return Palette; };
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
} }
@@ -41,9 +43,12 @@ namespace OpenRA.Widgets
Image = other.Image; Image = other.Image;
Frame = other.Frame; Frame = other.Frame;
Palette = other.Palette; Palette = other.Palette;
LoopAnimation = other.LoopAnimation;
GetImage = other.GetImage; GetImage = other.GetImage;
GetFrame = other.GetFrame; GetFrame = other.GetFrame;
GetPalette = other.GetPalette; GetPalette = other.GetPalette;
worldRenderer = other.worldRenderer; worldRenderer = other.worldRenderer;
} }
@@ -68,5 +73,32 @@ namespace OpenRA.Widgets
Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin, worldRenderer, palette); Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin, worldRenderer, palette);
} }
public int FrameCount
{
get { return Game.modData.SpriteLoader.LoadAllSprites(Image).Length-1; }
}
public void RenderNextFrame()
{
if (Frame < FrameCount)
Frame++;
else
Frame = 0;
}
public void RenderPreviousFrame()
{
if (Frame > 0)
Frame--;
else
Frame = FrameCount;
}
public override void Tick()
{
if (LoopAnimation)
RenderNextFrame();
}
} }
} }

View File

@@ -24,10 +24,15 @@ namespace OpenRA.Widgets
public float MinimumValue = 0; public float MinimumValue = 0;
public float MaximumValue = 1; public float MaximumValue = 1;
public float Value = 0; public float Value = 0;
public Func<float> GetValue;
protected bool isMoving = false; protected bool isMoving = false;
public SliderWidget() : base() {} public SliderWidget()
: base()
{
GetValue = () => Value;
}
public SliderWidget(SliderWidget other) public SliderWidget(SliderWidget other)
: base(other) : base(other)
@@ -38,6 +43,7 @@ namespace OpenRA.Widgets
MaximumValue = other.MaximumValue; MaximumValue = other.MaximumValue;
Value = other.Value; Value = other.Value;
TrackHeight = other.TrackHeight; TrackHeight = other.TrackHeight;
GetValue = other.GetValue;
} }
void UpdateValue(float newValue) void UpdateValue(float newValue)
@@ -53,7 +59,7 @@ namespace OpenRA.Widgets
if (mi.Event == MouseInputEvent.Down && !TakeFocus(mi)) return false; if (mi.Event == MouseInputEvent.Down && !TakeFocus(mi)) return false;
if (!Focused) return false; if (!Focused) return false;
switch( mi.Event ) switch(mi.Event)
{ {
case MouseInputEvent.Up: case MouseInputEvent.Up:
isMoving = false; isMoving = false;
@@ -99,6 +105,8 @@ namespace OpenRA.Widgets
if (!IsVisible()) if (!IsVisible())
return; return;
Value = GetValue();
var tr = ThumbRect; var tr = ThumbRect;
var rb = RenderBounds; var rb = RenderBounds;
var trackWidth = rb.Width; var trackWidth = rb.Width;

View File

@@ -436,6 +436,7 @@
<Compile Include="Widgets\ColorMixerWidget.cs" /> <Compile Include="Widgets\ColorMixerWidget.cs" />
<Compile Include="Widgets\HueSliderWidget.cs" /> <Compile Include="Widgets\HueSliderWidget.cs" />
<Compile Include="Render\WithTurret.cs" /> <Compile Include="Render\WithTurret.cs" />
<Compile Include="Widgets\Logic\AssetBrowserLogic.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -0,0 +1,124 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Widgets;
namespace OpenRA.Mods.RA.Widgets.Logic
{
public class AssetBrowserLogic
{
Widget panel;
ShpImageWidget spriteImage;
TextFieldWidget filenameInput;
SliderWidget frameSlider;
ButtonWidget playButton;
ButtonWidget pauseButton;
[ObjectCreator.UseCtor]
public AssetBrowserLogic(Widget widget, Action onExit, World world)
{
panel = widget;
spriteImage = panel.Get<ShpImageWidget>("SPRITE");
filenameInput = panel.Get<TextFieldWidget>("FILENAME_INPUT");
filenameInput.Text = spriteImage.Image;
filenameInput.OnEnterKey = () => LoadAsset(filenameInput.Text);
var assetList = panel.Get<ScrollPanelWidget>("ASSET_LIST");
var template = panel.Get<ScrollItemWidget>("ASSET_TEMPLATE");
assetList.RemoveChildren();
foreach (var folder in FileSystem.FolderPaths)
{
if (Directory.Exists(folder))
{
var shps = Directory.GetFiles(folder, "*.shp");
foreach (var shp in shps)
AddAsset(assetList, shp, template);
}
}
frameSlider = panel.Get<SliderWidget>("FRAME_SLIDER");
frameSlider.MaximumValue = (float)spriteImage.FrameCount;
frameSlider.Ticks = spriteImage.FrameCount+1;
frameSlider.OnChange += x => { spriteImage.Frame = (int)Math.Round(x); };
frameSlider.GetValue = () => spriteImage.Frame;
panel.Get<LabelWidget>("FRAME_COUNT").GetText = () => spriteImage.Frame.ToString();
playButton = panel.Get<ButtonWidget>("BUTTON_PLAY");
playButton.OnClick = () =>
{
spriteImage.LoopAnimation = true;
playButton.Visible = false;
pauseButton.Visible = true;
};
pauseButton = panel.Get<ButtonWidget>("BUTTON_PAUSE");
pauseButton.OnClick = () =>
{
spriteImage.LoopAnimation = false;
playButton.Visible = true;
pauseButton.Visible = false;
};
panel.Get<ButtonWidget>("BUTTON_STOP").OnClick = () =>
{
spriteImage.LoopAnimation = false;
frameSlider.Value = 0;
spriteImage.Frame = 0;
playButton.Visible = true;
pauseButton.Visible = false;
};
panel.Get<ButtonWidget>("BUTTON_NEXT").OnClick = () => { spriteImage.RenderNextFrame(); };
panel.Get<ButtonWidget>("BUTTON_PREV").OnClick = () => { spriteImage.RenderPreviousFrame(); };
panel.Get<ButtonWidget>("LOAD_BUTTON").OnClick = () =>
{
LoadAsset(filenameInput.Text);
};
panel.Get<ButtonWidget>("CLOSE_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
}
void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template)
{
var sprite = Path.GetFileNameWithoutExtension(filepath);
var item = ScrollItemWidget.Setup(template,
() => spriteImage != null && spriteImage.Image == sprite,
() => LoadAsset(sprite));
item.Get<LabelWidget>("TITLE").GetText = () => sprite;
list.AddChild(item);
}
bool LoadAsset(string filename)
{
if (filename == null)
return false;
filenameInput.Text = filename;
spriteImage.Frame = 0;
spriteImage.Image = filename;
frameSlider.MaximumValue = (float)spriteImage.FrameCount;
frameSlider.Ticks = spriteImage.FrameCount+1;
return true;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS) * Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information, * as published by the Free Software Foundation. For more information,
@@ -14,7 +14,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{ {
public class MainMenuButtonsLogic public class MainMenuButtonsLogic
{ {
enum MenuType { Main, None } enum MenuType { Main, None }
MenuType Menu = MenuType.Main; MenuType Menu = MenuType.Main;
@@ -73,6 +72,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic
}); });
}; };
widget.Get<ButtonWidget>("MAINMENU_BUTTON_ASSET_BROWSER").OnClick = () =>
{
Menu = MenuType.None;
Game.OpenWindow("ASSETBROWSER_BG", new WidgetArgs()
{
{ "onExit", () => Menu = MenuType.Main }
});
};
widget.Get<ButtonWidget>("MAINMENU_BUTTON_QUIT").OnClick = () => Game.Exit(); widget.Get<ButtonWidget>("MAINMENU_BUTTON_QUIT").OnClick = () => Game.Exit();
} }

View File

@@ -0,0 +1,196 @@
Background@ASSETBROWSER_BG:
Logic:AssetBrowserLogic
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:700
Height:410
Children:
Label@ASSETBROWSER_TITLE:
X:0
Y:20
Width:PARENT_RIGHT
Height:25
Text:Game Asset Viewer & Converter
Align:Center
Font:Bold
DropDownButton@SOURCE_SELECTOR:
X:40
Y:45
Width:160
Height:25
Font:Bold
Text:Folders
ScrollPanel@ASSET_LIST:
X:40
Y:80
Width:160
Height:190
Children:
ScrollItem@ASSET_TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Label@TITLE:
X:10
Width:PARENT_RIGHT-20
Height:25
TextField@FILENAME_INPUT:
X:40
Y:280
Width:140
Height:25
Button@LOAD_BUTTON:
X:40
Y:310
Width:140
Height:25
Text:Load
Font:Bold
Key:return
Label@PREVIEW_TITLE:
X:320
Y:45
Width:PARENT_RIGHT
Height:25
Text:Preview
Background@SPRITE_BG:
X:220
Y:80
Width:250
Height:250
Background:dialog3
Children:
ShpImage@SPRITE:
X:80
Y:80
Width:246
Height:246
Image:fact
Palette:player
Label@ACTIONS_TITLE:
X:PARENT_RIGHT - 150
Y:45
Width:PARENT_RIGHT
Height:25
Text:Actions
Button@REMAP_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 305
Width:160
Height:25
Text:Change Palette
Font:Bold
Button@IMPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 270
Width:160
Height:25
Text:Import from PNG
Font:Bold
Button@EXTRACT_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 235
Width:160
Height:25
Text:Extract to Folder
Font:Bold
Button@EXPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 200
Width:160
Height:25
Text:Export as PNG
Font:Bold
Button@CLOSE_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 115
Width:160
Height:25
Text:Close
Font:Bold
Key:escape
Container@FRAME_SELECTOR:
X:45
Y:360
Children:
Button@BUTTON_PREV:
X:0
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PREV:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:prev
Button@BUTTON_PLAY:
X:35
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PLAY:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:play
Button@BUTTON_PAUSE:
Visible: no
X:35
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PAUSE:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:pause
Button@BUTTON_STOP:
X:70
Y:0
Width:25
Height:25
Children:
Image@IMAGE_STOP:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:stop
Button@BUTTON_NEXT:
X:105
Y:0
Width:25
Height:25
Children:
Image@IMAGE_NEXT:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:next
Slider@FRAME_SLIDER:
X:175
Y:0
Width:410
Height:20
MinimumValue: 0
Label@FRAME_COUNT:
X:610
Y:0
Width:25
Height:25
Font:Bold

View File

@@ -23,7 +23,7 @@ Container@MAINMENU:
X:(WINDOW_RIGHT - WIDTH)/8 X:(WINDOW_RIGHT - WIDTH)/8
Y:(WINDOW_BOTTOM - HEIGHT)/2 Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:250 Width:250
Height:505 Height:555
Logic:MainMenuButtonsLogic Logic:MainMenuButtonsLogic
Children: Children:
Label@MAINMENU_LABEL_TITLE: Label@MAINMENU_LABEL_TITLE:
@@ -83,11 +83,18 @@ Container@MAINMENU:
Height:35 Height:35
Text:Replay Viewer Text:Replay Viewer
Font:Bold Font:Bold
Button@MAINMENU_BUTTON_QUIT: Button@MAINMENU_BUTTON_ASSET_BROWSER:
X:45 X:45
Y:430 Y:430
Width:160 Width:160
Height:35 Height:35
Text:Asset Browser
Font:Bold
Button@MAINMENU_BUTTON_QUIT:
X:45
Y:480
Width:160
Height:35
Text:Quit Text:Quit
Font:Bold Font:Bold
Background@PERF_BG: Background@PERF_BG:

View File

@@ -73,6 +73,7 @@ ChromeLayout:
mods/ra/chrome/cheats.yaml mods/ra/chrome/cheats.yaml
mods/ra/chrome/musicplayer.yaml mods/ra/chrome/musicplayer.yaml
mods/ra/chrome/tooltips.yaml mods/ra/chrome/tooltips.yaml
mods/ra/chrome/assetbrowser.yaml
Weapons: Weapons:
mods/ra/weapons.yaml mods/ra/weapons.yaml