增加 wallpaper view

This commit is contained in:
ww-rm
2025-09-27 23:43:54 +08:00
parent 4365c2a008
commit fe9b9829e2
9 changed files with 144 additions and 81 deletions

View File

@@ -73,6 +73,9 @@ namespace SpineViewer.Models
#region #region
[ObservableProperty]
private bool _wallpaperView;
[ObservableProperty] [ObservableProperty]
private bool _renderSelectedOnly; private bool _renderSelectedOnly;

View File

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">Max FPS</s:String> <s:String x:Key="Str_MaxFps">Max FPS</s:String>
<s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String> <s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String>
<s:String x:Key="Str_PlaySpeed">Playback Speed</s:String> <s:String x:Key="Str_PlaySpeed">Playback Speed</s:String>
<s:String x:Key="Str_WallpaperView">Wallpaper View</s:String>
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String> <s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
<s:String x:Key="Str_ShowAxis">Show Axis</s:String> <s:String x:Key="Str_ShowAxis">Show Axis</s:String>
<s:String x:Key="Str_BackgroundColor">Background Color</s:String> <s:String x:Key="Str_BackgroundColor">Background Color</s:String>

View File

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">最大FPS</s:String> <s:String x:Key="Str_MaxFps">最大FPS</s:String>
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String> <s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
<s:String x:Key="Str_PlaySpeed">再生速度</s:String> <s:String x:Key="Str_PlaySpeed">再生速度</s:String>
<s:String x:Key="Str_WallpaperView">壁紙表示</s:String>
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String> <s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String> <s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
<s:String x:Key="Str_BackgroundColor">背景色</s:String> <s:String x:Key="Str_BackgroundColor">背景色</s:String>

View File

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">最大帧率</s:String> <s:String x:Key="Str_MaxFps">最大帧率</s:String>
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String> <s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
<s:String x:Key="Str_PlaySpeed">播放速度</s:String> <s:String x:Key="Str_PlaySpeed">播放速度</s:String>
<s:String x:Key="Str_WallpaperView">桌面投影</s:String>
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String> <s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String> <s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String> <s:String x:Key="Str_BackgroundColor">背景颜色</s:String>

View File

@@ -77,7 +77,15 @@ namespace SpineViewer.ViewModels.MainWindow
public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel; public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel;
private readonly SFMLRendererViewModel _sfmlRendererViewModel; private readonly SFMLRendererViewModel _sfmlRendererViewModel;
public RelayCommand Cmd_Exit => new(App.Current.Shutdown); public RelayCommand Cmd_SwitchWallpaperView => _cmd_SwitchWallpaperView ??= new(() =>
{
_preferenceViewModel.WallpaperView = !_preferenceViewModel.WallpaperView;
_preferenceViewModel.SavePreference();
});
private RelayCommand _cmd_SwitchWallpaperView;
public RelayCommand Cmd_Exit => _cmd_Exit ??= new(App.Current.Shutdown);
private RelayCommand? _cmd_Exit;
/// <summary> /// <summary>
/// 打开工作区 /// 打开工作区
@@ -137,5 +145,6 @@ namespace SpineViewer.ViewModels.MainWindow
_spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects; _spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects;
} }
} }
} }
} }

View File

