diff --git a/cevio-casts.sln b/cevio-casts.sln index 16afb65..91c82bd 100644 --- a/cevio-casts.sln +++ b/cevio-casts.sln @@ -17,6 +17,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateCheck", "tests\Update EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CevioCasts.UpdateChecker", "languages\csharp\CevioCasts.UpdateChecker\CevioCasts.UpdateChecker.csproj", "{4B44E997-89E9-4E0C-8FD3-A059926CE007}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "viewer", "viewer", "{63AE9FF6-FFC9-4D2A-9C36-D0BDB80BF3E9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CastViewer", "CastViewer", "{DABA4146-2600-4C2C-974F-934F9D89B643}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CastViewer.Core", "viewer\CastViewer\CastViewer.Core\CastViewer.Core.csproj", "{6401CAD1-98BB-44AD-9A25-ABB76D53BFB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CastViewer", "viewer\CastViewer\CastViewer\CastViewer.csproj", "{4ACE5C06-354C-45F9-A57F-B74D6AC515D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CastViewer.Browser", "viewer\CastViewer\CastViewer.Browser\CastViewer.Browser.csproj", "{DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CastViewer.Desktop", "viewer\CastViewer\CastViewer.Desktop\CastViewer.Desktop.csproj", "{BB3045B4-358F-4814-97D7-F431E26F196C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,11 +54,32 @@ Global {4B44E997-89E9-4E0C-8FD3-A059926CE007}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B44E997-89E9-4E0C-8FD3-A059926CE007}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B44E997-89E9-4E0C-8FD3-A059926CE007}.Release|Any CPU.Build.0 = Release|Any CPU + {6401CAD1-98BB-44AD-9A25-ABB76D53BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6401CAD1-98BB-44AD-9A25-ABB76D53BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6401CAD1-98BB-44AD-9A25-ABB76D53BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6401CAD1-98BB-44AD-9A25-ABB76D53BFB7}.Release|Any CPU.Build.0 = Release|Any CPU + {4ACE5C06-354C-45F9-A57F-B74D6AC515D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ACE5C06-354C-45F9-A57F-B74D6AC515D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ACE5C06-354C-45F9-A57F-B74D6AC515D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ACE5C06-354C-45F9-A57F-B74D6AC515D1}.Release|Any CPU.Build.0 = Release|Any CPU + {DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B}.Release|Any CPU.Build.0 = Release|Any CPU + {BB3045B4-358F-4814-97D7-F431E26F196C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB3045B4-358F-4814-97D7-F431E26F196C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB3045B4-358F-4814-97D7-F431E26F196C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB3045B4-358F-4814-97D7-F431E26F196C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0DADEAF5-284F-403D-9614-76B50AF98559} = {79FD13AA-8212-4495-A58D-CBF517EB00C7} {52BF462D-66A4-4B5B-9945-B7EDC41524CD} = {0DADEAF5-284F-403D-9614-76B50AF98559} {F7E7758D-E28B-459D-ACBE-DE78AF594AB2} = {FF0FEE4A-4334-4D98-8873-CC62A1A37C7D} {4B44E997-89E9-4E0C-8FD3-A059926CE007} = {0DADEAF5-284F-403D-9614-76B50AF98559} + {DABA4146-2600-4C2C-974F-934F9D89B643} = {63AE9FF6-FFC9-4D2A-9C36-D0BDB80BF3E9} + {6401CAD1-98BB-44AD-9A25-ABB76D53BFB7} = {DABA4146-2600-4C2C-974F-934F9D89B643} + {4ACE5C06-354C-45F9-A57F-B74D6AC515D1} = {DABA4146-2600-4C2C-974F-934F9D89B643} + {DDD2BDDD-7DC8-48D8-9D53-13CD9DE5496B} = {DABA4146-2600-4C2C-974F-934F9D89B643} + {BB3045B4-358F-4814-97D7-F431E26F196C} = {DABA4146-2600-4C2C-974F-934F9D89B643} EndGlobalSection EndGlobal diff --git a/viewer/CastViewer/CastViewer.Android/CastViewer.Android.csproj b/viewer/CastViewer/CastViewer.Android/CastViewer.Android.csproj new file mode 100644 index 0000000..42608b8 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/CastViewer.Android.csproj @@ -0,0 +1,28 @@ + + + Exe + net8.0-android + 21 + enable + com.CompanyName.CastViewer + 1 + 1.0 + apk + false + + + + + Resources\drawable\Icon.png + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Icon.png b/viewer/CastViewer/CastViewer.Android/Icon.png new file mode 100644 index 0000000..41a2a61 Binary files /dev/null and b/viewer/CastViewer/CastViewer.Android/Icon.png differ diff --git a/viewer/CastViewer/CastViewer.Android/MainActivity.cs b/viewer/CastViewer/CastViewer.Android/MainActivity.cs new file mode 100644 index 0000000..b8a2acb --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/MainActivity.cs @@ -0,0 +1,23 @@ +using Android.App; +using Android.Content.PM; +using Avalonia; +using Avalonia.Android; +using Avalonia.ReactiveUI; + +namespace CastViewer.Android; + +[Activity( + Label = "CastViewer.Android", + Theme = "@style/MyTheme.NoActionBar", + Icon = "@drawable/icon", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] +public class MainActivity : AvaloniaMainActivity +{ + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont() + .UseReactiveUI(); + } +} diff --git a/viewer/CastViewer/CastViewer.Android/Properties/AndroidManifest.xml b/viewer/CastViewer/CastViewer.Android/Properties/AndroidManifest.xml new file mode 100644 index 0000000..9f44e64 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Properties/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/AboutResources.txt b/viewer/CastViewer/CastViewer.Android/Resources/AboutResources.txt new file mode 100644 index 0000000..4cedede --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Android/Resources/drawable-night-v31/avalonia_anim.xml b/viewer/CastViewer/CastViewer.Android/Resources/drawable-night-v31/avalonia_anim.xml new file mode 100644 index 0000000..1fef3ac --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/drawable-night-v31/avalonia_anim.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/drawable-v31/avalonia_anim.xml b/viewer/CastViewer/CastViewer.Android/Resources/drawable-v31/avalonia_anim.xml new file mode 100644 index 0000000..4784f80 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/drawable-v31/avalonia_anim.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/drawable/splash_screen.xml b/viewer/CastViewer/CastViewer.Android/Resources/drawable/splash_screen.xml new file mode 100644 index 0000000..4cebfe2 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/drawable/splash_screen.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/values-night/colors.xml b/viewer/CastViewer/CastViewer.Android/Resources/values-night/colors.xml new file mode 100644 index 0000000..0f6f6c8 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/values-night/colors.xml @@ -0,0 +1,4 @@ + + + #212121 + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/values-v31/styles.xml b/viewer/CastViewer/CastViewer.Android/Resources/values-v31/styles.xml new file mode 100644 index 0000000..8075ffa --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/values-v31/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/values/colors.xml b/viewer/CastViewer/CastViewer.Android/Resources/values/colors.xml new file mode 100644 index 0000000..6fbae25 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + diff --git a/viewer/CastViewer/CastViewer.Android/Resources/values/styles.xml b/viewer/CastViewer/CastViewer.Android/Resources/values/styles.xml new file mode 100644 index 0000000..77ed2d7 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Android/Resources/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Browser/CastViewer.Browser.csproj b/viewer/CastViewer/CastViewer.Browser/CastViewer.Browser.csproj new file mode 100644 index 0000000..5affca2 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/CastViewer.Browser.csproj @@ -0,0 +1,31 @@ + + + Exe + net8.0-browser + browser-wasm + wwwroot\main.js + ./_framework + true + true + + false + full + + + + + + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Browser/Program.cs b/viewer/CastViewer/CastViewer.Browser/Program.cs new file mode 100644 index 0000000..777bacb --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/Program.cs @@ -0,0 +1,28 @@ +using System.Runtime.Versioning; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Browser; +using Avalonia.Media; +using Avalonia.ReactiveUI; +using CastViewer; + +[assembly: SupportedOSPlatform("browser")] + +internal sealed partial class Program +{ + private static Task Main(string[] args) => BuildAvaloniaApp() + //.WithInterFont() + .UseReactiveUI() + .With(new FontManagerOptions + { + FontFallbacks = [ + new(){ + FontFamily = new("avares://CastViewer/Assets/NotoSansCJKjp-Regular.otf#Noto Sans CJK JP") + } + ], + }) + .StartBrowserAppAsync("out"); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Browser/Properties/launchSettings.json b/viewer/CastViewer/CastViewer.Browser/Properties/launchSettings.json new file mode 100644 index 0000000..fe67f29 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "CastViewer.Browser": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" + } + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Browser/runtimeconfig.template.json b/viewer/CastViewer/CastViewer.Browser/runtimeconfig.template.json new file mode 100644 index 0000000..cc3f2ae --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Browser/wwwroot/Logo.svg b/viewer/CastViewer/CastViewer.Browser/wwwroot/Logo.svg new file mode 100644 index 0000000..7d28229 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/wwwroot/Logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/viewer/CastViewer/CastViewer.Browser/wwwroot/app.css b/viewer/CastViewer/CastViewer.Browser/wwwroot/app.css new file mode 100644 index 0000000..c34bee5 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/wwwroot/app.css @@ -0,0 +1,74 @@ +:root { + --sat: env(safe-area-inset-top); + --sar: env(safe-area-inset-right); + --sab: env(safe-area-inset-bottom); + --sal: env(safe-area-inset-left); +} + +/* HTML styles for the splash screen */ + +.highlight { + color: white; + font-size: 2.5rem; + display: block; +} + +.purple { + color: #8b44ac; +} + +.icon { + opacity: 0.05; + height: 35%; + width: 35%; + position: absolute; + background-repeat: no-repeat; + right: 0px; + bottom: 0px; + margin-right: 3%; + margin-bottom: 5%; + z-index: 5000; + background-position: right bottom; + pointer-events: none; +} + +#avalonia-splash a { + color: whitesmoke; + text-decoration: none; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +#avalonia-splash { + position: relative; + height: 100%; + width: 100%; + color: whitesmoke; + background: #1b2a4e; + font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; + justify-content: center; + align-items: center; +} + +.splash-close { + animation: fadeout 0.25s linear forwards; +} + +@keyframes fadeout { + 0% { + opacity: 100%; + } + + 100% { + opacity: 0; + visibility: collapse; + } +} diff --git a/viewer/CastViewer/CastViewer.Browser/wwwroot/favicon.ico b/viewer/CastViewer/CastViewer.Browser/wwwroot/favicon.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/viewer/CastViewer/CastViewer.Browser/wwwroot/favicon.ico differ diff --git a/viewer/CastViewer/CastViewer.Browser/wwwroot/index.html b/viewer/CastViewer/CastViewer.Browser/wwwroot/index.html new file mode 100644 index 0000000..a00f52c --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/wwwroot/index.html @@ -0,0 +1,30 @@ + + + + + CastViewer.Browser + + + + + + + + + + +
+
+
+

