Progress on animated visualizations

Visualization editor dialog is fully functional.  Add/remove,
up/down, tag/interval timer editing, and animated preview are now
working.
This commit is contained in:
Andy McFadden 2019-12-20 15:05:16 -08:00
parent 9f9e518afc
commit fef7668b63
9 changed files with 624 additions and 59 deletions

View File

@ -48,6 +48,9 @@
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="FrameAnimationControl.xaml.cs">
<DependentUpon>FrameAnimationControl.xaml</DependentUpon>
</Compile>
<Compile Include="Helper.cs" />
<Compile Include="InverseBooleanConverter.cs" />
<Compile Include="MultiKeyInputGesture.cs" />
@ -79,6 +82,10 @@
</None>
</ItemGroup>
<ItemGroup>
<Page Include="FrameAnimationControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WorkProgress.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View File

@ -0,0 +1,37 @@
<!--
Copyright 2019 faddenSoft
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
Basic usage:
<common:FrameAnimationControl Name="previewAnim" Width="256" Height="256"/>
You must explicitly Stop() the animation, or it will continue to run forever.
Thanks: https://blogs.claritycon.com/wpf-image-sequencer-for-png-sequences-4c826f7d4462
-->
<UserControl x:Class="CommonWPF.FrameAnimationControl"
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:CommonWPF"
mc:Ignorable="d"
Background="Transparent"
d:DesignHeight="128" d:DesignWidth="128">
<Grid>
<Image Name="theImage"/>
</Grid>
</UserControl>

View File

@ -0,0 +1,111 @@
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace CommonWPF {
/// <summary>
/// The Frame Animation control provides a simple way to display a series of images.
/// (Think of an animated GIF.)
///
/// Set Bitmaps and IntervalMsec, then call Start.
/// </summary>
public partial class FrameAnimationControl : UserControl {
/// <summary>
/// List of bitmaps to be displayed.
/// </summary>
public List<BitmapSource> Bitmaps {
get { return mBitmaps; }
set {
if (value == null || value.Count == 0) {
throw new ArgumentException("Invalid bitmap list");
}
mBitmaps = value;
if (mNext >= value.Count) {
mNext = 0;
}
}
}
private List<BitmapSource> mBitmaps;
/// <summary>
/// How long to wait before showing next bitmap.
/// </summary>
public int IntervalMsec {
get { return mIntervalMsec; }
set {
if (value < 1) {
throw new ArgumentException("Invalid interval " + value);
}
mIntervalMsec = value;
mTimer.Interval = TimeSpan.FromMilliseconds(value);
}
}
private int mIntervalMsec = 100;
/// <summary>
/// True if the animation is currently running.
/// </summary>
public bool IsRunning {
get { return mTimer.IsEnabled; }
}
/// <summary>
/// Index of next image to display.
/// </summary>
private int mNext;
/// <summary>
/// Dispatcher-linked timer object.
/// </summary>
private DispatcherTimer mTimer;
/// <summary>
/// Constructor, invoked from XAML.
/// </summary>
public FrameAnimationControl() {
InitializeComponent();
mTimer = new DispatcherTimer(DispatcherPriority.Render);
mTimer.Interval = TimeSpan.FromMilliseconds(IntervalMsec);
mTimer.Tick += Tick;
}
public void Start() {
mTimer.Start();
}
public void Stop() {
mTimer.Stop();
}
private void Tick(object sender, EventArgs e) {
if (mBitmaps == null) {
throw new InvalidOperationException("Must set bitmaps before starting");
}
if (mNext >= mBitmaps.Count) {
mNext = 0;
}
theImage.Source = mBitmaps[mNext];
mNext++;
}
}
}

View File

