Merge branch 'master' into fix-slider-colours

This commit is contained in:
Dean Herbert
2018-06-28 22:36:19 +09:00
committed by GitHub
23 changed files with 223 additions and 154 deletions

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -17,6 +19,12 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")] [Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase public class TestCaseLeaderboard : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] {
typeof(Placeholder),
typeof(MessagePlaceholder),
typeof(RetrievalFailurePlaceholder),
};
private RulesetStore rulesets; private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard; private readonly FailableLeaderboard leaderboard;

View File

@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps
PostNotification?.Invoke(new SimpleNotification PostNotification?.Invoke(new SimpleNotification
{ {
Icon = FontAwesome.fa_superpowers, Icon = FontAwesome.fa_superpowers,
Text = "You gotta be a supporter to download for now 'yo" Text = "You gotta be an osu!supporter to download for now 'yo"
}); });
return; return;
} }

View File

@ -69,11 +69,22 @@ namespace osu.Game.Beatmaps
} }
catch catch
{ {
return new TrackVirtual(); return null;
} }
} }
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); protected override Waveform GetWaveform()
{
try
{
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData);
}
catch
{
return null;
}
}
protected override Storyboard GetStoryboard() protected override Storyboard GetStoryboard()
{ {

View File

@ -45,7 +45,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4"); protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4");
protected override Track GetTrack() => new TrackVirtual(); protected override Track GetTrack() => new TrackVirtual { Length = 1000 };
private class DummyRulesetInfo : RulesetInfo private class DummyRulesetInfo : RulesetInfo
{ {

View File

@ -19,7 +19,7 @@ using osu.Game.Skinning;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
public abstract class WorkingBeatmap : IDisposable public abstract partial class WorkingBeatmap : IDisposable
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly BeatmapInfo BeatmapInfo;
@ -145,7 +145,7 @@ namespace osu.Game.Beatmaps
private Track populateTrack() private Track populateTrack()
{ {
// we want to ensure that we always have a track, even if it's a fake one. // we want to ensure that we always have a track, even if it's a fake one.
var t = GetTrack() ?? new TrackVirtual(); var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
applyRateAdjustments(t); applyRateAdjustments(t);
return t; return t;
} }

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
public partial class WorkingBeatmap
{
/// <summary>
/// A type of <see cref="TrackVirtual"/> which provides a valid length based on the <see cref="HitObject"/>s of an <see cref="IBeatmap"/>.
/// </summary>
protected class VirtualBeatmapTrack : TrackVirtual
{
private const double excess_length = 1000;
public VirtualBeatmapTrack(IBeatmap beatmap)
{
var lastObject = beatmap.HitObjects.LastOrDefault();
switch (lastObject)
{
case null:
Length = excess_length;
break;
case IHasEndTime endTime:
Length = endTime.EndTime + excess_length;
break;
default:
Length = lastObject.StartTime + excess_length;
break;
}
}
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -16,6 +17,8 @@ namespace osu.Game.Graphics.Containers
protected override SpriteText CreateSpriteText() => new OsuSpriteText(); protected override SpriteText CreateSpriteText() => new OsuSpriteText();
public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable);
public void AddIcon(FontAwesome icon, Action<SpriteText> creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); public void AddIcon(FontAwesome icon, Action<SpriteText> creationParameters = null) => AddText(((char)icon).ToString(), creationParameters);
} }
} }

View File

