22 Commits
Author SHA1 Message Date
obugs 59cb634737 Merge pull request '底部时间栏组件更换' (#11) from obsmain into master
Reviewed-on: RDTKEditor/RDTKEditor#11
2025-05-13 21:10:18 +08:00
OLDREDSTONE 7cb34ca981 底部时间栏组件更换 2025-05-13 21:06:03 +08:00
obugs b1d7b65218 Merge pull request 'obsmain' (#7) from obsmain into master
Reviewed-on: obugs/RDTKEditor#7
2025-05-07 10:19:06 +08:00
OLDREDSTONE a58f85b1c4 添加属性类型 2025-05-07 10:18:43 +08:00
OLDREDSTONE 6813f5b78a 添加属性组件 2025-05-07 10:15:40 +08:00
OLDREDSTONE 3533286dbc 同步更新 RDTK 2025-05-07 10:15:40 +08:00
OLDREDSTONE e78b04c352 添加属性组件 2025-05-07 10:08:38 +08:00
obugs 6c96c54193 Merge pull request 'master' (#6) from Koquem/RDTKEditor:master into master
Reviewed-on: obugs/RDTKEditor#6
2025-05-07 10:07:29 +08:00
Koquem ab62486525 下方输出beat 2025-05-07 10:02:08 +08:00
OLDREDSTONE aec91ed0db 同步更新 RDTK 2025-05-05 18:41:48 +08:00
Koquem 6cb66cde36 实现第一个输出beat左对齐 2025-04-24 21:05:14 +08:00
Koquem 65dc344f79 界面下方输出beat(未完全实现) 2025-04-24 20:17:57 +08:00
Koquem 3fa6184341 test 2025-04-15 09:48:47 +08:00
obugs 520c08ff96 Merge pull request '添加了 AI 助手接口' (#5) from obsmain into master
Reviewed-on: obugs/RDTKEditor#5
2025-04-14 11:46:53 +08:00
OLDREDSTONE a7210e527e 添加了 AI 助手接口 2025-04-14 11:15:40 +08:00
obugs 083f6b239e Merge pull request '封装预览区域绘制函数' (#4) from obsmain into master
Reviewed-on: obugs/RDTKEditor#4
2025-04-02 10:42:11 +08:00
OLDREDSTONE 8305c5b3d6 封装预览区域绘制函数 2025-03-26 12:06:43 +08:00
obugs 0d285b4ccc Merge pull request 'master' (#3) from Koquem/RDTKEditor:master into master
Reviewed-on: obugs/RDTKEditor#3
2025-03-26 10:51:43 +08:00
Koquem 26e52b98e4 1 2025-03-26 10:43:54 +08:00
Koquem 087b7f2f4e merge upstream 2025-03-25 16:27:05 +08:00
obugs 3a898c428f Merge pull request '添加对 Core 的直接引用' (#2) from obsmain into master
Reviewed-on: obugs/RDTKEditor#2
2025-03-25 16:17:15 +08:00
OLDREDSTONE 06c19bf10a 添加对 Core 的直接引用 2025-03-25 15:49:57 +08:00
5 changed files with 401 additions and 96 deletions
+102
View File
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CSharpToJsonSchema;
using Ollama;
using RhythmBase.RhythmDoctor.Components;
using System.ComponentModel;
namespace RDTKEditor.AIAssistant
{
public class Assistant
{
public const string Deepseek = "deepseek-r1:14b";
public const string Smollm = "smollm2:135m";
public const string Qwen_0_5 = "qwen2.5-coder:0.5b";
public const string Qwen_3 = "qwen2.5-coder:7b";
public OllamaApiClient Ollama { get; } = new(baseUri: new Uri("http://127.0.0.1:11434/api"));
public Chat Chat { get; }
public Assistant(string modelName)
{
Chat = Ollama.Chat(
model: modelName,
autoCallTools: true
);
var utils = new RhythmBaseUtils();
Chat.AddToolService(utils.AsTools().AsOllamaTools(), utils.AsCalls());
}
public async IAsyncEnumerable<GenerateCompletionResponse> GetResponse(string modelName, string userInput)
{
await foreach (var responce in Ollama.Completions.GenerateCompletionAsync(modelName, userInput))
{
if (responce != null)
{
yield return responce;
}
}
}
}
[GenerateJsonSchema]
public interface IRhythmBaseUtils
{
[Description("创建空关卡")]
public void CreateEmptyLevel();
[Description("创建关卡模板(默认关卡)")]
public void CreateLevelTemplate();
[Description("打开关卡")]
public void OpenLevel([Description("关卡路径")] string filepath);
[Description("添加轨道")]
public void AddRow(
[Description("轨道所在的房间")] RDRoomIndex roomIndex,
[Description("轨道名称")] string trackName
);
[Description("保存关卡")]
public void SaveLevel([Description("关卡路径,后缀为 .rdlevel")] string filepath);
}
public class RhythmBaseUtils : IRhythmBaseUtils
{
private RDLevel? level;
public void CreateEmptyLevel()
{
Console.BackgroundColor = ConsoleColor.Green;
Console.WriteLine($"Called CreateEmptyLevel");
Console.ResetColor();
level = [];
}
public void CreateLevelTemplate()
{
Console.BackgroundColor = ConsoleColor.Green;
Console.WriteLine($"Called CreateLevelTemplate");
Console.ResetColor();
level = RDLevel.Default;
}
public void OpenLevel(string filepath)
{
Console.BackgroundColor = ConsoleColor.Green;
Console.WriteLine($"Called OpenLevel: {filepath}");
Console.ResetColor();
level = RDLevel.Read(filepath);
}
public void AddRow(RDRoomIndex roomIndex, string trackName)
{
Console.BackgroundColor = ConsoleColor.Green;
Console.WriteLine($"Called AddRow: [{roomIndex}] {trackName}");
Console.ResetColor();
level?.CreateRow(roomIndex, trackName);
}
public void SaveLevel(string filepath)
{
Console.BackgroundColor = ConsoleColor.Green;
Console.WriteLine($"Called SaveLevel: {filepath}");
Console.ResetColor();
string dir = Path.GetDirectoryName(filepath) ?? "";
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
level?.Write(filepath);
}
}
}
+53 -3
View File
@@ -5,6 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
xmlns:local="clr-namespace:RDTKEditor"
xmlns:rdtkwpf="clr-namespace:RhythmBase.Control.WPF;assembly=RhythmBase.Control.WPF"
mc:Ignorable="d"
Title="RDTK Editor" Height="450" Width="800">
<Window.DataContext>
@@ -15,6 +16,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0" VerticalAlignment="Top">
<MenuItem Header="文件(_F)">
<MenuItem Header="打开文件(_O)" Click="OpenFile" InputGestureText="Ctrl+O"/>
@@ -42,6 +44,41 @@
<Border Grid.Column="0" Background="LightBlue">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemProperties" Background="LightGray">
<ComboBox Grid.Row="0" BorderBrush="#FFDF4444" Foreground="#FFC65E5E" RenderTransformOrigin="0.5,0.5">
<ComboBox.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-0.383"/>
<TranslateTransform/>
</TransformGroup>
</ComboBox.RenderTransform>
<ComboBox.Background>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="#FFF0F0F0"/>
<GradientStop Color="#FF963333" Offset="1"/>
</LinearGradientBrush>
</ComboBox.Background>
<ComboBoxItem Content="选项 1" />
<ComboBoxItem Content="选项 2" />
</ComboBox>
<Label Grid.Row="1" Content="Label" RenderTransformOrigin="0.5,0.5">
<Label.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0.017"/>
<TranslateTransform/>
</TransformGroup>
</Label.RenderTransform>
</Label>
<ListView Grid.Row="3" d:ItemsSource="{d:SampleData ItemCount=3}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</ItemsControl>
</ScrollViewer>
</Border>
@@ -54,13 +91,26 @@
<GridSplitter Grid.Column="3" Width="5" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="4" Background="LightBlue" >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid x:Name="timelineCanvas3" Background="LightGray" />
<Grid x:Name="timelineCanvas3" Background="LightGray" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" x:Name="AIAssistantText" TextWrapping="Wrap" >
AI问答
</TextBox>
<Button Grid.Row="1" x:Name="AIButton" VerticalAlignment="Bottom" Click="AIAssistantButton_Click" IsEnabled="{Binding IsButtonEnabled}" >
点击按钮开发
</Button>
</Grid>
</ScrollViewer>
</Border>
<GridSplitter Grid.Row="1" Grid.ColumnSpan="5" Height="5" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.ColumnSpan="5" Grid.Row="2" Background="LightSalmon">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="timelineCanvas" Background="LightGray" />
<ScrollViewer x:Name="timelineScrollviewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<rdtkwpf:RDTimeLinePanel x:Name="timeLine">
</rdtkwpf:RDTimeLinePanel>
</ScrollViewer>
</Border>
</Grid>
+226 -89
View File
@@ -1,12 +1,18 @@
using Microsoft.Win32;
using Ollama;
using RDTKEditor.AIAssistant;
using RDTKEditor.Models;
using RhythmBase.Events;
using RhythmBase.RhythmDoctor.Components;
using RhythmBase.RhythmDoctor.Events;
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using RhythmBase.Control.WPF;
namespace RDTKEditor
{
@@ -15,10 +21,15 @@ namespace RDTKEditor
/// </summary>
public partial class MainWindow : Window
{
private readonly Assistant Assistant = new(Assistant.Qwen_3);
private SKSizeI _vsScale = new(1600, 900);
private readonly RDTKView viewModel = new();
private readonly List<BaseEvent> _selectedEvents = [];
internal readonly Dictionary<Type, StackPanel[]> _eventPanel = [];
public MainWindow()
{
InitializeComponent();
timeLine.Level = RDLevel.Default;
}
private void OpenFile(object sender, RoutedEventArgs e)
{
@@ -50,35 +61,18 @@ namespace RDTKEditor
}
private void About(object sender, RoutedEventArgs e)
{
OnUpdateEventProperties(GetProperties(new MoveRow()));
//OnUpdateEventProperties([
// new(){
// Verifier = new RDTKPropertyVerifierDefault(),
// Name = "Name",
// Value = "RDTK Editor",
// Type = RDPropertyType.String,
// },
// new(){
// Verifier = new RDTKPropertyVerifierDefault(),
// Name = "Version",
// Value = "1.0.0",
// Type = RDPropertyType.String,
// },
// new(){
// Verifier = new RDTKPropertyVerifierEnum(typeof(EventType)),
// Name = "Description",
// Value = "RDTK Editor 是一个简单的节奏医生关卡编辑器。",
// Type = RDPropertyType.Enum,
// },
// new(){
// Verifier = new RDTKPropertyVerifierBool(),
// Name = "测试值",
// Value = "true",
// Type = RDPropertyType.Bool,
// }
// ]);
//MessageBox.Show("RDTK Editor 是一个简单的节奏医生关卡编辑器。", "关于 RDTK Editor");
if (_selectedEvents.Count == 0)
{
MessageBox.Show("请先选择一个事件。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
if (_selectedEvents.Count == 1)
{
OnUpdateEventProperties(_selectedEvents[0], GetProperties(_selectedEvents[0]));
}
else
{
}
}
private RDTKProperty[] GetProperties(BaseEvent e)
{
@@ -121,6 +115,30 @@ namespace RDTKEditor
Value = property.GetValue(obj)?.ToString() ?? "null",
Type = RDPropertyType.String,
};
case "Int32":
return new()
{
Verifier = new RDTKPropertyVerifierDefault(),
Name = property.Name,
Value = property.GetValue(obj)?.ToString() ?? "null",
Type = RDPropertyType.Integer,
};
case "Single":
return new()
{
Verifier = new RDTKPropertyVerifierDefault(),
Name = property.Name,
Value = property.GetValue(obj)?.ToString() ?? "null",
Type = RDPropertyType.Float,
};
case "RDColor":
return new()
{
Verifier = new RDTKPropertyVerifierDefault(),
Name = property.Name,
Value = property.GetValue(obj)?.ToString() ?? "null",
Type = RDPropertyType.Color,
};
}
return new()
{
@@ -130,16 +148,22 @@ namespace RDTKEditor
Type = RDPropertyType.String,
};
}
internal void OnUpdateEventProperties(RDTKProperty[] properties)
private readonly List<BaseEvent> selectedEvents = new();
internal void OnUpdateEventProperties(BaseEvent baseEvent, RDTKProperty[] properties)
{
ItemProperties.Items.Clear();
foreach (var property in properties)
StackPanel[] panel = new StackPanel[properties.Length];
if (_eventPanel.ContainsKey(baseEvent.GetType()))
{
StackPanel stackPanel;
switch (property.Type)
}
else
{
foreach (var property in properties)
{
case RDPropertyType.Bool:
stackPanel = new()
StackPanel stackPanel = property.Type switch
{
RDPropertyType.Bool => new()
{
Orientation = Orientation.Horizontal,
FlowDirection = FlowDirection.LeftToRight,
@@ -148,10 +172,8 @@ namespace RDTKEditor
new Label(){Content = property.Name},
new CheckBox() { IsChecked = bool.Parse( property.Value), }
},
};
break;
case RDPropertyType.Enum:
stackPanel = new()
},
RDPropertyType.Enum => new()
{
AllowDrop = true,
Orientation = Orientation.Horizontal,
@@ -165,10 +187,8 @@ namespace RDTKEditor
SelectedValue = property.Value,
}
},
};
break;
case RDPropertyType.String:
stackPanel = new()
},
RDPropertyType.String => new()
{
AllowDrop = true,
Orientation = Orientation.Horizontal,
@@ -178,10 +198,41 @@ namespace RDTKEditor
new Label(){Content = property.Name},
new TextBox() { Text = property.Value, }
},
};
break;
default:
stackPanel = new()
},
RDPropertyType.Integer => new()
{
AllowDrop = true,
Orientation = Orientation.Horizontal,
FlowDirection = FlowDirection.LeftToRight,
Children =
{
new Label(){Content = property.Name},
new TextBox() { Text = property.Value, }
},
},
RDPropertyType.Float => new()
{
AllowDrop = true,
Orientation = Orientation.Horizontal,
FlowDirection = FlowDirection.LeftToRight,
Children =
{
new Label(){Content = property.Name},
new TextBox() { Text = property.Value, }
},
},
RDPropertyType.Color => new()
{
AllowDrop = true,
Orientation = Orientation.Horizontal,
FlowDirection = FlowDirection.LeftToRight,
Children =
{
new Label(){Content = property.Name},
new TextBox() { Text = property.Value, }
},
},
_ => new()
{
Orientation = Orientation.Horizontal,
Children =
@@ -189,57 +240,84 @@ namespace RDTKEditor
new Label(){Content = property.Name},
new Label(){Content = property.Value}
}
};
break;
}
foreach (var child in stackPanel.Children)
{
switch (child)
},
};
foreach (var child in stackPanel.Children)
{
case CheckBox checkBox:
checkBox.Checked += (sender, e) =>
{
property.OnChanged("true");
};
checkBox.Unchecked += (sender, e) =>
{
property.OnChanged("false");
};
break;
case TextBox textBox:
textBox.TextChanged += (sender, e) =>
{
property.OnChanged(textBox.Text);
};
break;
case ComboBox comboBox:
comboBox.SelectionChanged += (sender, e) =>
{
property.OnChanged(comboBox.SelectedValue);
};
break;
default:
break;
switch (child)
{
case CheckBox checkBox:
checkBox.Checked += (sender, e) =>
{
property.OnChanged("true");
};
checkBox.Unchecked += (sender, e) =>
{
property.OnChanged("false");
};
break;
case TextBox textBox:
textBox.TextChanged += (sender, e) =>
{
property.OnChanged(textBox.Text);
};
break;
case ComboBox comboBox:
comboBox.SelectionChanged += (sender, e) =>
{
property.OnChanged(comboBox.SelectedValue);
};
break;
default:
break;
}
}
ItemProperties.Items.Add(stackPanel);
property.PropertyChanged += (e, s) => UpdateEvent();
}
ItemProperties.Items.Add(stackPanel);
property.PropertyChanged += (e, s) => UpdateEvent();
}
}
private void UpdateEvent()
{
MessageBox.Show("Update Event");
if (_selectedEvents.Count == 0)
return;
else if (_selectedEvents.Count == 1)
{
var e = _selectedEvents[0];
foreach (var property in ItemProperties.Items)
{
if (property is StackPanel stackPanel)
{
foreach (var child in stackPanel.Children)
{
switch (child)
{
case CheckBox checkBox:
e.GetType().GetProperty(stackPanel.Name)?.SetValue(e, checkBox.IsChecked);
break;
case TextBox textBox:
e.GetType().GetProperty(stackPanel.Name)?.SetValue(e, textBox.Text);
break;
case ComboBox comboBox:
e.GetType().GetProperty(stackPanel.Name)?.SetValue(e, comboBox.SelectedValue);
break;
default:
break;
}
}
}
}
}
else
{
}
}
private void UpdateTimeLine()
{
Rectangle rectangle = new()
{
Width = 100,
Height = 100,
};
timelineCanvas.Children.Add(rectangle);
}
private void mainUI_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e)
/*private void mainUI_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e)
{
var sfc = e.Surface;
var cvs = sfc.Canvas;
@@ -248,10 +326,69 @@ namespace RDTKEditor
{
Color = SKColors.Red,
});
}*/
private void mainUI_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var sfc = e.Surface;
var cvs = sfc.Canvas;
var info = e.Info;
cvs.Clear(SKColors.Black);
SKPoint scale = new((float)info.Width / _vsScale.Width, (float)info.Height / _vsScale.Height);
cvs.Save();
cvs.Scale(scale);
Draw(cvs);
cvs.Restore();
}
private void Draw(SKCanvas canvas)
{
// 以 1600x900 为基准绘制
canvas.DrawLine(0, 0, _vsScale.Width, _vsScale.Height, new SKPaint
{
Color = SKColors.Red,
StrokeWidth = 3,
IsStroke = true,
});
canvas.DrawLine(0, _vsScale.Height, _vsScale.Width, 0, new SKPaint
{
Color = SKColors.White,
StrokeWidth = 3,
IsStroke = true,
});
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show("已确认");
}
private async void AIAssistantButton_Click(object sender, RoutedEventArgs e)
{
string input = AIAssistantText.Text;
if (string.IsNullOrEmpty(input))
return;
Message responce = await Assistant.Chat.SendAsync(input);
AIAssistantText.Text = responce.Content;
}
}
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
private bool _isButtonEnabled = true;
public bool IsButtonEnabled
{
get => _isButtonEnabled;
set
{
_isButtonEnabled = value;
}
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
using RhythmBase.Components;
using RhythmBase.RhythmDoctor.Components;
using System.Diagnostics.CodeAnalysis;
namespace RDTKEditor.Models
@@ -91,7 +91,7 @@ namespace RDTKEditor.Models
internal class RDTKView
{
private RDLevel? Level { get; set; }
public RDLevel? Level { get; set; }
public RDTKView()
{
}
+18 -2
View File
@@ -9,8 +9,24 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RhythmBase" Version="1.1.0-beta" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="3.118.0-preview.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Ollama" Version="1.15.1-dev.13" />
<PackageReference Include="RhythmBase" Version="1.2.0-beta1" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="3.119.0-preview.1.2" />
<PackageReference Include="sly" Version="3.7.3" />
</ItemGroup>
<ItemGroup>
<Reference Include="RhythmBase.Control.Core">
<HintPath>..\RhythmBase.Control.WPF\bin\Debug\net8.0-windows\RhythmBase.Control.Core.dll</HintPath>
</Reference>
<Reference Include="RhythmBase.Control.WPF">
<HintPath>..\RhythmBase.Control.WPF\bin\Debug\net8.0-windows\RhythmBase.Control.WPF.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Using Include="System.IO" />
</ItemGroup>
</Project>