@ -29,6 +29,16 @@ namespace SourceGen {
/// view angles.
/// </remarks>
public class VisualizationAnimation : Visualization {
/// <summary>
/// Frame delay parameter.
/// </summary>
public const string FRAME_DELAY_MSEC_PARAM = "frame-delay-msec";
/// <summary>
/// Fake visualization generation identifier.
/// </summary>
public const string ANIM_VIS_GEN = "(animation)";
/// <summary>
/// Serial numbers of visualizations, e.g. bitmap frames.
/// </summary>
@ -60,5 +70,18 @@ namespace SourceGen {
mSerialNumbers = visSerialNumbers;
}
/// <summary>
/// Returns true if this visualization holds a reference to the specified serial number.
/// </summary>
public bool ContainsSerial(int serial) {
// Linear search. We don't do this a lot and our lists our short, so okay for now.
foreach (int ser in mSerialNumbers) {
if (ser == serial) {
return true;
}
}
return false;
}
}
}

View File

@ -20,10 +20,12 @@ limitations under the License.
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGen.WpfGui"
xmlns:common="clr-namespace:CommonWPF;assembly=CommonWPF"
mc:Ignorable="d"
Title="Edit Bitmap Animation"
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner">
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Closing="Window_Closing">
<Grid Margin="8">
<Grid.RowDefinitions>
@ -31,10 +33,23 @@ limitations under the License.
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- top section: bitmap visualization selection -->
<Grid Grid.Row="0">
<!-- top section: tag entry -->
<StackPanel Grid.Row="0">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,4,0" Text="Tag:"/>
<TextBox Width="250" Margin="4,1,0,0"
Text="{Binding TagString, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
</StackPanel>
<TextBlock Text="• Must be unique, 2+ chars" Margin="30,0,0,0" Foreground="{Binding TagLabelBrush}"/>
</StackPanel>
<!-- upper-middle section: bitmap visualization selection -->
<Grid Grid.Row="1" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
@ -45,10 +60,10 @@ limitations under the License.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"
Text="Select bitmap visualizations to use as animation frames:"/>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Margin="0,0,0,4"
Text="Select visualizations to use as animation frames:"/>
<DataGrid Grid.Column="0" Grid.Row="1" Name="visSourceGrid"
<DataGrid Grid.Column="0" Grid.Row="1" Name="visSourceGrid" Width="251" Height="300"
IsReadOnly="True"
ItemsSource="{Binding VisSourceItems}"
FontFamily="{StaticResource GeneralMonoFont}"
@ -59,6 +74,7 @@ limitations under the License.
HeadersVisibility="Column"
CanUserReorderColumns="False"
SelectionMode="Single"
SelectionChanged="VisSourceGrid_SelectionChanged"
MouseDoubleClick="VisSourceGrid_MouseDoubleClick">
<DataGrid.Resources>
<!-- make the no-focus color the same as the in-focus color -->
@ -83,7 +99,7 @@ limitations under the License.
</DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Column="2" Grid.Row="1" Name="visAnimGrid"
<DataGrid Grid.Column="2" Grid.Row="1" Name="visAnimGrid" Width="251" Height="300"
IsReadOnly="True"
ItemsSource="{Binding VisAnimItems}"
FontFamily="{StaticResource GeneralMonoFont}"
@ -94,6 +110,7 @@ limitations under the License.
HeadersVisibility="Column"
CanUserReorderColumns="False"
SelectionMode="Single"
SelectionChanged="VisAnimGrid_SelectionChanged"
MouseDoubleClick="VisAnimGrid_MouseDoubleClick">
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
@ -116,30 +133,67 @@ limitations under the License.
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1" Grid.Row="1" Height="200">
<Button Content="Add &#x2192;" Width="70" Margin="4,24,4,4"/>
<Button Content="&#x2190; Remove" Width="70" Margin="4,0,4,4"/>
<Button Content="Clear" Width="70" Margin="4,4,4,4"/>
<Button Content="Up &#x2191;" Width="70" Margin="4,20,4,4"/>
<Button Content="Down &#x2193;" Width="70" Margin="4,0,4,4"/>
<StackPanel Grid.Column="1" Grid.Row="1">
<Button Content="Add &#x2192;" Width="70" Margin="4,24,4,4"
IsEnabled="{Binding IsAddEnabled}" Click="AddButton_Click"/>
<Button Content="&#x2190; Remove" Width="70" Margin="4,0,4,4"
IsEnabled="{Binding IsRemoveEnabled}" Click="RemoveButton_Click"/>
<Button Content="Clear" Width="70" Margin="4,4,4,4"
IsEnabled="{Binding IsRemoveEnabled}" Click="ClearButton_Click"/>
<Button Content="Up &#x2191;" Width="70" Margin="4,20,4,4"
IsEnabled="{Binding IsUpEnabled}" Click="UpButton_Click"/>
<Button Content="Down &#x2193;" Width="70" Margin="4,0,4,4"
IsEnabled="{Binding IsDownEnabled}" Click="DownButton_Click"/>
</StackPanel>
</Grid>
<!-- middle section: parameters -->
<Grid Grid.Row="1" Margin="0,8,0,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Frame delay (msec):" Margin="0,1,0,0"/>
<TextBox Width="40" MaxLength="5" Margin="4,0,0,0"
Text="{Binding FrameDelayTimeMsec, FallbackValue=88888}" />
<!-- lower-middle section: parameters -->
<Grid Grid.Row="2" Margin="0,8,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation="Horizontal">
</StackPanel>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="2" HorizontalAlignment="Right" Margin="0,1,4,0"
Text="Frame delay (msec):" Foreground="{Binding FrameDelayLabelBrush}"/>
<TextBox Grid.Column="1" Grid.Row="2" Width="40" HorizontalAlignment="Left" Margin="0,0,0,0"
Text="{Binding FrameDelayTimeMsec, UpdateSourceTrigger=PropertyChanged, FallbackValue=88888}"
MaxLength="5"/>
</Grid>
</Grid>
<!-- bottom section: preview -->
<Grid Grid.Row="2" Margin="0,8,0,0">
<TextBlock Text="Preview:"/>
<Grid Grid.Row="3" Margin="0,8,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="Black" BorderThickness="1"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="{StaticResource CheckerBackground}">
<common:FrameAnimationControl Name="previewAnim" Width="256" Height="256"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</Border>
<Button Grid.Row="1" Width="120" Content="Start / Stop" Margin="0,4,0,0"
Click="showPreviewClick" IsEnabled="{Binding IsPreviewEnabled}"/>
</Grid>
<DockPanel Grid.Row="3" Margin="0,8,0,0" LastChildFill="False">
<DockPanel Grid.Row="4" Margin="0,8,0,0" LastChildFill="False">
<Button DockPanel.Dock="Right" Content="Cancel" Width="70" Margin="8,0,0,0" IsCancel="True"/>
<Button DockPanel.Dock="Right" Grid.Column="1" Content="OK" Width="70"
IsDefault="True" IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>

