mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Noise brush - Greatly improved performance on large layouts
Profiles core - Pass real delta time brushes/effects Profiles core - Allow main segment to be disabled on layers General module - Removed test data model General module - Removed expensive open window scan, was fun to try :P Profile editor - Refactored segments Profile editor - Updated segment visuals to scale well when really narrow
This commit is contained in:
parent
72d606f40d
commit
2045b230c2
@ -70,12 +70,12 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
|
||||
// to it's start
|
||||
var timelineDeltaTime = UpdateTimeline(deltaTime);
|
||||
UpdateTimeline(deltaTime);
|
||||
|
||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||
{
|
||||
baseLayerEffect.BaseProperties?.Update();
|
||||
baseLayerEffect.Update(timelineDeltaTime);
|
||||
baseLayerEffect.Update(deltaTime);
|
||||
}
|
||||
|
||||
// Iterate the children in reverse because that's how they must be rendered too
|
||||
|
||||
@ -70,7 +70,6 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
||||
ApplyRenderElementEntity();
|
||||
ApplyRenderElementDefaults();
|
||||
}
|
||||
|
||||
internal LayerEntity LayerEntity { get; set; }
|
||||
@ -224,17 +223,17 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
// Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back
|
||||
// to it's start
|
||||
var timelineDeltaTime = UpdateTimeline(deltaTime);
|
||||
UpdateTimeline(deltaTime);
|
||||
|
||||
General.Update();
|
||||
Transform.Update();
|
||||
LayerBrush.BaseProperties?.Update();
|
||||
LayerBrush.Update(timelineDeltaTime);
|
||||
LayerBrush.Update(deltaTime);
|
||||
|
||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||
{
|
||||
baseLayerEffect.BaseProperties?.Update();
|
||||
baseLayerEffect.Update(timelineDeltaTime);
|
||||
baseLayerEffect.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +255,10 @@ namespace Artemis.Core.Models.Profile
|
||||
else
|
||||
{
|
||||
var progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds;
|
||||
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
|
||||
if (progress > 0)
|
||||
TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength;
|
||||
else
|
||||
TimelinePosition = StartSegmentLength;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -437,6 +439,39 @@ namespace Artemis.Core.Models.Profile
|
||||
return position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
|
||||
/// layer translations out
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public void ExcludePathFromTranslation(SKPath path)
|
||||
{
|
||||
var sizeProperty = Transform.Scale.CurrentValue;
|
||||
var rotationProperty = Transform.Rotation.CurrentValue;
|
||||
|
||||
var anchorPosition = GetLayerAnchorPosition(Path);
|
||||
var anchorProperty = Transform.AnchorPoint.CurrentValue;
|
||||
|
||||
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
||||
var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width;
|
||||
var y = anchorPosition.Y - Bounds.MidY - anchorProperty.Y * Bounds.Height;
|
||||
|
||||
var reversedXScale = 1f / (sizeProperty.Width / 100f);
|
||||
var reversedYScale = 1f / (sizeProperty.Height / 100f);
|
||||
|
||||
if (General.FillType == LayerFillType.Stretch)
|
||||
{
|
||||
path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y));
|
||||
path.Transform(SKMatrix.MakeScale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y));
|
||||
path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y));
|
||||
path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LED management
|
||||
|
||||
@ -17,8 +17,7 @@ namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
protected void ApplyRenderElementDefaults()
|
||||
{
|
||||
if (MainSegmentLength <= TimeSpan.Zero)
|
||||
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
protected void ApplyRenderElementEntity()
|
||||
|
||||
@ -172,130 +172,62 @@
|
||||
|
||||
<!-- Timeline headers -->
|
||||
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Caret -->
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.StartSegmentWidth}" />
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.MainSegmentWidth}" />
|
||||
<ColumnDefinition Width="{Binding TimelineViewModel.EndSegmentWidth}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Canvas Background="{DynamicResource MaterialDesignCardBackground}" Width="{Binding ActualWidth, ElementName=PropertyTimeLine}">
|
||||
<!-- Timeline segments -->
|
||||
<ContentControl Canvas.Left="{Binding EndTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding EndTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding MainTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding MainTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding StartTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding StartTimelineSegmentViewModel}" />
|
||||
|
||||
<Canvas Grid.ColumnSpan="3"
|
||||
Panel.ZIndex="2"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<Polygon Points="-8,0 -8,8 0,20, 8,8 8,0" Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
<!-- Timeline caret -->
|
||||
<Polygon Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||
Fill="{StaticResource SecondaryAccentBrush}" />
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
|
||||
<timeline:PropertyTimelineHeader Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0 25 0 0"
|
||||
|
||||
<!-- Timeline header body -->
|
||||
<timeline:PropertyTimelineHeader Margin="0 25 0 0"
|
||||
Fill="{DynamicResource MaterialDesignBody}"
|
||||
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
|
||||
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
|
||||
OffsetFirstValue="True"
|
||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
|
||||
|
||||
<!-- Start segment -->
|
||||
<Grid Grid.Column="0" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
|
||||
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played when a layer starts displaying because it's conditions are met">
|
||||
Start
|
||||
</TextBlock>
|
||||
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Disable start segment" Width="20" Height="20" Margin="0 0 6 0"
|
||||
Command="{s:Action DisableSegment}" CommandParameter="Start">
|
||||
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Main segment -->
|
||||
<Grid Grid.Column="1" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
|
||||
Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played while a condition is met, either once or on a repeating loop">
|
||||
Main
|
||||
</TextBlock>
|
||||
|
||||
<ToggleButton Grid.Column="1" Style="{StaticResource MaterialDesignFlatToggleButton}" ToolTip="Toggle main segment repeat" Width="16" Height="16"
|
||||
IsChecked="{Binding RepeatMainSegment}" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="Repeat" Height="12" Width="12" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button Grid.Column="2" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Remove" Width="20" Height="20" Margin="5 0 8 0"
|
||||
Command="{s:Action DisableSegment}" CommandParameter="Main">
|
||||
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- End segment -->
|
||||
<Grid Grid.Column="2" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
|
||||
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played once a condition is no longer met">
|
||||
End
|
||||
</TextBlock>
|
||||
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Disable end segment" Width="20" Height="20" Margin="0 0 6 0"
|
||||
Command="{s:Action DisableSegment}" CommandParameter="End">
|
||||
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Segment movement display -->
|
||||
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||
|
||||
<!-- Segment movement handles -->
|
||||
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
MouseDown="{s:Action StartSegmentMouseDown}" MouseUp="{s:Action StartSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
|
||||
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
MouseDown="{s:Action MainSegmentMouseDown}" MouseUp="{s:Action MainSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
|
||||
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
MouseDown="{s:Action EndSegmentMouseDown}" MouseUp="{s:Action EndSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
|
||||
</Grid>
|
||||
</Canvas>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Timeline rails -->
|
||||
<ScrollViewer x:Name="TimelineRailsScrollViewer"
|
||||
Grid.Row="1"
|
||||
Style="{StaticResource SvStyle}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<ScrollViewer x:Name="TimelineRailsScrollViewer" Grid.Row="1" Style="{StaticResource SvStyle}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}">
|
||||
<Canvas Grid.Column="0"
|
||||
Panel.ZIndex="1"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
<Canvas Grid.Column="0" Panel.ZIndex="1">
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||
</Canvas>
|
||||
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" />
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" x:Name="PropertyTimeLine" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
@ -303,8 +235,8 @@
|
||||
<!-- Bottom row, a bit hacky but has a ZIndex of 2 to cut off the time caret that overlaps the entire timeline -->
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Panel.ZIndex="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Selected layer controls -->
|
||||
<Grid.ColumnDefinitions>
|
||||
@ -409,15 +341,15 @@
|
||||
<MenuItem Header="Start"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Start"
|
||||
IsEnabled="{Binding Data.StartSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
IsEnabled="{Binding Data.StartTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="Main"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Main"
|
||||
IsEnabled="{Binding Data.MainSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
IsEnabled="{Binding Data.MainTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="End"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="End"
|
||||
IsEnabled="{Binding Data.EndSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
IsEnabled="{Binding Data.EndTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<TextBlock FontSize="11">
|
||||
|
||||
@ -35,6 +35,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
private RenderProfileElement _selectedProfileElement;
|
||||
private TimelineViewModel _timelineViewModel;
|
||||
private TreeViewModel _treeViewModel;
|
||||
private TimelineSegmentViewModel _startTimelineSegmentViewModel;
|
||||
private TimelineSegmentViewModel _mainTimelineSegmentViewModel;
|
||||
private TimelineSegmentViewModel _endTimelineSegmentViewModel;
|
||||
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService,
|
||||
ILayerPropertyVmFactory layerPropertyVmFactory)
|
||||
@ -68,10 +71,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
|
||||
public string FormattedCurrentTime => $"{Math.Floor(ProfileEditorService.CurrentTime.TotalSeconds):00}.{ProfileEditorService.CurrentTime.Milliseconds:000}";
|
||||
|
||||
public Thickness TimeCaretPosition
|
||||
public double TimeCaretPosition
|
||||
{
|
||||
get => new Thickness(ProfileEditorService.CurrentTime.TotalSeconds * ProfileEditorService.PixelsPerSecond, 0, 0, 0);
|
||||
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / ProfileEditorService.PixelsPerSecond);
|
||||
get => ProfileEditorService.CurrentTime.TotalSeconds * ProfileEditorService.PixelsPerSecond;
|
||||
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value / ProfileEditorService.PixelsPerSecond);
|
||||
}
|
||||
|
||||
public int PropertyTreeIndex
|
||||
@ -94,8 +97,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
if (!SetAndNotify(ref _selectedProfileElement, value)) return;
|
||||
NotifyOfPropertyChange(nameof(SelectedLayer));
|
||||
NotifyOfPropertyChange(nameof(SelectedFolder));
|
||||
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
|
||||
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +128,24 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
set => SetAndNotify(ref _timelineViewModel, value);
|
||||
}
|
||||
|
||||
public TimelineSegmentViewModel StartTimelineSegmentViewModel
|
||||
{
|
||||
get => _startTimelineSegmentViewModel;
|
||||
set => SetAndNotify(ref _startTimelineSegmentViewModel, value);
|
||||
}
|
||||
|
||||
public TimelineSegmentViewModel MainTimelineSegmentViewModel
|
||||
{
|
||||
get => _mainTimelineSegmentViewModel;
|
||||
set => SetAndNotify(ref _mainTimelineSegmentViewModel, value);
|
||||
}
|
||||
|
||||
public TimelineSegmentViewModel EndTimelineSegmentViewModel
|
||||
{
|
||||
get => _endTimelineSegmentViewModel;
|
||||
set => SetAndNotify(ref _endTimelineSegmentViewModel, value);
|
||||
}
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
||||
@ -227,11 +246,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
|
||||
TimelineViewModel?.Dispose();
|
||||
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups);
|
||||
StartTimelineSegmentViewModel?.Dispose();
|
||||
StartTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.Start);
|
||||
MainTimelineSegmentViewModel?.Dispose();
|
||||
MainTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.Main);
|
||||
EndTimelineSegmentViewModel?.Dispose();
|
||||
EndTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.End);
|
||||
|
||||
ApplyLayerBrush();
|
||||
ApplyEffects();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e)
|
||||
{
|
||||
ApplyLayerBrush();
|
||||
@ -564,163 +591,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
#endregion
|
||||
|
||||
#region Segments
|
||||
|
||||
public bool StartSegmentEnabled
|
||||
{
|
||||
get => SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero;
|
||||
set
|
||||
{
|
||||
SelectedProfileElement.StartSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanAddSegment));
|
||||
}
|
||||
}
|
||||
|
||||
public bool MainSegmentEnabled
|
||||
{
|
||||
get => SelectedProfileElement?.MainSegmentLength != TimeSpan.Zero;
|
||||
set
|
||||
{
|
||||
SelectedProfileElement.MainSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
NotifyOfPropertyChange(nameof(MainSegmentEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanAddSegment));
|
||||
}
|
||||
}
|
||||
|
||||
public bool EndSegmentEnabled
|
||||
{
|
||||
get => SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero;
|
||||
set
|
||||
{
|
||||
SelectedProfileElement.EndSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanAddSegment));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanAddSegment => !StartSegmentEnabled || !MainSegmentEnabled || !EndSegmentEnabled;
|
||||
|
||||
public bool RepeatMainSegment
|
||||
{
|
||||
get => SelectedProfileElement?.RepeatMainSegment ?? false;
|
||||
set
|
||||
{
|
||||
SelectedProfileElement.RepeatMainSegment = value;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
NotifyOfPropertyChange(nameof(RepeatMainSegment));
|
||||
}
|
||||
}
|
||||
|
||||
private bool _draggingStartSegment;
|
||||
private bool _draggingMainSegment;
|
||||
private bool _draggingEndSegment;
|
||||
|
||||
public void DisableSegment(string segment)
|
||||
{
|
||||
if (segment == "Start")
|
||||
StartSegmentEnabled = false;
|
||||
else if (segment == "Main")
|
||||
MainSegmentEnabled = false;
|
||||
else if (segment == "End")
|
||||
EndSegmentEnabled = false;
|
||||
}
|
||||
|
||||
public void EnableSegment(string segment)
|
||||
{
|
||||
if (segment == "Start")
|
||||
StartSegmentEnabled = true;
|
||||
StartTimelineSegmentViewModel.EnableSegment();
|
||||
else if (segment == "Main")
|
||||
MainSegmentEnabled = true;
|
||||
MainTimelineSegmentViewModel.EnableSegment();
|
||||
else if (segment == "End")
|
||||
EndSegmentEnabled = true;
|
||||
}
|
||||
|
||||
public void StartSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
_draggingStartSegment = true;
|
||||
}
|
||||
|
||||
public void StartSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
_draggingStartSegment = false;
|
||||
}
|
||||
|
||||
public void MainSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
_draggingMainSegment = true;
|
||||
}
|
||||
|
||||
public void MainSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
_draggingMainSegment = false;
|
||||
}
|
||||
|
||||
public void EndSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
_draggingEndSegment = true;
|
||||
}
|
||||
|
||||
public void EndSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
_draggingEndSegment = false;
|
||||
}
|
||||
|
||||
public void SegmentMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
// Get the parent grid, need that for our position
|
||||
var parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||
var x = Math.Max(0, e.GetPosition(parent).X);
|
||||
var newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||
|
||||
// If holding down shift, snap to the closest element on the timeline
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, true);
|
||||
}
|
||||
// If holding down control, round to the closest 50ms
|
||||
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
||||
}
|
||||
|
||||
if (_draggingStartSegment)
|
||||
{
|
||||
if (newTime < TimeSpan.FromMilliseconds(100))
|
||||
newTime = TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.StartSegmentLength = newTime;
|
||||
}
|
||||
else if (_draggingMainSegment)
|
||||
{
|
||||
if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||
newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength;
|
||||
}
|
||||
else if (_draggingEndSegment)
|
||||
{
|
||||
if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||
newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
|
||||
}
|
||||
}
|
||||
EndTimelineSegmentViewModel.EnableSegment();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.TimelineSegmentView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:TimelineSegmentViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||
</UserControl.Resources>
|
||||
<StackPanel Orientation="Horizontal" Visibility="{Binding SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<StackPanel.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Repeat main segment"
|
||||
IsCheckable="True"
|
||||
IsChecked="{Binding Data.RepeatSegment, Source={StaticResource DataContextProxy}}"
|
||||
Visibility="{Binding Data.IsMainSegment, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Source={StaticResource DataContextProxy}}"/>
|
||||
<MenuItem Header="Disable segment" Command="{s:Action DisableSegment}"/>
|
||||
</ContextMenu>
|
||||
</StackPanel.ContextMenu>
|
||||
|
||||
<!-- Segment content -->
|
||||
<Grid VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}" Width="{Binding SegmentWidth}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock TextAlignment="Center"
|
||||
Padding="0 3"
|
||||
Margin="5 0 0 0"
|
||||
FontSize="12"
|
||||
ToolTip="{Binding ToolTip}"
|
||||
Text="{Binding Segment}"
|
||||
ClipToBounds="False">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowSegmentName}" Value="False">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:0.25">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<ToggleButton Grid.Column="1"
|
||||
ToolTip="Toggle main segment repeat"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IsChecked="{Binding RepeatSegment}"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding IsMainSegment, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<ToggleButton.Style>
|
||||
<Style TargetType="ToggleButton" BasedOn="{StaticResource MaterialDesignFlatToggleButton}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowRepeatButton}" Value="False">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:0.25">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" >
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:0.25" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
<materialDesign:PackIcon Kind="Repeat" Height="12" Width="12" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button Grid.Column="2" ToolTip="Disable segment" Width="20" Height="20" Margin="0 0 5 0" Command="{s:Action DisableSegment}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource MaterialDesignIconButton}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowDisableButton}" Value="False">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:0.25">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Segment movement display -->
|
||||
<Rectangle RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="-2 -2 0 0" Width="5" Height="24" VerticalAlignment="Top" HorizontalAlignment="Right" />
|
||||
|
||||
<!-- Segment movement handles -->
|
||||
<Rectangle RadiusX="2"
|
||||
RadiusY="2"
|
||||
Fill="Transparent"
|
||||
Margin="-9 -2 0 0"
|
||||
Width="12"
|
||||
Height="25"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action SegmentMouseDown}"
|
||||
MouseUp="{s:Action SegmentMouseUp}"
|
||||
MouseMove="{s:Action SegmentMouseMove}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelineSegmentViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
private bool _draggingSegment;
|
||||
private bool _showSegmentName;
|
||||
private bool _showRepeatButton;
|
||||
private bool _showDisableButton;
|
||||
|
||||
public TimelineSegmentViewModel(IProfileEditorService profileEditorService, SegmentViewModelType segment)
|
||||
{
|
||||
ProfileEditorService = profileEditorService;
|
||||
Segment = segment;
|
||||
SelectedProfileElement = ProfileEditorService.SelectedProfileElement;
|
||||
|
||||
switch (Segment)
|
||||
{
|
||||
case SegmentViewModelType.Start:
|
||||
ToolTip = "This segment is played when a layer starts displaying because it's conditions are met";
|
||||
break;
|
||||
case SegmentViewModelType.Main:
|
||||
ToolTip = "This segment is played while a condition is met, either once or on a repeating loop";
|
||||
break;
|
||||
case SegmentViewModelType.End:
|
||||
ToolTip = "This segment is played once a condition is no longer met";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(segment));
|
||||
}
|
||||
|
||||
UpdateDisplay();
|
||||
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
|
||||
}
|
||||
|
||||
public RenderProfileElement SelectedProfileElement { get; }
|
||||
|
||||
public SegmentViewModelType Segment { get; }
|
||||
public IProfileEditorService ProfileEditorService { get; }
|
||||
public string ToolTip { get; }
|
||||
|
||||
public TimeSpan SegmentLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return Segment switch
|
||||
{
|
||||
SegmentViewModelType.Start => SelectedProfileElement?.StartSegmentLength ?? TimeSpan.Zero,
|
||||
SegmentViewModelType.Main => SelectedProfileElement?.MainSegmentLength ?? TimeSpan.Zero,
|
||||
SegmentViewModelType.End => SelectedProfileElement?.EndSegmentLength ?? TimeSpan.Zero,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public double SegmentWidth => ProfileEditorService.PixelsPerSecond * SegmentLength.TotalSeconds;
|
||||
|
||||
public bool SegmentEnabled => SegmentLength != TimeSpan.Zero;
|
||||
public bool IsMainSegment => Segment == SegmentViewModelType.Main;
|
||||
|
||||
// Only the main segment supports this, for any other segment the getter always returns false and the setter does nothing
|
||||
public bool RepeatSegment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Segment != SegmentViewModelType.Main)
|
||||
return false;
|
||||
|
||||
return SelectedProfileElement?.RepeatMainSegment ?? false;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Segment != SegmentViewModelType.Main)
|
||||
return;
|
||||
|
||||
SelectedProfileElement.RepeatMainSegment = value;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
NotifyOfPropertyChange(nameof(RepeatSegment));
|
||||
}
|
||||
}
|
||||
|
||||
public double SegmentStartPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return Segment switch
|
||||
{
|
||||
SegmentViewModelType.Start => 0,
|
||||
SegmentViewModelType.Main => ProfileEditorService.PixelsPerSecond * SelectedProfileElement.StartSegmentLength.TotalSeconds,
|
||||
SegmentViewModelType.End => ProfileEditorService.PixelsPerSecond * (SelectedProfileElement.StartSegmentLength.TotalSeconds + SelectedProfileElement.MainSegmentLength.TotalSeconds),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSegmentName
|
||||
{
|
||||
get => _showSegmentName;
|
||||
set => SetAndNotify(ref _showSegmentName, value);
|
||||
}
|
||||
|
||||
public bool ShowRepeatButton
|
||||
{
|
||||
get => _showRepeatButton;
|
||||
set => SetAndNotify(ref _showRepeatButton, value);
|
||||
}
|
||||
|
||||
public bool ShowDisableButton
|
||||
{
|
||||
get => _showDisableButton;
|
||||
set => SetAndNotify(ref _showDisableButton, value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
|
||||
}
|
||||
|
||||
public void DisableSegment()
|
||||
{
|
||||
switch (Segment)
|
||||
{
|
||||
case SegmentViewModelType.Start:
|
||||
SelectedProfileElement.StartSegmentLength = TimeSpan.Zero;
|
||||
break;
|
||||
case SegmentViewModelType.Main:
|
||||
SelectedProfileElement.MainSegmentLength = TimeSpan.Zero;
|
||||
break;
|
||||
case SegmentViewModelType.End:
|
||||
SelectedProfileElement.EndSegmentLength = TimeSpan.Zero;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(nameof(SegmentEnabled));
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
public void EnableSegment()
|
||||
{
|
||||
switch (Segment)
|
||||
{
|
||||
case SegmentViewModelType.Start:
|
||||
SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1);
|
||||
break;
|
||||
case SegmentViewModelType.Main:
|
||||
SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1);
|
||||
break;
|
||||
case SegmentViewModelType.End:
|
||||
SelectedProfileElement.EndSegmentLength = TimeSpan.FromSeconds(1);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(nameof(SegmentEnabled));
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
public void SegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
_draggingSegment = true;
|
||||
}
|
||||
|
||||
public void SegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
_draggingSegment = false;
|
||||
}
|
||||
|
||||
public void SegmentMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton != MouseButtonState.Pressed || !_draggingSegment)
|
||||
return;
|
||||
|
||||
// Get the parent scroll viewer, need that for our position
|
||||
var parent = VisualTreeUtilities.FindParent<ScrollViewer>((DependencyObject) sender, "TimelineHeaderScrollViewer");
|
||||
|
||||
var x = Math.Max(0, e.GetPosition(parent).X);
|
||||
var newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||
|
||||
// If holding down shift, snap to the closest element on the timeline
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, true);
|
||||
// If holding down control, round to the closest 50ms
|
||||
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
||||
|
||||
|
||||
if (Segment == SegmentViewModelType.Start)
|
||||
{
|
||||
if (newTime < TimeSpan.FromMilliseconds(100))
|
||||
newTime = TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.StartSegmentLength = newTime;
|
||||
}
|
||||
else if (Segment == SegmentViewModelType.Main)
|
||||
{
|
||||
if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||
newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength;
|
||||
}
|
||||
else if (Segment == SegmentViewModelType.End)
|
||||
{
|
||||
if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||
newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||
SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(nameof(SegmentLength));
|
||||
NotifyOfPropertyChange(nameof(SegmentWidth));
|
||||
|
||||
UpdateDisplay();
|
||||
}
|
||||
|
||||
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) ||
|
||||
e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) ||
|
||||
e.PropertyName == nameof(RenderProfileElement.EndSegmentLength))
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(SegmentStartPosition));
|
||||
NotifyOfPropertyChange(nameof(SegmentWidth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(SegmentWidth));
|
||||
NotifyOfPropertyChange(nameof(SegmentStartPosition));
|
||||
|
||||
UpdateDisplay();
|
||||
}
|
||||
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
if (!IsMainSegment)
|
||||
ShowSegmentName = SegmentWidth > 60;
|
||||
else
|
||||
ShowSegmentName = SegmentWidth > 80;
|
||||
|
||||
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
|
||||
ShowDisableButton = SegmentWidth > 25;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SegmentViewModelType
|
||||
{
|
||||
Start,
|
||||
Main,
|
||||
End
|
||||
}
|
||||
}
|
||||
@ -73,6 +73,19 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
var height = (int) Math.Floor(path.Bounds.Height * _renderScale);
|
||||
|
||||
CreateBitmap(width, height);
|
||||
|
||||
var clipPath = new SKPath(Layer.Path);
|
||||
Layer.ExcludePathFromTranslation(clipPath);
|
||||
clipPath = new SKPath(clipPath);
|
||||
clipPath.Transform(SKMatrix.MakeTranslation(Layer.Path.Bounds.Left * -1, Layer.Path.Bounds.Top * -1));
|
||||
|
||||
// Fill a canvas matching the final area that will be rendered
|
||||
using var bitmapCanvas = new SKCanvas(_bitmap);
|
||||
using var clipPaint = new SKPaint {Color = new SKColor(0, 0, 0, 255)};
|
||||
bitmapCanvas.Clear();
|
||||
bitmapCanvas.Scale(_renderScale);
|
||||
bitmapCanvas.DrawPath(clipPath, clipPaint);
|
||||
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
@ -84,6 +97,10 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
if (double.IsInfinity(evalX) || double.IsNaN(evalX) || double.IsNaN(evalY) || double.IsInfinity(evalY))
|
||||
continue;
|
||||
|
||||
var pixel = _bitmap.GetPixel(x, y);
|
||||
if (pixel.Alpha != 255)
|
||||
continue;
|
||||
|
||||
var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness;
|
||||
var amount = Math.Max(0f, Math.Min(1f, v));
|
||||
if (Properties.ColorType.BaseValue == ColorMappingType.Simple)
|
||||
|
||||
@ -1,62 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Artemis.Core.Plugins.Abstract.DataModels.Attributes;
|
||||
using Artemis.Plugins.Modules.General.DataModel.Windows;
|
||||
using SkiaSharp;
|
||||
using Artemis.Plugins.Modules.General.DataModel.Windows;
|
||||
|
||||
namespace Artemis.Plugins.Modules.General.DataModel
|
||||
{
|
||||
public class GeneralDataModel : Core.Plugins.Abstract.DataModels.DataModel
|
||||
{
|
||||
public TestDataModel TestDataModel { get; set; }
|
||||
public WindowsDataModel Windows { get; set; }
|
||||
|
||||
|
||||
public GeneralDataModel()
|
||||
{
|
||||
TestDataModel = new TestDataModel();
|
||||
Windows = new WindowsDataModel();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestDataModel
|
||||
{
|
||||
public TestDataModel()
|
||||
{
|
||||
PlayerInfo = new PlayerInfo();
|
||||
IntsList = new List<int>();
|
||||
PlayerInfosList = new List<PlayerInfo>();
|
||||
}
|
||||
|
||||
[DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")]
|
||||
public string TestString { get; set; }
|
||||
|
||||
[DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")]
|
||||
public bool TestBoolean { get; set; }
|
||||
|
||||
public SKColor TestColor { get; set; } = new SKColor(221, 21, 152);
|
||||
|
||||
[DataModelProperty(Name = "Player info", Description = "[TEST] Contains information about the player")]
|
||||
public PlayerInfo PlayerInfo { get; set; }
|
||||
|
||||
public double UpdatesDividedByFour { get; set; }
|
||||
public int Updates { get; set; }
|
||||
|
||||
public List<int> IntsList { get; set; }
|
||||
public List<PlayerInfo> PlayerInfosList { get; set; }
|
||||
}
|
||||
|
||||
public class PlayerInfo
|
||||
{
|
||||
[DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")]
|
||||
public string TestString { get; set; }
|
||||
|
||||
[DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")]
|
||||
public bool TestBoolean { get; set; }
|
||||
|
||||
[DataModelProperty(Affix = "%", MinValue = 0, MaxValue = 100)]
|
||||
public int Health { get; set; }
|
||||
|
||||
public SKPoint Position { get; set; }
|
||||
public WindowDataModel ActiveWindow { get; set; }
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ namespace Artemis.Plugins.Modules.General.DataModel.Windows
|
||||
ProcessName = process.ProcessName;
|
||||
|
||||
// Accessing MainModule requires admin privileges, this way does not
|
||||
ProgramLocation = WindowMonitor.GetProcessFilename(process);
|
||||
ProgramLocation = WindowUtilities.GetProcessFilename(process);
|
||||
}
|
||||
|
||||
public string WindowTitle { get; set; }
|
||||
|
||||
@ -6,7 +6,6 @@ namespace Artemis.Plugins.Modules.General.DataModel.Windows
|
||||
{
|
||||
public class WindowsDataModel
|
||||
{
|
||||
public WindowDataModel ActiveWindow { get; set; }
|
||||
public List<WindowDataModel> OpenWindows { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.Core.Plugins.Abstract.ViewModels;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
using Artemis.Plugins.Modules.General.DataModel;
|
||||
using Artemis.Plugins.Modules.General.DataModel.Windows;
|
||||
using Artemis.Plugins.Modules.General.Utilities;
|
||||
using Artemis.Plugins.Modules.General.ViewModels;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Plugins.Modules.General
|
||||
{
|
||||
public class GeneralModule : ProfileModule<GeneralDataModel>
|
||||
{
|
||||
private readonly PluginSettings _settings;
|
||||
private readonly Random _rand;
|
||||
|
||||
|
||||
public GeneralModule(PluginSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_rand = new Random();
|
||||
}
|
||||
|
||||
public override IEnumerable<ModuleViewModel> GetViewModels()
|
||||
{
|
||||
return new List<ModuleViewModel> {new GeneralViewModel(this)};
|
||||
@ -35,19 +18,7 @@ namespace Artemis.Plugins.Modules.General
|
||||
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
DataModel.TestDataModel.UpdatesDividedByFour += 0.25;
|
||||
DataModel.TestDataModel.Updates += 1;
|
||||
DataModel.TestDataModel.PlayerInfo.Position = new SKPoint(_rand.Next(100), _rand.Next(100));
|
||||
DataModel.TestDataModel.PlayerInfo.Health++;
|
||||
if (DataModel.TestDataModel.PlayerInfo.Health > 200)
|
||||
DataModel.TestDataModel.PlayerInfo.Health = 0;
|
||||
|
||||
DataModel.TestDataModel.IntsList[0] = _rand.Next();
|
||||
DataModel.TestDataModel.IntsList[2] = _rand.Next();
|
||||
|
||||
UpdateCurrentWindow();
|
||||
UpdateBackgroundWindows();
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
@ -56,11 +27,6 @@ namespace Artemis.Plugins.Modules.General
|
||||
DisplayName = "General";
|
||||
DisplayIcon = "AllInclusive";
|
||||
ExpandsDataModel = true;
|
||||
|
||||
DataModel.TestDataModel.IntsList = new List<int> {_rand.Next(), _rand.Next(), _rand.Next()};
|
||||
DataModel.TestDataModel.PlayerInfosList = new List<PlayerInfo> {new PlayerInfo()};
|
||||
|
||||
var testSetting = _settings.GetSetting("TestSetting", DateTime.Now);
|
||||
}
|
||||
|
||||
public override void DisablePlugin()
|
||||
@ -69,31 +35,11 @@ namespace Artemis.Plugins.Modules.General
|
||||
|
||||
#region Open windows
|
||||
|
||||
private DateTime _lastBackgroundWindowsUpdate;
|
||||
|
||||
public void UpdateCurrentWindow()
|
||||
{
|
||||
var processId = WindowMonitor.GetActiveProcessId();
|
||||
if (DataModel.Windows.ActiveWindow == null || DataModel.Windows.ActiveWindow.Process.Id != processId)
|
||||
DataModel.Windows.ActiveWindow = new WindowDataModel(Process.GetProcessById(processId));
|
||||
}
|
||||
|
||||
public void UpdateBackgroundWindows()
|
||||
{
|
||||
// This is kinda slow so lets not do it very often and lets do it in a task
|
||||
if (DateTime.Now - _lastBackgroundWindowsUpdate < TimeSpan.FromSeconds(5))
|
||||
return;
|
||||
|
||||
_lastBackgroundWindowsUpdate = DateTime.Now;
|
||||
Task.Run(() =>
|
||||
{
|
||||
// All processes with a main window handle are considered open windows
|
||||
DataModel.Windows.OpenWindows = Process.GetProcesses()
|
||||
.Where(p => p.MainWindowHandle != IntPtr.Zero)
|
||||
.Select(p => new WindowDataModel(p))
|
||||
.Where(w => !string.IsNullOrEmpty(w.WindowTitle))
|
||||
.ToList();
|
||||
});
|
||||
var processId = WindowUtilities.GetActiveProcessId();
|
||||
if (DataModel.ActiveWindow == null || DataModel.ActiveWindow.Process.Id != processId)
|
||||
DataModel.ActiveWindow = new WindowDataModel(Process.GetProcessById(processId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -5,7 +5,7 @@ using System.Text;
|
||||
|
||||
namespace Artemis.Plugins.Modules.General.Utilities
|
||||
{
|
||||
public static class WindowMonitor
|
||||
public static class WindowUtilities
|
||||
{
|
||||
public static int GetActiveProcessId()
|
||||
{
|
||||
Loading…
x
Reference in New Issue
Block a user