mirror of
https://github.com/ppy/osu.git
synced 2025-03-15 11:47:18 +08:00
Merge pull request #10234 from peppy/editor-load-audio
Add audio track selection to editor setup screen
This commit is contained in:
commit
93a137ed84
@ -0,0 +1,69 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Tests.Resources;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneEditorBeatmapCreation : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
|
||||
|
||||
base.SetUpSteps();
|
||||
|
||||
// if we save a beatmap with a hash collision, things fall over.
|
||||
// probably needs a more solid resolution in the future but this will do for now.
|
||||
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewBeatmap()
|
||||
{
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddAudioTrack()
|
||||
{
|
||||
AddAssert("switch track to real track", () =>
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
var temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
string extractedFolder = $"{temp}_extracted";
|
||||
Directory.CreateDirectory(extractedFolder);
|
||||
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"));
|
||||
|
||||
File.Delete(temp);
|
||||
Directory.Delete(extractedFolder, true);
|
||||
|
||||
return success;
|
||||
});
|
||||
|
||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||
}
|
||||
}
|
||||
}
|
@ -260,7 +260,7 @@ namespace osu.Game.Beatmaps
|
||||
fileInfo.Filename = beatmapInfo.Path;
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
UpdateFile(setInfo, fileInfo, stream);
|
||||
ReplaceFile(setInfo, fileInfo, stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,12 +401,27 @@ namespace osu.Game.Database
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an existing file, or create a new entry if not already part of the <paramref name="model"/>'s files.
|
||||
/// Replace an existing file with a new version.
|
||||
/// </summary>
|
||||
/// <param name="model">The item to operate on.</param>
|
||||
/// <param name="file">The file model to be updated or added.</param>
|
||||
/// <param name="file">The existing file to be replaced.</param>
|
||||
/// <param name="contents">The new file contents.</param>
|
||||
public void UpdateFile(TModel model, TFileModel file, Stream contents)
|
||||
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
|
||||
public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null)
|
||||
{
|
||||
using (ContextFactory.GetForWrite())
|
||||
{
|
||||
DeleteFile(model, file);
|
||||
AddFile(model, contents, filename ?? file.Filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete new file.
|
||||
/// </summary>
|
||||
/// <param name="model">The item to operate on.</param>
|
||||
/// <param name="file">The existing file to be deleted.</param>
|
||||
public void DeleteFile(TModel model, TFileModel file)
|
||||
{
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
{
|
||||
@ -415,15 +430,28 @@ namespace osu.Game.Database
|
||||
{
|
||||
Files.Dereference(file.FileInfo);
|
||||
|
||||
// Remove the file model.
|
||||
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
|
||||
// Definitely can be removed once we rework the database backend.
|
||||
usage.Context.Set<TFileModel>().Remove(file);
|
||||
}
|
||||
|
||||
// Add the new file info and containing file model.
|
||||
model.Files.Remove(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new file.
|
||||
/// </summary>
|
||||
/// <param name="model">The item to operate on.</param>
|
||||
/// <param name="contents">The new file contents.</param>
|
||||
/// <param name="filename">The filename for the new file.</param>
|
||||
public void AddFile(TModel model, Stream contents, string filename)
|
||||
{
|
||||
using (ContextFactory.GetForWrite())
|
||||
{
|
||||
model.Files.Add(new TFileModel
|
||||
{
|
||||
Filename = file.Filename,
|
||||
Filename = filename,
|
||||
FileInfo = Files.Add(contents)
|
||||
});
|
||||
|
||||
|
@ -44,13 +44,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Component.BorderColour = colours.Blue;
|
||||
}
|
||||
|
||||
protected override OsuTextBox CreateComponent() => new OsuTextBox
|
||||
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox
|
||||
{
|
||||
CommitOnFocusLost = true,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
}.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText));
|
||||
};
|
||||
|
||||
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
||||
{
|
||||
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,11 @@ namespace osu.Game.Overlays
|
||||
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forcefully reload the current <see cref="WorkingBeatmap"/>'s track from disk.
|
||||
/// </summary>
|
||||
public void ReloadCurrentTrack() => changeTrack();
|
||||
|
||||
/// <summary>
|
||||
/// Change the position of a <see cref="BeatmapSetInfo"/> in the current playlist.
|
||||
/// </summary>
|
||||
|
@ -18,7 +18,8 @@ namespace osu.Game.Screens.Edit.Components
|
||||
private const float contents_padding = 15;
|
||||
|
||||
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||
protected Track Track => Beatmap.Value.Track;
|
||||
|
||||
protected readonly IBindable<Track> Track = new Bindable<Track>();
|
||||
|
||||
private readonly Drawable background;
|
||||
private readonly Container content;
|
||||
@ -42,9 +43,11 @@ namespace osu.Game.Screens.Edit.Components
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours)
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, EditorClock clock)
|
||||
{
|
||||
Beatmap.BindTo(beatmap);
|
||||
Track.BindTo(clock.Track);
|
||||
|
||||
background.Colour = colours.Gray1;
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +62,12 @@ namespace osu.Game.Screens.Edit.Components
|
||||
}
|
||||
};
|
||||
|
||||
Track?.AddAdjustment(AdjustableProperty.Tempo, tempo);
|
||||
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo);
|
||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo);
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
@ -22,6 +23,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
{
|
||||
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
protected readonly IBindable<Track> Track = new Bindable<Track>();
|
||||
|
||||
private readonly Container<T> content;
|
||||
|
||||
protected override Container<T> Content => content;
|
||||
@ -35,12 +38,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
updateRelativeChildSize();
|
||||
LoadBeatmap(b.NewValue);
|
||||
};
|
||||
|
||||
Track.ValueChanged += _ => updateRelativeChildSize();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, EditorClock clock)
|
||||
{
|
||||
Beatmap.BindTo(beatmap);
|
||||
Track.BindTo(clock.Track);
|
||||
}
|
||||
|
||||
private void updateRelativeChildSize()
|
||||
|
@ -43,6 +43,7 @@ using osuTK.Input;
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
[Cached]
|
||||
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider
|
||||
{
|
||||
public override float BackgroundParallaxAmount => 0.1f;
|
||||
@ -91,6 +92,9 @@ namespace osu.Game.Screens.Edit
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, GameHost host)
|
||||
{
|
||||
@ -98,9 +102,9 @@ namespace osu.Game.Screens.Edit
|
||||
beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue);
|
||||
|
||||
// Todo: should probably be done at a DrawableRuleset level to share logic with Player.
|
||||
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
|
||||
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
|
||||
clock.ChangeSource(sourceClock);
|
||||
|
||||
UpdateClockSource();
|
||||
|
||||
dependencies.CacheAs(clock);
|
||||
AddInternal(clock);
|
||||
@ -271,6 +275,15 @@ namespace osu.Game.Screens.Edit
|
||||
bottomBackground.Colour = colours.Gray2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the beatmap's track has changed, this method must be called to keep the editor in a valid state.
|
||||
/// </summary>
|
||||
public void UpdateClockSource()
|
||||
{
|
||||
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
|
||||
clock.ChangeSource(sourceClock);
|
||||
}
|
||||
|
||||
protected void Save()
|
||||
{
|
||||
// apply any set-level metadata changes.
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Utils;
|
||||
@ -17,7 +19,11 @@ namespace osu.Game.Screens.Edit
|
||||
/// </summary>
|
||||
public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock
|
||||
{
|
||||
public readonly double TrackLength;
|
||||
public IBindable<Track> Track => track;
|
||||
|
||||
private readonly Bindable<Track> track = new Bindable<Track>();
|
||||
|
||||
public double TrackLength => track.Value?.Length ?? 60000;
|
||||
|
||||
public ControlPointInfo ControlPointInfo;
|
||||
|
||||
@ -35,7 +41,6 @@ namespace osu.Game.Screens.Edit
|
||||
this.beatDivisor = beatDivisor;
|
||||
|
||||
ControlPointInfo = controlPointInfo;
|
||||
TrackLength = trackLength;
|
||||
|
||||
underlyingClock = new DecoupleableInterpolatingFramedClock();
|
||||
}
|
||||
@ -190,7 +195,11 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
|
||||
|
||||
public void ChangeSource(IClock source) => underlyingClock.ChangeSource(source);
|
||||
public void ChangeSource(IClock source)
|
||||
{
|
||||
track.Value = source as Track;
|
||||
underlyingClock.ChangeSource(source);
|
||||
}
|
||||
|
||||
public IClock Source => underlyingClock.Source;
|
||||
|
||||
|
@ -1,17 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
@ -23,6 +30,16 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private LabelledTextBox titleTextBox;
|
||||
private LabelledTextBox creatorTextBox;
|
||||
private LabelledTextBox difficultyTextBox;
|
||||
private LabelledTextBox audioTrackTextBox;
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private Editor editor { get; set; }
|
||||
|
||||
public SetupScreen()
|
||||
: base(EditorScreenMode.SongSetup)
|
||||
@ -32,6 +49,12 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Container audioTrackFileChooserContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -75,6 +98,18 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
},
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Resources"
|
||||
},
|
||||
audioTrackTextBox = new FileChooserLabelledTextBox
|
||||
{
|
||||
Label = "Audio Track",
|
||||
Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" },
|
||||
Target = audioTrackFileChooserContainer,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
audioTrackFileChooserContainer,
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Beatmap metadata"
|
||||
},
|
||||
@ -109,10 +144,47 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
}
|
||||
};
|
||||
|
||||
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
|
||||
|
||||
foreach (var item in flow.OfType<LabelledTextBox>())
|
||||
item.OnCommit += onCommit;
|
||||
}
|
||||
|
||||
public bool ChangeAudioTrack(string path)
|
||||
{
|
||||
var info = new FileInfo(path);
|
||||
|
||||
if (!info.Exists)
|
||||
return false;
|
||||
|
||||
var set = Beatmap.Value.BeatmapSetInfo;
|
||||
|
||||
// remove the previous audio track for now.
|
||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile);
|
||||
|
||||
using (var stream = info.OpenRead())
|
||||
{
|
||||
if (oldFile != null)
|
||||
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
|
||||
else
|
||||
beatmaps.AddFile(set, stream, info.Name);
|
||||
}
|
||||
|
||||
Beatmap.Value.Metadata.AudioFile = info.Name;
|
||||
|
||||
music.ReloadCurrentTrack();
|
||||
|
||||
editor?.UpdateClockSource();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void audioTrackChanged(ValueChangedEvent<string> filePath)
|
||||
{
|
||||
if (!ChangeAudioTrack(filePath.NewValue))
|
||||
audioTrackTextBox.Current.Value = filePath.OldValue;
|
||||
}
|
||||
|
||||
private void onCommit(TextBox sender, bool newText)
|
||||
{
|
||||
if (!newText) return;
|
||||
@ -125,4 +197,60 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class FileChooserLabelledTextBox : LabelledTextBox
|
||||
{
|
||||
public Container Target;
|
||||
|
||||
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||
|
||||
public FileChooserLabelledTextBox()
|
||||
{
|
||||
currentFile.BindValueChanged(onFileSelected);
|
||||
}
|
||||
|
||||
private void onFileSelected(ValueChangedEvent<FileInfo> file)
|
||||
{
|
||||
if (file.NewValue == null)
|
||||
return;
|
||||
|
||||
Target.Clear();
|
||||
Current.Value = file.NewValue.FullName;
|
||||
}
|
||||
|
||||
protected override OsuTextBox CreateTextBox() =>
|
||||
new FileChooserOsuTextBox
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
OnFocused = DisplayFileChooser
|
||||
};
|
||||
|
||||
public void DisplayFileChooser()
|
||||
{
|
||||
Target.Child = new FileSelector(validFileExtensions: new[] { ".mp3", ".ogg" })
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 400,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
CurrentFile = { BindTarget = currentFile }
|
||||
};
|
||||
}
|
||||
|
||||
internal class FileChooserOsuTextBox : OsuTextBox
|
||||
{
|
||||
public Action OnFocused;
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
OnFocused?.Invoke();
|
||||
base.OnFocus(e);
|
||||
|
||||
GetContainingInputManager().TriggerFocusContention(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,15 @@ namespace osu.Game.Tests.Visual
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
}
|
||||
|
||||
protected virtual bool EditorComponentsReady => Editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
||||
&& Editor.ChildrenOfType<TimelineArea>().FirstOrDefault()?.IsLoaded == true;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load editor", () => LoadScreen(Editor = CreateEditor()));
|
||||
AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
||||
&& Editor.ChildrenOfType<TimelineArea>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for editor to load", () => EditorComponentsReady);
|
||||
AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType<EditorBeatmap>().Single());
|
||||
AddStep("get clock", () => EditorClock = Editor.ChildrenOfType<EditorClock>().Single());
|
||||
}
|
||||
|
@ -909,6 +909,7 @@ private void load()
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=beatmaps/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=beatmap_0027s/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bindable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bindables/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Catmull/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Drawables/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=gameplay/@EntryIndexedValue">True</s:Boolean>
|
||||
|
Loading…
x
Reference in New Issue
Block a user