View File

@ -17,9 +17,10 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@ -29,6 +30,21 @@ namespace SourceGen.WpfGui {
/// Bitmap animation visualization editor.
/// </summary>
public partial class EditBitmapAnimation : Window, INotifyPropertyChanged {
private const int MAX_FRAME_DELAY = 10000; // 10 sec, in ms
private const int DEFAULT_FRAME_DELAY = 100; // 0.1 sec, in ms
/// <summary>
/// New/edited animation, only valid when dialog result is true.
/// </summary>
public VisualizationAnimation NewAnim { get; private set; }
private int mSetOffset;
private SortedList<int, VisualizationSet> mEditedList;
private VisualizationAnimation mOrigAnim;
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
private Brush mErrorLabelColor = Brushes.Red;
/// <summary>
/// True if current contents represent a valid visualization animation. Determines
/// whether the OK button is enabled.
@ -45,14 +61,68 @@ namespace SourceGen.WpfGui {
public ObservableCollection<Visualization> VisAnimItems { get; private set; } =
new ObservableCollection<Visualization>();
/// <summary>
/// Visualization tag.
/// </summary>
public string TagString {
get { return mTagString; }
set { mTagString = value; OnPropertyChanged(); UpdateControls(); }
}
private string mTagString;
// Text turns red on error.
public Brush TagLabelBrush {
get { return mTagLabelBrush; }
set { mTagLabelBrush = value; OnPropertyChanged(); }
}
private Brush mTagLabelBrush;
/// <summary>
/// Time between frames, in milliseconds.
/// </summary>
public int FrameDelayTimeMsec {
public string FrameDelayTimeMsec {
get { return mFrameDelayTimeMsec; }
set { mFrameDelayTimeMsec = value; OnPropertyChanged(); }
set { mFrameDelayTimeMsec = value; OnPropertyChanged(); UpdateControls(); }
}
private int mFrameDelayTimeMsec;
private string mFrameDelayTimeMsec;
private int mFrameDelayIntMsec = -1;
public Brush FrameDelayLabelBrush {
get { return mFrameDelayLabelBrush; }
set { mFrameDelayLabelBrush = value; OnPropertyChanged(); }
}
private Brush mFrameDelayLabelBrush;
public bool IsAddEnabled {
get { return mIsAddEnabled; }
set { mIsAddEnabled = value; OnPropertyChanged(); }
}
private bool mIsAddEnabled;
public bool IsRemoveEnabled {
get { return mIsRemoveEnabled; }
set { mIsRemoveEnabled = value; OnPropertyChanged(); }
}
private bool mIsRemoveEnabled;
public bool IsUpEnabled {
get { return mIsUpEnabled; }
set { mIsUpEnabled = value; OnPropertyChanged(); }
}
private bool mIsUpEnabled;
public bool IsDownEnabled {
get { return mIsDownEnabled; }
set { mIsDownEnabled = value; OnPropertyChanged(); }
}
private bool mIsDownEnabled;
public bool IsPreviewEnabled {
get { return mIsPreviewEnabled; }
set { mIsPreviewEnabled = value; OnPropertyChanged(); }
}
private bool mIsPreviewEnabled;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
@ -60,26 +130,271 @@ namespace SourceGen.WpfGui {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner"></param>
public EditBitmapAnimation(Window owner) {
public EditBitmapAnimation(Window owner, int setOffset,
SortedList<int, VisualizationSet> editedList, VisualizationAnimation origAnim) {
InitializeComponent();
Owner = owner;
DataContext = this;
mSetOffset = setOffset;
mEditedList = editedList;
mOrigAnim = origAnim;
// this will cause initialization of the Brush properties
FrameDelayTimeMsec = DEFAULT_FRAME_DELAY.ToString();
if (origAnim != null) {
TagString = origAnim.Tag;
int frameDelay = PluginCommon.Util.GetFromObjDict(origAnim.VisGenParams,
VisualizationAnimation.FRAME_DELAY_MSEC_PARAM, DEFAULT_FRAME_DELAY);
} else {
TagString = "anim" + mSetOffset.ToString("x6");
}
PopulateItemLists();
}
private void PopulateItemLists() {
foreach (KeyValuePair<int, VisualizationSet> kvp in mEditedList) {
foreach (Visualization vis in kvp.Value) {
if (vis is VisualizationAnimation) {
// disallow animations with animations
continue;
}
if (mOrigAnim != null && mOrigAnim.ContainsSerial(vis.SerialNumber)) {
VisAnimItems.Add(vis);
} else {
VisSourceItems.Add(vis);
}
}
}
if (VisSourceItems.Count > 0) {
visSourceGrid.SelectedIndex = 0;
}
if (VisAnimItems.Count > 0) {
visAnimGrid.SelectedIndex = 0;
}
}
private void Window_Closing(object sender, CancelEventArgs e) {
previewAnim.Stop();
}
private void OkButton_Click(object sender, RoutedEventArgs e) {
Dictionary<string, object> visGenParams = new Dictionary<string, object>(1);
visGenParams.Add(VisualizationAnimation.FRAME_DELAY_MSEC_PARAM, mFrameDelayIntMsec);
List<int> serials = new List<int>(VisAnimItems.Count);
foreach (Visualization vis in VisAnimItems) {
serials.Add(vis.SerialNumber);
}
NewAnim = new VisualizationAnimation(TagString, VisualizationAnimation.ANIM_VIS_GEN,
new ReadOnlyDictionary<string, object>(visGenParams), serials);
DialogResult = true;
}
private void UpdateControls() {
IsValid = true;
if (!int.TryParse(FrameDelayTimeMsec, out int frameDelay) ||
frameDelay <= 0 || frameDelay > MAX_FRAME_DELAY) {
mFrameDelayIntMsec = -1;
FrameDelayLabelBrush = mErrorLabelColor;
IsValid = false;
} else {
mFrameDelayIntMsec = frameDelay;
FrameDelayLabelBrush = mDefaultLabelColor;
}
bool isSourceItemSelected = (visSourceGrid.SelectedItem != null);
bool isAnimItemSelected = (visAnimGrid.SelectedItem != null);
IsAddEnabled = VisSourceItems.Count > 0 && isSourceItemSelected;
IsRemoveEnabled = VisAnimItems.Count > 0 && isAnimItemSelected;
IsUpEnabled = isAnimItemSelected && visAnimGrid.SelectedIndex != 0;
IsDownEnabled = isAnimItemSelected &&
visAnimGrid.SelectedIndex != VisAnimItems.Count - 1;
IsPreviewEnabled = VisAnimItems.Count > 0;
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool tagOk);
Visualization match =
EditVisualizationSet.FindVisualizationByTag(mEditedList, trimTag);
if (match != null && (mOrigAnim == null || trimTag != mOrigAnim.Tag)) {
// Another vis already has this tag. We're checking the edited list, so we'll
// be current with edits to this or other Visualizations in the same set.
tagOk = false;
}
if (!tagOk) {
TagLabelBrush = mErrorLabelColor;
IsValid = false;
} else {
TagLabelBrush = mDefaultLabelColor;
}
RefreshAnim();
}
private void VisSourceGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
UpdateControls();
}
private void VisAnimGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
UpdateControls();
}
private void VisSourceGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
AddSelection();
}
private void VisAnimGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
RemoveSelection();
}
private void AddButton_Click(object sender, RoutedEventArgs e) {
AddSelection();
}
private void RemoveButton_Click(object sender, RoutedEventArgs e) {
RemoveSelection();
}
/// <summary>
/// Adds an item to the animation list, moving it from the source list.
/// </summary>
private void AddSelection() {
if (!IsAddEnabled) {
return;
}
Visualization item = (Visualization)visSourceGrid.SelectedItem;
int index = VisSourceItems.IndexOf(item);
Debug.Assert(index >= 0);
VisSourceItems.Remove(item);
VisAnimItems.Add(item);
if (index == VisSourceItems.Count) {
index--;
}
if (index >= 0) {
visSourceGrid.SelectedIndex = index;
}
if (visAnimGrid.SelectedIndex < 0) {
visAnimGrid.SelectedIndex = 0;
}
RefreshAnim();
}
/// <summary>
/// Removes an item from the animation list, moving it to the source list.
/// </summary>
private void RemoveSelection() {
if (!IsRemoveEnabled) {
return;
}
Visualization item = (Visualization)visAnimGrid.SelectedItem;
int index = VisAnimItems.IndexOf(item);
Debug.Assert(index >= 0);
VisAnimItems.Remove(item);
VisSourceItems.Add(item);
if (index == VisAnimItems.Count) {
index--;
}
if (index >= 0) {
visAnimGrid.SelectedIndex = index;
}
if (visSourceGrid.SelectedIndex < 0) {
visSourceGrid.SelectedIndex = 0;
}
RefreshAnim();
}
/// <summary>
/// Clears the animation list.
/// </summary>
private void ClearButton_Click(object sender, RoutedEventArgs e) {
Debug.Assert(IsRemoveEnabled);
while (VisAnimItems.Count > 0) {
Visualization item = VisAnimItems[0];
VisAnimItems.Remove(item);
VisSourceItems.Add(item);
}
if (visSourceGrid.SelectedIndex < 0) {
visSourceGrid.SelectedIndex = 0;
}
RefreshAnim();
}
/// <summary>
/// Repositions an item in the animation list, moving it up one slot.
/// </summary>
private void UpButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visAnimGrid.SelectedItem;
int index = VisAnimItems.IndexOf(item);
Debug.Assert(index > 0);
VisAnimItems.Remove(item);
VisAnimItems.Insert(index - 1, item);
visAnimGrid.SelectedIndex = index - 1;
//RefreshAnim();
}
/// <summary>
/// Repositions an item in the animation list, moving it down one slot.
/// </summary>
private void DownButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visAnimGrid.SelectedItem;
int index = VisAnimItems.IndexOf(item);
Debug.Assert(index >= 0 && index < VisAnimItems.Count - 1);
VisAnimItems.Remove(item);
VisAnimItems.Insert(index + 1, item);
visAnimGrid.SelectedIndex = index + 1;
//RefreshAnim();
}
private void showPreviewClick(object sender, RoutedEventArgs e) {
if (previewAnim.IsRunning) {
previewAnim.Stop();
} else {
if (RefreshAnim()) {
previewAnim.Start();
}
}
}
/// <summary>
/// Updates the frame animation control's parameters. Stops the animation if something
/// looks wrong.
/// </summary>
/// <returns>True if all is well, false if something is wrong and the animation
/// should not be started.</returns>
private bool RefreshAnim() {
if (VisAnimItems.Count == 0 || mFrameDelayIntMsec <= 0) {
previewAnim.Stop();
return false;
}
List<BitmapSource> bitmaps = new List<BitmapSource>(VisAnimItems.Count);
foreach (Visualization vis in VisAnimItems) {
bitmaps.Add(vis.CachedImage);
}
previewAnim.Bitmaps = bitmaps;
previewAnim.IntervalMsec = mFrameDelayIntMsec;
return true;
}
}
}