@ -12,14 +12,14 @@ namespace osu.Game.Graphics
{ {
public class DrawableDate : OsuSpriteText, IHasTooltip public class DrawableDate : OsuSpriteText, IHasTooltip
{ {
private readonly DateTimeOffset date; protected readonly DateTimeOffset Date;
public DrawableDate(DateTimeOffset date) public DrawableDate(DateTimeOffset date)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Font = "Exo2.0-RegularItalic"; Font = "Exo2.0-RegularItalic";
this.date = date.ToLocalTime(); Date = date.ToLocalTime();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -38,7 +38,7 @@ namespace osu.Game.Graphics
{ {
updateTime(); updateTime();
var diffToNow = DateTimeOffset.Now.Subtract(date); var diffToNow = DateTimeOffset.Now.Subtract(Date);
double timeUntilNextUpdate = 1000; double timeUntilNextUpdate = 1000;
if (diffToNow.TotalSeconds > 60) if (diffToNow.TotalSeconds > 60)
@ -58,8 +58,10 @@ namespace osu.Game.Graphics
public override bool HandleMouseInput => true; public override bool HandleMouseInput => true;
private void updateTime() => Text = date.Humanize(); protected virtual string Format() => Date.Humanize();
public string TooltipText => date.ToString(); private void updateTime() => Text = Format();
public virtual string TooltipText => string.Format($"{Date:MMMM d, yyyy h:mm tt \"UTC\"z}");
} }
} }

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Graphics;
namespace osu.Game.Overlays.Profile.Components
{
public class DrawableJoinDate : DrawableDate
{
public DrawableJoinDate(DateTimeOffset date)
: base(date)
{
}
protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}";
public override string TooltipText => $"{Date:MMMM d, yyyy}";
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Profile.Components
{
public class GradeBadge : Container
{
private const float width = 50;
private readonly string grade;
private readonly Sprite badge;
private readonly SpriteText numberText;
public int DisplayCount
{
set => numberText.Text = value.ToString(@"#,0");
}
public GradeBadge(string grade)
{
this.grade = grade;
Width = width;
Height = 41;
Add(badge = new Sprite
{
Width = width,
Height = 26
});
Add(numberText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
TextSize = 14,
Font = @"Exo2.0-Bold"
});
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
badge.Texture = textures.Get($"Grades/{grade}");
}
}
}

View File

@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Profile.Components;
using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header;
using osu.Game.Users; using osu.Game.Users;
@ -375,12 +376,12 @@ namespace osu.Game.Overlays.Profile
if (user.JoinDate.ToUniversalTime().Year < 2008) if (user.JoinDate.ToUniversalTime().Year < 2008)
{ {
infoTextLeft.AddText("Here since the beginning", boldItalic); infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText);
} }
else else
{ {
infoTextLeft.AddText("Joined ", lightText); infoTextLeft.AddText("Joined ", lightText);
infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic); infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic);
} }
infoTextLeft.NewLine(); infoTextLeft.NewLine();
@ -470,43 +471,5 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine(); infoTextRight.NewLine();
} }
private class GradeBadge : Container
{
private const float width = 50;
private readonly string grade;
private readonly Sprite badge;
private readonly SpriteText numberText;
public int DisplayCount
{
set { numberText.Text = value.ToString(@"#,0"); }
}
public GradeBadge(string grade)
{
this.grade = grade;
Width = width;
Height = 41;
Add(badge = new Sprite
{
Width = width,
Height = 26
});
Add(numberText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
TextSize = 14,
Font = @"Exo2.0-Bold"
});
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
badge.Texture = textures.Get($"Grades/{grade}");
}
}
} }
} }

View File

@ -141,11 +141,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
break; break;
case RecentActivityType.UserSupportFirst: case RecentActivityType.UserSupportFirst:
message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!"; message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!";
break; break;
case RecentActivityType.UserSupportGift: case RecentActivityType.UserSupportGift:
message = $"{userLinkTemplate()} has received the gift of osu! supporter!"; message = $"{userLinkTemplate()} has received the gift of osu!supporter!";
break; break;
case RecentActivityType.UsernameChange: case RecentActivityType.UsernameChange:

View File

@ -84,10 +84,11 @@ namespace osu.Game.Rulesets
{ {
try try
{ {
var instance = r.CreateInstance(); var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo), (RulesetInfo)null)).RulesetInfo;
r.Name = instance.Description; r.Name = instanceInfo.Name;
r.ShortName = instance.ShortName; r.ShortName = instanceInfo.ShortName;
r.InstantiationInfo = instanceInfo.InstantiationInfo;
r.Available = true; r.Available = true;
} }

View File

@ -52,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
if (Beatmap.Value == null) if (Beatmap.Value == null)
return; return;
if (Beatmap.Value.Track.Length == double.PositiveInfinity) return;
float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length);
} }

View File

@ -47,8 +47,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return; return;
} }
// Todo: This should be handled more gracefully timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
} }
protected void Add(Drawable visualisation) => timeline.Add(visualisation); protected void Add(Drawable visualisation) => timeline.Add(visualisation);

View File