@@ -111,6 +111,7 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = DebugPoints, DebugPoints = DebugPoints,
DebugClippings = DebugClippings, DebugClippings = DebugClippings,
WallpaperView = WallpaperView,
RenderSelectedOnly = RenderSelectedOnly, RenderSelectedOnly = RenderSelectedOnly,
AssociateFileSuffix = AssociateFileSuffix, AssociateFileSuffix = AssociateFileSuffix,
AppLanguage = AppLanguage, AppLanguage = AppLanguage,
@@ -136,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = value.DebugPoints; DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings; DebugClippings = value.DebugClippings;
WallpaperView = value.WallpaperView;
RenderSelectedOnly = value.RenderSelectedOnly; RenderSelectedOnly = value.RenderSelectedOnly;
AssociateFileSuffix = value.AssociateFileSuffix; AssociateFileSuffix = value.AssociateFileSuffix;
AppLanguage = value.AppLanguage; AppLanguage = value.AppLanguage;
@@ -244,6 +246,19 @@ namespace SpineViewer.ViewModels.MainWindow
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray(); public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public bool AutoRun
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public bool WallpaperView
{
get => _wallpaperView;
set => SetProperty(ref _wallpaperView, value);
}
private bool _wallpaperView; // UI 变化通过 PropertyChanged 事件交由 View 层处理
public bool RenderSelectedOnly public bool RenderSelectedOnly
{ {
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly; get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;

View File

@@ -940,7 +940,7 @@
MouseDoubleClick="_notifyIcon_MouseDoubleClick"> MouseDoubleClick="_notifyIcon_MouseDoubleClick">
<hc:NotifyIcon.ContextMenu> <hc:NotifyIcon.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="There may be a funtion :)" IsChecked="True"/> <MenuItem Header="{DynamicResource Str_WallpaperView}" Command="{Binding Cmd_SwitchWallpaperView}" IsChecked="{Binding PreferenceViewModel.WallpaperView}"/>
<Separator/> <Separator/>
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/> <MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/>
</ContextMenu> </ContextMenu>

View File

@@ -12,6 +12,7 @@ using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection.Metadata;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -47,67 +48,28 @@ public partial class MainWindow : Window
InitializeComponent(); InitializeComponent();
InitializeLogConfiguration(); InitializeLogConfiguration();
_wallpaperRenderWindow = new(new(500, 500), "SpineViewerWallpaper", SFML.Window.Styles.None); // Initialize Wallpaper RenderWindow
_wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None);
_wallpaperRenderWindow.SetVisible(false); _wallpaperRenderWindow.SetVisible(false);
var handle = _wallpaperRenderWindow.SystemHandle;
var style = User32.GetWindowLong(handle, User32.GWL_STYLE) | User32.WS_POPUP;
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED | User32.WS_EX_TOOLWINDOW;
User32.SetWindowLong(handle, User32.GWL_STYLE, style);
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow); DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow);
// XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定 // XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定
_notifyIcon.Text = _vm.Title; _notifyIcon.Text = _vm.Title;
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
Loaded += MainWindow_Loaded; Loaded += MainWindow_Loaded;
ContentRendered += MainWindow_ContentRendered; ContentRendered += MainWindow_ContentRendered;
Closed += MainWindow_Closed; Closed += MainWindow_Closed;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
{ _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
var vm = _vm.SFMLRendererViewModel; _vm.PreferenceViewModel.PropertyChanged += PreferenceViewModel_PropertyChanged;
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
_renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
// 设置默认参数并启动渲染
vm.SetResolution(1500, 1000);
vm.Zoom = 0.75f;
vm.CenterX = 0;
vm.CenterY = 0;
vm.FlipY = true;
vm.MaxFps = 30;
vm.StartRender();
// 加载首选项
_vm.PreferenceViewModel.LoadPreference();
LoadLastState();
}
private void MainWindow_ContentRendered(object? sender, EventArgs e)
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
string[] filePaths = args.Skip(1).ToArray();
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
}
}
private void MainWindow_Closed(object? sender, EventArgs e)
{
SaveLastState();
var vm = _vm.SFMLRendererViewModel;
vm.StopRender();
}
/// <summary>
/// 给管道通信提供的打开文件外部调用方法
/// </summary>
public void OpenFiles(IEnumerable<string> filePaths)
{
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
} }
/// <summary> /// <summary>
@@ -195,6 +157,95 @@ public partial class MainWindow : Window
JsonHelper.Serialize(m, LastStateFilePath); JsonHelper.Serialize(m, LastStateFilePath);
} }
/// <summary>
/// 给管道通信提供的打开文件外部调用方法
/// </summary>
public void OpenFiles(IEnumerable<string> filePaths)
{
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
}
#region MainWindow
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var vm = _vm.SFMLRendererViewModel;
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
_renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
// 设置默认参数并启动渲染
vm.SetResolution(1500, 1000);
vm.Zoom = 0.75f;
vm.CenterX = 0;
vm.CenterY = 0;
vm.FlipY = true;
vm.MaxFps = 30;
vm.StartRender();
// 加载首选项
_vm.PreferenceViewModel.LoadPreference();
LoadLastState();
}
private void MainWindow_ContentRendered(object? sender, EventArgs e)
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
string[] filePaths = args.Skip(1).ToArray();
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
}
}
private void MainWindow_Closed(object? sender, EventArgs e)
{
SaveLastState();
var vm = _vm.SFMLRendererViewModel;
vm.StopRender();
}
#endregion
#region PreferenceViewModel
private void PreferenceViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PreferenceViewModel.WallpaperView))
{
if (_vm.PreferenceViewModel.WallpaperView)
{
var workerw = User32.GetWorkerW();
if (workerw == IntPtr.Zero)
{
_logger.Error("Failed to enable wallpaper view, WorkerW not found");
return;
}
var wnd = _wallpaperRenderWindow;
var handle = wnd.SystemHandle;
User32.GetPrimaryScreenResolution(out var sw, out var sh);
User32.SetParent(handle, workerw);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
wnd.Position = new(0, 0);
wnd.Size = new(sw + 1, sh);
wnd.Size = new(sw, sh);
wnd.SetVisible(true);
}
else
{
_wallpaperRenderWindow.SetVisible(false);
}
}
}
#endregion
#region _spinesListView #region _spinesListView
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e) private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
@@ -609,29 +660,7 @@ public partial class MainWindow : Window
private void DebugMenuItem_Click(object sender, RoutedEventArgs e) private void DebugMenuItem_Click(object sender, RoutedEventArgs e)
{ {
#if DEBUG #if DEBUG
var www = _wallpaperRenderWindow;
User32.GetPrimaryScreenResolution(out var sw, out var sh);
www.Position = new(0, 0);
www.Size = new(sw, sh);
var handle = www.SystemHandle;
Debug.WriteLine($"Handle: {handle:x8}");
var style = User32.GetWindowLong(handle, User32.GWL_STYLE) | User32.WS_POPUP;
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED | User32.WS_EX_TOOLWINDOW | User32.WS_EX_TOPMOST;
User32.SetWindowLong(handle, User32.GWL_STYLE, style);
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, 200, User32.LWA_ALPHA);
var workerw = User32.GetWorkerW();
var res = User32.SetParent(handle, workerw);
Debug.WriteLine($"SetParent: {res:x8}");
User32.SetLayeredWindowAttributes(handle, 0, 255, User32.LWA_ALPHA);
var s = www.Size;
www.Size = new(s.X + 1, s.Y + 1);
www.Size = s;
www.SetVisible(true);
#endif #endif
} }
} }

View File

@@ -144,16 +144,20 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/> <Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_WallpaperView}"/>
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/> <ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/> <Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/> <ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Language}"/> <Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
<ComboBox Grid.Row="2" Grid.Column="1" <ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Row="3" Grid.Column="1"
SelectedItem="{Binding AppLanguage}" SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/> ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>