View File

@ -120,12 +120,13 @@ limitations under the License.
SelectionChanged="VisComboBox_SelectionChanged"/>
<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" Margin="0,0,4,0"
Text="Tag:" Foreground="{Binding TagLabelBrush}"/>
Text="Tag:"/>
<TextBox Grid.Column="1" Grid.Row="1" Width="250" Margin="0,1,0,0" HorizontalAlignment="Left"
Text="{Binding TagString, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Grid.Column="1" Grid.Row="2" Text="• Must be unique, 2+ chars"/>
<TextBlock Grid.Column="1" Grid.Row="2" Text="• Must be unique, 2+ chars"
Foreground="{Binding TagLabelBrush}"/>
<TextBlock Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Margin="0,10,0,4" Text="Preview:"/>
</Grid>

View File

@ -33,7 +33,7 @@ namespace SourceGen.WpfGui {
/// </summary>
public partial class EditVisualization : Window, INotifyPropertyChanged {
/// <summary>
/// Dialog result.
/// New/edited visualization, only valid when dialog result is true.
/// </summary>
public Visualization NewVis { get; private set; }
@ -44,7 +44,8 @@ namespace SourceGen.WpfGui {
private Visualization mOrigVis;
/// <summary>
/// Identifier for last visualizer we used, for the benefit of "new".
/// Visualization generation identifier for the last visualizer we used, for the benefit
/// of "new".
/// </summary>
private static string sLastVisIdent = string.Empty;
@ -187,6 +188,7 @@ namespace SourceGen.WpfGui {
mScriptSupport = new ScriptSupport(this);
mProject.PrepareScripts(mScriptSupport);
// this will initialize mTagLabelBrush
if (vis != null) {
TagString = vis.Tag;
} else {
@ -464,7 +466,8 @@ namespace SourceGen.WpfGui {
}
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool tagOk);
Visualization match = FindVisualizationByTag(trimTag);
Visualization match =
EditVisualizationSet.FindVisualizationByTag(mEditedList, trimTag);
if (match != null && (mOrigVis == null || trimTag != mOrigVis.Tag)) {
// Another vis already has this tag. We're checking the edited list, so we'll
// be current with edits to this or other Visualizations in the same set.
@ -478,23 +481,6 @@ namespace SourceGen.WpfGui {
}
}
/// <summary>
/// Finds a Visualization with a matching tag, searching across all sets in the
/// edited list.
/// </summary>
/// <param name="tag">Tag to search for.</param>
/// <returns>Matching Visualization, or null if not found.</returns>
private Visualization FindVisualizationByTag(string tag) {
foreach (KeyValuePair<int, VisualizationSet> kvp in mEditedList) {
foreach (Visualization vis in kvp.Value) {
if (vis.Tag == tag) {
return vis;
}
}
}
return null;
}
private void VisComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
VisualizationItem item = (VisualizationItem)visComboBox.SelectedItem;
if (item == null) {

View File

@ -167,11 +167,13 @@ namespace SourceGen.WpfGui {
}
private void NewBitmapAnimationButton_Click(object sender, RoutedEventArgs e) {
EditBitmapAnimation dlg = new EditBitmapAnimation(this);
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
CreateEditedSetList(), null);
if (dlg.ShowDialog() != true) {
return;
}
// TODO(xyzzy)
VisualizationList.Add(dlg.NewAnim);
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
}
private void EditButton_Click(object sender, RoutedEventArgs e) {
@ -184,16 +186,27 @@ namespace SourceGen.WpfGui {
return;
}
Visualization item = (Visualization)visualizationGrid.SelectedItem;
Visualization newVis;
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
CreateEditedSetList(), item);
if (dlg.ShowDialog() != true) {
return;
if (item is VisualizationAnimation) {
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
CreateEditedSetList(), (VisualizationAnimation)item);
if (dlg.ShowDialog() != true) {
return;
}
newVis = dlg.NewAnim;
} else {
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
CreateEditedSetList(), item);
if (dlg.ShowDialog() != true) {
return;
}
newVis = dlg.NewVis;
}
int index = VisualizationList.IndexOf(item);
VisualizationList.Remove(item);
VisualizationList.Insert(index, dlg.NewVis);
VisualizationList.Insert(index, newVis);
visualizationGrid.SelectedIndex = index;
okButton.Focus();
@ -223,7 +236,7 @@ namespace SourceGen.WpfGui {
private void DownButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visualizationGrid.SelectedItem;
int index = VisualizationList.IndexOf(item);
Debug.Assert(index < VisualizationList.Count - 1);
Debug.Assert(index >= 0 && index < VisualizationList.Count - 1);
VisualizationList.Remove(item);
VisualizationList.Insert(index + 1, item);
visualizationGrid.SelectedIndex = index + 1;
@ -265,5 +278,23 @@ namespace SourceGen.WpfGui {
}
return mixList;
}
/// <summary>
/// Finds a Visualization with a matching tag, searching across all sets in the
/// edited list.
/// </summary>
/// <param name="tag">Tag to search for.</param>
/// <returns>Matching Visualization, or null if not found.</returns>
public static Visualization FindVisualizationByTag(SortedList<int, VisualizationSet> list,
string tag) {
foreach (KeyValuePair<int, VisualizationSet> kvp in list) {
foreach (Visualization vis in kvp.Value) {
if (vis.Tag == tag) {
return vis;
}
}
}
return null;
}
}
}