@ -52,13 +52,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap); Beatmap.BindTo(beatmap);
} Beatmap.BindValueChanged(b =>
protected override void LoadComplete()
{ {
base.LoadComplete(); waveform.Waveform = b.Waveform;
Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform); track = b.Track;
waveform.Waveform = Beatmap.Value.Waveform; }, true);
} }
/// <summary> /// <summary>
@ -81,6 +79,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
/// </summary> /// </summary>
private bool trackWasPlaying; private bool trackWasPlaying;
private Track track;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -118,21 +118,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
private void seekTrackToCurrent() private void seekTrackToCurrent()
{ {
var track = Beatmap.Value.Track; if (!track.IsLoaded)
if (track is TrackVirtual || !track.IsLoaded)
return; return;
if (!(Beatmap.Value.Track is TrackVirtual)) adjustableClock.Seek(Current / Content.DrawWidth * track.Length);
adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
} }
private void scrollToTrackTime() private void scrollToTrackTime()
{ {
var track = Beatmap.Value.Track; if (!track.IsLoaded)
if (track is TrackVirtual || !track.IsLoaded)
return; return;
ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false); ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

View File

@ -146,7 +146,7 @@ namespace osu.Game.Screens.Select.Leaderboards
replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!"));
break; break;
case PlaceholderState.NotSupporter: case PlaceholderState.NotSupporter:
replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"));
break; break;
default: default:
replacePlaceholder(null); replacePlaceholder(null);
@ -331,23 +331,4 @@ namespace osu.Game.Screens.Select.Leaderboards
} }
} }
} }
public enum LeaderboardScope
{
Local,
Country,
Global,
Friend,
}
public enum PlaceholderState
{
Successful,
Retrieving,
NetworkFailure,
Unavailable,
NoScores,
NotLoggedIn,
NotSupporter,
}
} }

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Select.Leaderboards
{
public enum LeaderboardScope
{
Local,
Country,
Global,
Friend,
}
}

View File

@ -2,10 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
{ {
@ -15,22 +12,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public MessagePlaceholder(string message) public MessagePlaceholder(string message)
{ {
Direction = FillDirection.Horizontal; AddIcon(FontAwesome.fa_exclamation_circle, cp =>
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{ {
new SpriteIcon cp.TextSize = TEXT_SIZE;
{ cp.Padding = new MarginPadding { Right = 10 };
Icon = FontAwesome.fa_exclamation_circle, });
Size = new Vector2(26),
Margin = new MarginPadding { Right = 10 }, AddText(this.message = message);
},
new OsuSpriteText
{
Text = this.message = message,
TextSize = 22,
},
};
} }
public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message; public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;

View File

@ -3,16 +3,27 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
{ {
public abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder> public abstract class Placeholder : OsuTextFlowContainer, IEquatable<Placeholder>
{ {
protected const float TEXT_SIZE = 22;
public override bool HandleMouseInput => true;
protected Placeholder() protected Placeholder()
: base(cp => cp.TextSize = TEXT_SIZE)
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
TextAnchor = Anchor.TopCentre;
Padding = new MarginPadding(20);
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
} }
public virtual bool Equals(Placeholder other) => GetType() == other?.GetType(); public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Select.Leaderboards
{
public enum PlaceholderState
{
Successful,
Retrieving,
NetworkFailure,
Unavailable,
NoScores,
NotLoggedIn,
NotSupporter,
}
}

View File

@ -3,11 +3,9 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
@ -18,22 +16,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetrievalFailurePlaceholder() public RetrievalFailurePlaceholder()
{ {
Direction = FillDirection.Horizontal; AddArbitraryDrawable(new RetryButton
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new RetryButton
{ {
Action = () => OnRetry?.Invoke(), Action = () => OnRetry?.Invoke(),
Margin = new MarginPadding { Right = 10 }, Padding = new MarginPadding { Right = 10 }
}, });
new OsuSpriteText
{ AddText(@"Couldn't retrieve scores!");
Anchor = Anchor.TopLeft,
Text = @"Couldn't retrieve scores!",
TextSize = 22,
},
};
} }
public class RetryButton : OsuHoverContainer public class RetryButton : OsuHoverContainer
@ -44,18 +33,16 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetryButton() public RetryButton()
{ {
Height = 26; AutoSizeAxes = Axes.Both;
Width = 26;
Child = new OsuClickableContainer Child = new OsuClickableContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => Action?.Invoke(), Action = () => Action?.Invoke(),
Child = icon = new SpriteIcon Child = icon = new SpriteIcon
{ {
Icon = FontAwesome.fa_refresh, Icon = FontAwesome.fa_refresh,
Size = new Vector2(26), Size = new Vector2(TEXT_SIZE),
Shadow = true, Shadow = true,
}, },
}; };

View File

@ -1,12 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Tests.Beatmaps namespace osu.Game.Tests.Beatmaps
{ {
@ -26,21 +24,6 @@ namespace osu.Game.Tests.Beatmaps
private readonly IBeatmap beatmap; private readonly IBeatmap beatmap;
protected override IBeatmap GetBeatmap() => beatmap; protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override Track GetTrack() => null;
protected override Track GetTrack()
{
var lastObject = beatmap.HitObjects.LastOrDefault();
if (lastObject != null)
return new TestTrack(((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime) + 1000);
return new TrackVirtual();
}
private class TestTrack : TrackVirtual
{
public TestTrack(double length)
{
Length = length;
}
}
} }
} }