Skip to content

Commit 368b3ea

Browse files
committed
Add a sample for changing the avatar.
1 parent 8816329 commit 368b3ea

File tree

11 files changed

+614
-0
lines changed

11 files changed

+614
-0
lines changed

ExpressionAvatarChanger/App.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Application x:Class="ExpressionAvatarChanger.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:ExpressionAvatarChanger"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
8+
</Application.Resources>
9+
</Application>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Configuration;
2+
using System.Data;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Windows.Data;
6+
7+
namespace ExpressionAvatarChanger;
8+
/// <summary>
9+
/// Interaction logic for App.xaml
10+
/// </summary>
11+
public partial class App : Application
12+
{
13+
14+
}
15+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]

ExpressionAvatarChanger/Avatar.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using System.Collections.Immutable;
2+
using BuildSoft.VRChat.Osc;
3+
4+
namespace ExpressionAvatarChanger;
5+
6+
public record class Avatar(string Id, string Name, IImmutableList<(string, OscType)> Parameters);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System.Collections;
2+
using System.Collections.Immutable;
3+
using BuildSoft.VRChat.Osc.Avatar;
4+
5+
namespace ExpressionAvatarChanger;
6+
7+
public class AvatarCollection : IList<Avatar>
8+
{
9+
private readonly List<Avatar> _items = [];
10+
11+
public AvatarCollection()
12+
{
13+
14+
}
15+
16+
public AvatarCollection(IEnumerable<Avatar> collection) : this()
17+
{
18+
AddRange(collection);
19+
}
20+
21+
public AvatarCollection(IEnumerable<OscAvatarConfig> collection) : this()
22+
{
23+
AddRange(collection);
24+
}
25+
26+
public Avatar this[int index]
27+
{
28+
get => _items[index];
29+
set => _items[index] = value;
30+
}
31+
32+
public int Count => _items.Count;
33+
34+
bool ICollection<Avatar>.IsReadOnly => false;
35+
36+
public void Add(Avatar item)
37+
{
38+
ArgumentNullException.ThrowIfNull(item, nameof(item));
39+
_items.Add(item);
40+
}
41+
42+
public void Add(OscAvatarConfig item)
43+
{
44+
ArgumentNullException.ThrowIfNull(item, nameof(item));
45+
var parameters = item.Parameters.Items
46+
.Where(v => v.Input != null)
47+
.Select(v => (v.Name, v.Input!.OscType))
48+
.ToImmutableArray();
49+
_items.Add(new(item.Id, item.Name, parameters));
50+
}
51+
52+
public void AddRange(IEnumerable<Avatar> collection)
53+
{
54+
ArgumentNullException.ThrowIfNull(collection, nameof(collection));
55+
56+
if (collection is ICollection<Avatar> c)
57+
{
58+
if (_items.Capacity < _items.Count + c.Count)
59+
{
60+
_items.Capacity = _items.Count + c.Count;
61+
}
62+
}
63+
foreach (var item in collection)
64+
{
65+
ArgumentNullException.ThrowIfNull(item, nameof(collection));
66+
Add(item);
67+
}
68+
}
69+
70+
public void AddRange(IEnumerable<OscAvatarConfig> collection)
71+
{
72+
ArgumentNullException.ThrowIfNull(collection, nameof(collection));
73+
74+
if (collection is ICollection<OscAvatarParameter> c)
75+
{
76+
if (_items.Capacity < _items.Count + c.Count)
77+
{
78+
_items.Capacity = _items.Count + c.Count;
79+
}
80+
}
81+
foreach (var item in collection)
82+
{
83+
ArgumentNullException.ThrowIfNull(item, nameof(collection));
84+
Add(item);
85+
}
86+
}
87+
88+
public void Clear() => _items.Clear();
89+
90+
public bool Contains(Avatar item) => _items.Contains(item);
91+
92+
public void CopyTo(Avatar[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
93+
94+
public IEnumerator<Avatar> GetEnumerator() => _items.GetEnumerator();
95+
96+
public int IndexOf(Avatar item) => _items.IndexOf(item);
97+
98+
public void Insert(int index, Avatar item)
99+
{
100+
ArgumentNullException.ThrowIfNull(item);
101+
_items.Insert(index, item);
102+
}
103+
104+
public bool Remove(Avatar item) => _items.Remove(item);
105+
106+
public void RemoveAt(int index) => _items.RemoveAt(index);
107+
108+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
109+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>net9.0-windows</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<UseWPF>true</UseWPF>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\src\VRCOscLib\VRCOscLib\vrcosclib.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<Window x:Class="ExpressionAvatarChanger.MainWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:local="clr-namespace:ExpressionAvatarChanger"
7+
mc:Ignorable="d"
8+
Title="MainWindow" Height="450" Width="800"
9+
Loaded="Window_Loaded">
10+
<StackPanel>
11+
<Label Content="Expression Avatar Changer"/>
12+
<StackPanel Margin="12">
13+
<Grid>
14+
<Grid.ColumnDefinitions>
15+
<ColumnDefinition Width="50"/>
16+
<ColumnDefinition Width="2*" MaxWidth="300"/>
17+
<ColumnDefinition/>
18+
<ColumnDefinition/>
19+
<ColumnDefinition Width="80"/>
20+
<ColumnDefinition Width="100"/>
21+
</Grid.ColumnDefinitions>
22+
<Label Grid.Column="0" Content="Active"/>
23+
<Label Grid.Column="1" Content="Avatar ID"/>
24+
<Label Grid.Column="2" Content="Avatar Name"/>
25+
<Label Grid.Column="3" Content="Parameter Name"/>
26+
<Label Grid.Column="4" Content="Type"/>
27+
<Label Grid.Column="5" Content="Value"/>
28+
<StackPanel Grid.Column="5" Orientation="Horizontal" HorizontalAlignment="Right">
29+
<Button Width="20" Height="20" Margin="2" Click="AddContentButton_Click">+</Button>
30+
<Button Width="20" Height="20" Margin="2" Click="RemoveContentButton_Click">-</Button>
31+
</StackPanel>
32+
</Grid>
33+
<StackPanel x:Name="InputPanel">
34+
<local:TriggerInputItem/>
35+
</StackPanel>
36+
</StackPanel>
37+
</StackPanel>
38+
</Window>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Text;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Data;
5+
using System.Windows.Documents;
6+
using System.Windows.Input;
7+
using System.Windows.Media;
8+
using System.Windows.Media.Imaging;
9+
using System.Windows.Navigation;
10+
using System.Windows.Shapes;
11+
using BuildSoft.VRChat.Osc.Avatar;
12+
13+
namespace ExpressionAvatarChanger;
14+
15+
/// <summary>
16+
/// Interaction logic for MainWindow.xaml
17+
/// </summary>
18+
public partial class MainWindow : Window
19+
{
20+
AvatarCollection? _avatars;
21+
AvatarCollection AvatarList => _avatars ??= new(OscAvatarConfig.CreateAll());
22+
23+
public MainWindow()
24+
{
25+
InitializeComponent();
26+
}
27+
28+
private void AddContentButton_Click(object sender, RoutedEventArgs e)
29+
{
30+
var triggerInput = new TriggerInputItem() { AvatarList = AvatarList };
31+
if (InputPanel.Children.Count > 0)
32+
{
33+
var lastItem = (TriggerInputItem)InputPanel.Children[^1];
34+
triggerInput.AvatarId = lastItem.AvatarId;
35+
triggerInput.AvatarName = lastItem.AvatarName;
36+
triggerInput.ParameterName = lastItem.ParameterName;
37+
triggerInput.ThresholdType = lastItem.ThresholdType;
38+
triggerInput.Threshold = lastItem.Threshold;
39+
}
40+
InputPanel.Children.Add(triggerInput);
41+
}
42+
43+
private void RemoveContentButton_Click(object sender, RoutedEventArgs e)
44+
{
45+
if (InputPanel.Children.Count <= 0)
46+
{
47+
return;
48+
}
49+
var item = (TriggerInputItem)InputPanel.Children[^1];
50+
item.IsActive = false;
51+
52+
InputPanel.Children.Remove(item);
53+
}
54+
55+
private void Window_Loaded(object sender, RoutedEventArgs e)
56+
{
57+
foreach (var item in InputPanel.Children)
58+
{
59+
if (item is not TriggerInputItem triggerInput)
60+
{
61+
continue;
62+
}
63+
triggerInput.AvatarList = AvatarList;
64+
}
65+
}
66+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<UserControl x:Class="ExpressionAvatarChanger.TriggerInputItem"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:local="clr-namespace:ExpressionAvatarChanger"
7+
xmlns:osc="clr-namespace:BuildSoft.VRChat.Osc;assembly=vrcosclib"
8+
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
9+
mc:Ignorable="d" d:DesignWidth="800">
10+
<UserControl.Resources>
11+
<ResourceDictionary>
12+
</ResourceDictionary>
13+
</UserControl.Resources>
14+
<Grid>
15+
<Grid.ColumnDefinitions>
16+
<ColumnDefinition Width="50"/>
17+
<ColumnDefinition Width="2*" MaxWidth="300"/>
18+
<ColumnDefinition/>
19+
<ColumnDefinition/>
20+
<ColumnDefinition Width="80"/>
21+
<ColumnDefinition Width="100"/>
22+
</Grid.ColumnDefinitions>
23+
<CheckBox Grid.Column="0" Margin="10,2" IsChecked="{Binding IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
24+
<ComboBox x:Name="AvatarIdComboBox" Grid.Column="1" Margin="10,2"
25+
Text="{Binding AvatarId, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}"
26+
IsEditable="True"
27+
ItemsSource="{Binding AvatarIdList}"
28+
SelectionChanged="AvatarIdComboBox_SelectionChanged"
29+
TextBoxBase.TextChanged="InactiveOnTextChanged"/>
30+
<ComboBox x:Name="AvatarNameComboBox" Grid.Column="2" Margin="10,2"
31+
Text="{Binding AvatarName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}"
32+
IsEditable="True"
33+
ItemsSource="{Binding AvatarNameList}"
34+
SelectionChanged="AvatarNameComboBox_SelectionChanged"
35+
TextBoxBase.TextChanged="InactiveOnTextChanged"/>
36+
<ComboBox x:Name="ParameterNameComboBox" Grid.Column="3" Margin="10,2"
37+
Text="{Binding ParameterName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}"
38+
IsEditable="True"
39+
ItemsSource="{Binding ParameterList}"
40+
SelectionChanged="ParameterNameComboBox_SelectionChanged"
41+
TextBoxBase.TextChanged="InactiveOnTextChanged"/>
42+
<ComboBox x:Name="ThresholdTypeComboBox" Grid.Column="4" Margin="10,2"
43+
Text="{Binding ThresholdType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}"
44+
IsEditable="False"
45+
TextBoxBase.TextChanged="InactiveOnTextChanged">
46+
<ComboBoxItem Content="{x:Static osc:OscType.Bool}"></ComboBoxItem>
47+
<ComboBoxItem Content="{x:Static osc:OscType.Int}"></ComboBoxItem>
48+
<ComboBoxItem Content="{x:Static osc:OscType.Float}"></ComboBoxItem>
49+
</ComboBox>
50+
<ComboBox x:Name="ThresholdComboBox" Grid.Column="5" Margin="10,2" Text="{Binding Threshold, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}" TextBoxBase.TextChanged="InactiveOnTextChanged">
51+
<Control.Style>
52+
<Style TargetType="ComboBox">
53+
<Setter Property="IsEditable" Value="True"/>
54+
<Style.Triggers>
55+
<DataTrigger Binding="{Binding ThresholdType,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TriggerInputItem}}}" Value="{x:Static osc:OscType.Bool}">
56+
<Setter Property="ItemsSource">
57+
<Setter.Value>
58+
<x:Array Type="sys:String">
59+
<sys:String>True</sys:String>
60+
<sys:String>False</sys:String>
61+
</x:Array>
62+
</Setter.Value>
63+
</Setter>
64+
<Setter Property="IsEditable" Value="False"/>
65+
</DataTrigger>
66+
</Style.Triggers>
67+
</Style>
68+
</Control.Style>
69+
</ComboBox>
70+
</Grid>
71+
</UserControl>

0 commit comments

Comments
 (0)