+ Powered by + Avalonia UI +

+
+ Avalonia Logo +
+
+ + + + \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Browser/wwwroot/main.js b/viewer/CastViewer/CastViewer.Browser/wwwroot/main.js new file mode 100644 index 0000000..3604add --- /dev/null +++ b/viewer/CastViewer/CastViewer.Browser/wwwroot/main.js @@ -0,0 +1,13 @@ +import { dotnet } from './_framework/dotnet.js' + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const dotnetRuntime = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +const config = dotnetRuntime.getConfig(); + +await dotnetRuntime.runMain(config.mainAssemblyName, [window.location.search]); diff --git a/viewer/CastViewer/CastViewer.Core/CastViewer.Core.csproj b/viewer/CastViewer/CastViewer.Core/CastViewer.Core.csproj new file mode 100644 index 0000000..050a30c --- /dev/null +++ b/viewer/CastViewer/CastViewer.Core/CastViewer.Core.csproj @@ -0,0 +1,15 @@ + + + net8.0 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Core/Model/DisplayCast.cs b/viewer/CastViewer/CastViewer.Core/Model/DisplayCast.cs new file mode 100644 index 0000000..2a5ed9e --- /dev/null +++ b/viewer/CastViewer/CastViewer.Core/Model/DisplayCast.cs @@ -0,0 +1,150 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using CevioCasts; +using PropertyModels.ComponentModel.DataAnnotations; +using PropertyModels.Extensions; + +namespace CastViewer.Core.Model; + +public sealed class DisplayCast : Cast +{ + [Category("Basic")] + new public string? Id { get; set; } + [Category("Basic")] + new public string? Cname { get; set; } + [Category("Basic")] + [ConditionTarget] + new public Category Category { get; set; } + + [Category("Basic")] + new public Product Product { get; set; } + [Category("Basic")] + new public Gender Gender { get; set; } + [Category("Basic")] + new public Lang Lang { get; set; } + + [Category("Basic")] + [Editable(false)] + new public BindingList? Names { get; set; } + + [Category("Basic")] + [Editable(false)] + new public BindingList? Versions { get; set; } + + [Category("Emotion")] + [Editable(false)] + [ReadOnly(true)] + [ConditionTarget] + new public bool HasEmotions { get; set; } + + [Category("Emotion")] + [Editable(false)] + [VisibilityPropertyCondition(nameof(HasEmotions), true)] + new public BindingList? Emotions { get; set; } + + [Category("Song")] + [Editable(false)] + [VisibilityPropertyCondition(nameof(Category), CevioCasts.Category.SingerSong)] + new public BindingList? SpSymbols { get; set; } + + [Category("Emotion")] + [Editable(false)] + [VisibilityPropertyCondition(nameof(HasEmotions), true)] + new public BindingList? EmotionOrder { get; set; } + + public DisplayCast(Cast? instance) + { + if(instance is null){ return; } + + // リフレクションを使用してプロパティをコピー + foreach (PropertyInfo property in typeof(Cast).GetProperties()) + { + var value = property?.GetValue(instance); + property?.SetValue(this, value); + } + + Id = instance.Id ?? "ID NOT FOUND"; + Cname = instance.Cname ?? "CNAME NOT FOUND"; + Product = instance.Product; + Gender = instance.Gender; + Lang = instance.Lang; + Names = new(instance.Names.Select(v => v.Display).ToList()); + Versions = new(instance.Versions); + Category = instance.Category; + HasEmotions = instance.HasEmotions; + + var emotions = (instance?.Emotions ?? []) + .Select(v => new(v)); + Emotions = [ .. emotions ]; + + var sbs = instance?.SpSymbols ?? []; + var symbols = sbs + .Select(v => new(v)); + SpSymbols = [.. symbols]; + + var orders = instance?.EmotionOrder is null + ? [] + : instance.EmotionOrder + .Where(item => item is not null) + .ToList() + .Select(v => new(v)) + ; + EmotionOrder = orders is null + ? [] + : [.. orders]; + } +} + +[Category("Song")] +[TypeConverter(typeof(ExpandableObjectConverter))] +public class DisplaySpSymbol +{ + [Category("Song")] + [Editable(false)] + public string? Id { get; set; } + + [Category("Song")] + [Editable(false)] + public BindingList? Names { get; set; } + + [Category("Song")] + [Editable(false)] + public BindingList? Symbols { get; set; } + + public DisplaySpSymbol(SpSymbol instance) + { + if (instance is null) { return; } + + Id = instance.Id; + Names = + new(instance.Names.Select(v => v.Display).ToList()); + Symbols = [.. instance.Symbols]; + } + + public override string ToString() + { + return $"{Id}: {string.Join(", ", Names ?? [])}, {string.Join("|", Symbols ?? [])}"; + } +} + +[Category("Emotion")] +[TypeConverter(typeof(ExpandableObjectConverter))] +public class DisplayEmotionOrder +{ + [Category("Emotion")] + [Editable(false)] + public string? Version { get; set; } + + [Category("Emotion")] + [Editable(false)] + public BindingList? Order { get; set; } + + public DisplayEmotionOrder(EmotionOrder instance) + { + if (instance is null) { return; } + + Version = instance.Version; + Order = [..instance.Order]; + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Core/Model/DisplayEmotion.cs b/viewer/CastViewer/CastViewer.Core/Model/DisplayEmotion.cs new file mode 100644 index 0000000..75c27d7 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Core/Model/DisplayEmotion.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using CevioCasts; + +namespace CastViewer.Core.Model; + +[Category("Emotion")] +[TypeConverter(typeof(ExpandableObjectConverter))] +public class DisplayEmotion(Emotion instance) : Emotion +{ + [Category("Emotion")] + [Editable(false)] + new public string Id { get; set; } = instance.Id; + + [Category("Emotion")] + [Editable(false)] + new public BindingList Names { get; set; } = + new(instance.Names.Select(v => v.Display).ToList()); +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer.Desktop/CastViewer.Desktop.csproj b/viewer/CastViewer/CastViewer.Desktop/CastViewer.Desktop.csproj new file mode 100644 index 0000000..17fe289 --- /dev/null +++ b/viewer/CastViewer/CastViewer.Desktop/CastViewer.Desktop.csproj @@ -0,0 +1,39 @@ + + + WinExe + + net8.0 + enable + true + + + + app.manifest + + + + true + /_/ + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\')) + $(RepoRoot)=$(DeterministicSourceRoot) + false + true + copyused + true + true + true + false + en-US;ja-JP + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.Desktop/Program.cs b/viewer/CastViewer/CastViewer.Desktop/Program.cs new file mode 100644 index 0000000..49d415c --- /dev/null +++ b/viewer/CastViewer/CastViewer.Desktop/Program.cs @@ -0,0 +1,23 @@ +using System; +using Avalonia; +using Avalonia.ReactiveUI; + +namespace CastViewer.Desktop; + +sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); +} diff --git a/viewer/CastViewer/CastViewer.Desktop/app.manifest b/viewer/CastViewer/CastViewer.Desktop/app.manifest new file mode 100644 index 0000000..b068dfc --- /dev/null +++ b/viewer/CastViewer/CastViewer.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.iOS/AppDelegate.cs b/viewer/CastViewer/CastViewer.iOS/AppDelegate.cs new file mode 100644 index 0000000..a78a651 --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/AppDelegate.cs @@ -0,0 +1,25 @@ +using Foundation; +using UIKit; +using Avalonia; +using Avalonia.Controls; +using Avalonia.iOS; +using Avalonia.Media; +using Avalonia.ReactiveUI; + +namespace CastViewer.iOS; + +// The UIApplicationDelegate for the application. This class is responsible for launching the +// User Interface of the application, as well as listening (and optionally responding) to +// application events from iOS. +[Register("AppDelegate")] +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix +public partial class AppDelegate : AvaloniaAppDelegate +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix +{ + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont() + .UseReactiveUI(); + } +} diff --git a/viewer/CastViewer/CastViewer.iOS/CastViewer.iOS.csproj b/viewer/CastViewer/CastViewer.iOS/CastViewer.iOS.csproj new file mode 100644 index 0000000..29fab51 --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/CastViewer.iOS.csproj @@ -0,0 +1,16 @@ + + + Exe + net8.0-ios + 13.0 + enable + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer.iOS/Entitlements.plist b/viewer/CastViewer/CastViewer.iOS/Entitlements.plist new file mode 100644 index 0000000..c2cba09 --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/Entitlements.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/viewer/CastViewer/CastViewer.iOS/Info.plist b/viewer/CastViewer/CastViewer.iOS/Info.plist new file mode 100644 index 0000000..f3ed229 --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDisplayName + CastViewer + CFBundleIdentifier + companyName.CastViewer + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 13.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/viewer/CastViewer/CastViewer.iOS/Main.cs b/viewer/CastViewer/CastViewer.iOS/Main.cs new file mode 100644 index 0000000..18744cf --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/Main.cs @@ -0,0 +1,14 @@ +using UIKit; + +namespace CastViewer.iOS; + +public class Application +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/viewer/CastViewer/CastViewer.iOS/Resources/LaunchScreen.xib b/viewer/CastViewer/CastViewer.iOS/Resources/LaunchScreen.xib new file mode 100644 index 0000000..eb51ef4 --- /dev/null +++ b/viewer/CastViewer/CastViewer.iOS/Resources/LaunchScreen.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer/App.axaml b/viewer/CastViewer/CastViewer/App.axaml new file mode 100644 index 0000000..e18bead --- /dev/null +++ b/viewer/CastViewer/CastViewer/App.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + avares://CastViewer/Assets/NotoSansCJKjp-Regular.otf + + \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer/App.axaml.cs b/viewer/CastViewer/CastViewer/App.axaml.cs new file mode 100644 index 0000000..38c914c --- /dev/null +++ b/viewer/CastViewer/CastViewer/App.axaml.cs @@ -0,0 +1,49 @@ +using System.Runtime.InteropServices; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using CastViewer.ViewModels; +using CastViewer.Views; +using HotAvalonia; + +namespace CastViewer; + +public partial class App : Application +{ + public override void Initialize() + { + switch (RuntimeInformation.OSArchitecture) + { + case Architecture.Wasm: + break; + + default: + { + this.EnableHotReload(); + break; + } + } + + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainViewModel() + }; + } + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + singleViewPlatform.MainView = new MainView + { + DataContext = new MainViewModel() + }; + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer/Assets/NotoSansCJKjp-Regular.otf b/viewer/CastViewer/CastViewer/Assets/NotoSansCJKjp-Regular.otf new file mode 100644 index 0000000..f562249 Binary files /dev/null and b/viewer/CastViewer/CastViewer/Assets/NotoSansCJKjp-Regular.otf differ diff --git a/viewer/CastViewer/CastViewer/Assets/avalonia-logo.ico b/viewer/CastViewer/CastViewer/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/viewer/CastViewer/CastViewer/Assets/avalonia-logo.ico differ diff --git a/viewer/CastViewer/CastViewer/CastViewer.csproj b/viewer/CastViewer/CastViewer/CastViewer.csproj new file mode 100644 index 0000000..d2a442b --- /dev/null +++ b/viewer/CastViewer/CastViewer/CastViewer.csproj @@ -0,0 +1,53 @@ + + + net8.0 + enable + latest + true + + + $(DefineConstants);ENABLE_XAML_HOT_RELOAD + + + true + /_/ + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\')) + $(RepoRoot)=$(DeterministicSourceRoot) + false + + en-US;ja-JP + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer/ViewLocator.cs b/viewer/CastViewer/CastViewer/ViewLocator.cs new file mode 100644 index 0000000..edd7680 --- /dev/null +++ b/viewer/CastViewer/CastViewer/ViewLocator.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using CastViewer.ViewModels; + +namespace CastViewer; + +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? param) + { + if (param is null) + { + return null; + } + + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer/ViewModels/MainViewModel.cs b/viewer/CastViewer/CastViewer/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..7b07d11 --- /dev/null +++ b/viewer/CastViewer/CastViewer/ViewModels/MainViewModel.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; +using CastViewer.Core.Model; +using CevioCasts; +using Epoxy; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Platform; +using System.IO; +using Avalonia.PropertyGrid.Controls; +using System.Diagnostics.CodeAnalysis; + +namespace CastViewer.ViewModels; + +[ViewModel] +public class MainViewModel : ViewModelBase +{ + private ImmutableList? loadedList; + + public ObservableCollection CastList { get; set; } + + public DisplayCast? SelectedCast { get; set; } + + public int SelectedCastIndex { get; set; } + public Task AsyncSelectedCast => GetCastDataAsync(); + + public bool IsLoading { get; set; } + public bool IsPgEnabled { get; set; } + + public Command? CastDataChanged { get; } + + public Pile PgPile { get; } + = Pile.Factory.Create(); + + public ImmutableList TestList { get; } = + [ + new() + { + Id = "thisisid1", + Cname = "thisiscname1", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語名前1", }, + new() { Lang = Lang.English, Display = "English name 1", }, + ], + Category = Category.SingerSong, + Product = Product.CeVIO_AI, + Gender = Gender.Male, + Lang = Lang.Japanese, + Versions = ["2.0.0", "1.0.1"], + HasEmotions = true, + Emotions = + [ + new() + { + Id = "id1", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前1", }, + new() { Lang = Lang.English, Display = "English emo name 1", }, + ] + }, + new() + { + Id = "id2", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前2", }, + new() { Lang = Lang.English, Display = "English emo name 2", }, + ] + }, + ], + SpSymbols = + [ + new() + { + Id = "sp-id1", + Symbols = ["@", "@"], + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語 sp 名前1", }, + new() { Lang = Lang.English, Display = "English sp name 1", }, + ], + }, + new() + { + Id = "sp-id2", + Symbols = ["$", "$"], + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語 sp 名前2", }, + new() { Lang = Lang.English, Display = "English sp name 2", }, + ], + } + ], + }, + new() + { + Id = "thisisid2", + Cname = "thisiscname2", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語名前2", }, + new() { Lang = Lang.English, Display = "English name 2", }, + ], + Category = Category.TextVocal, + Product = Product.VoiSona, + Gender = Gender.Female, + Lang = Lang.English, + Versions = ["2.0.0", "1.1.0", "1.0.1 ported from CeVIO AI"], + HasEmotions = true, + Emotions = + [ + new() + { + Id = "id1", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前1", }, + new() { Lang = Lang.English, Display = "English emo name 1", }, + ] + }, + new() + { + Id = "id2", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前2", }, + new() { Lang = Lang.English, Display = "English emo name 2", }, + ] + }, + new() + { + Id = "id3", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前3", }, + new() { Lang = Lang.English, Display = "English emo name 3", }, + ] + }, + new() + { + Id = "id4", + Names = + [ + new() { Lang = Lang.Japanese, Display = "日本語感情名前4", }, + new() { Lang = Lang.English, Display = "English emo name 4", }, + ] + }, + ], + EmotionOrder = + [ + new() { Version = "2.0.0", Order = ["id2", "id1", "id3", "id4"], }, + new() { Version = "1.1.0", Order = ["id1", "id2", "id3", "id4"], } + ], + }, + ]; + + public MainViewModel() + { + var list = TestList + .Select(v => new(v)) + .ToImmutableList(); + CastList = new(list); + + using var fs = new StreamReader(AssetLoader.Open(new Uri("avares://CastViewer/Assets/data.json"))); + string jsonString = fs.ReadToEnd(); + var definitions = Definitions.FromJson(jsonString, true); + loadedList = definitions + .Casts + .Select(v => new(v)) + .ToImmutableList(); + CastList = new(loadedList); + + IsPgEnabled = true; + } + + private Task GetCastDataAsync() + { + IsLoading = true; + + return Task.Run(() => { + var cast = CastList[SelectedCastIndex]; + if(cast is null) + { + IsLoading = false; + return new DisplayCast(null); + } + + IsLoading = false; + return cast; + }); + } + + [PropertyChanged(nameof(SelectedCast))] + [SuppressMessage("","IDE0051")] + private async ValueTask SelectedCastChangedAsync(DisplayCast value) + { + if(PgPile is null){return;} + + IsLoading = true; + await PgPile.RentAsync(async pg => + { + await Task.Delay(300); + pg.DataContext = value; + await Task.Delay(500); + }); + IsLoading = false; + + //return default; + } +} diff --git a/viewer/CastViewer/CastViewer/ViewModels/ViewModelBase.cs b/viewer/CastViewer/CastViewer/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..0beda65 --- /dev/null +++ b/viewer/CastViewer/CastViewer/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using ReactiveUI; + +namespace CastViewer.ViewModels; + +public class ViewModelBase : ReactiveObject +{ +} diff --git a/viewer/CastViewer/CastViewer/Views/MainView.axaml b/viewer/CastViewer/CastViewer/Views/MainView.axaml new file mode 100644 index 0000000..d96bf4b --- /dev/null +++ b/viewer/CastViewer/CastViewer/Views/MainView.axaml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/viewer/CastViewer/CastViewer/Views/MainView.axaml.cs b/viewer/CastViewer/CastViewer/Views/MainView.axaml.cs new file mode 100644 index 0000000..8b17da4 --- /dev/null +++ b/viewer/CastViewer/CastViewer/Views/MainView.axaml.cs @@ -0,0 +1,50 @@ +using System; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.PropertyGrid.Controls; +using CastViewer.ViewModels; + +namespace CastViewer.Views; + +public partial class MainView : UserControl +{ + public MainView() + { + InitializeComponent(); + } + + private void Listbox_SelectionChanged( + object? sender, + SelectionChangedEventArgs e) + { + if (sender is not ListBox listBox || listBox.SelectedItem is null) + { + return; + } + + if (DataContext is not MainViewModel vm) + { + return; + } + + //vm.IsLoading = true; + } + + private void PropertyGrid_Loaded( + object? sender, + EventArgs e + ) + { + if (sender is not PropertyGrid pg || !pg.IsLoaded) + { + return; + } + + if (DataContext is not MainViewModel vm) + { + return; + } + + vm.IsLoading = false; + } +} \ No newline at end of file diff --git a/viewer/CastViewer/CastViewer/Views/MainWindow.axaml b/viewer/CastViewer/CastViewer/Views/MainWindow.axaml new file mode 100644 index 0000000..d2f7573 --- /dev/null +++ b/viewer/CastViewer/CastViewer/Views/MainWindow.axaml @@ -0,0 +1,12 @@ + + + diff --git a/viewer/CastViewer/CastViewer/Views/MainWindow.axaml.cs b/viewer/CastViewer/CastViewer/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..7f0405e --- /dev/null +++ b/viewer/CastViewer/CastViewer/Views/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace CastViewer.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/viewer/CastViewer/Directory.Build.props b/viewer/CastViewer/Directory.Build.props new file mode 100644 index 0000000..ed5fd9c --- /dev/null +++ b/viewer/CastViewer/Directory.Build.props @@ -0,0 +1,21 @@ + + + enable + 11.0.10 + 12.0 + 8.0-Recommended + true + true + + + + + true + /_/ + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\')) + $(RepoRoot)=$(DeterministicSourceRoot) + false + false + en-US;ja-JP + +