Compare commits

..

32 Commits

Author SHA1 Message Date
ww-rm
9040e02025 Merge pull request #140 from ww-rm/dev/wpf
v0.16.10
2025-10-29 21:35:34 +08:00
ww-rm
8b0ea750d8 Merge pull request #139 from ww-rm/dev/wpf
v0.16.10
2025-10-29 20:45:24 +08:00
ww-rm
64bc12db06 Merge pull request #136 from ww-rm/dev/wpf
v0.16.9
2025-10-27 23:51:47 +08:00
ww-rm
0893bd4b54 Merge pull request #133 from ww-rm/dev/wpf
v0.16.8
2025-10-19 20:19:48 +08:00
ww-rm
4b23c779d3 Merge pull request #124 from ww-rm/dev/wpf
v0.16.7
2025-10-06 14:04:13 +08:00
ww-rm
249b930602 Merge pull request #122 from ww-rm/dev/wpf
v0.16.6
2025-10-04 20:55:59 +08:00
ww-rm
6727fa8e8f Merge pull request #120 from ww-rm/dev/wpf
v0.16.5
2025-10-04 16:59:08 +08:00
ww-rm
a0b7db0a70 Merge pull request #119 from ww-rm/dev/wpf
v0.16.4
2025-10-04 00:11:15 +08:00
ww-rm
03c4974c9f Merge pull request #118 from ww-rm/dev/wpf
v0.16.4
2025-10-03 23:50:05 +08:00
ww-rm
6f9b357473 Merge pull request #116 from ww-rm/dev/wpf
v0.16.3
2025-10-02 14:22:11 +08:00
ww-rm
d1d32b6292 Merge pull request #115 from ww-rm/dev/wpf
v0.16.2
2025-10-01 23:48:03 +08:00
ww-rm
47aafc7948 Merge pull request #113 from ww-rm/dev/wpf
v0.16.1
2025-09-30 22:19:01 +08:00
ww-rm
267c7b81c3 Merge pull request #111 from ww-rm/dev/wpf
update readme
2025-09-30 12:19:17 +08:00
ww-rm
6e46152e4c Merge pull request #110 from ww-rm/dev/wpf
update readme
2025-09-30 12:00:41 +08:00
ww-rm
34f9eeff2c Merge pull request #109 from ww-rm/dev/wpf
v0.16.0
2025-09-30 11:49:45 +08:00
ww-rm
f7ace4dfe9 Merge pull request #106 from ww-rm/dev/wpf
v0.15.19
2025-09-27 23:48:05 +08:00
ww-rm
0443d5e3d5 Merge pull request #104 from ww-rm/dev/wpf
v0.15.18
2025-09-24 23:45:55 +08:00
ww-rm
a28cb3f424 Merge pull request #102 from ww-rm/dev/wpf
v0.15.17
2025-09-21 10:09:56 +08:00
ww-rm
2c3b076b58 Merge pull request #101 from ww-rm/dev/wpf
v0.15.16
2025-09-21 01:14:57 +08:00
ww-rm
b3cd0b9349 Merge pull request #99 from ww-rm/dev/wpf
v0.15.15
2025-09-11 23:20:45 +08:00
ww-rm
5ef13239da Merge pull request #97 from ww-rm/dev/wpf
v0.15.14
2025-09-08 00:07:36 +08:00
ww-rm
0c16b2f104 Merge pull request #93 from ww-rm/dev/wpf
v0.15.13
2025-09-04 20:09:18 +08:00
ww-rm
707aa7f570 Merge pull request #91 from ww-rm/dev/wpf
v0.15.12
2025-09-03 21:58:18 +08:00
ww-rm
be8193e235 Merge pull request #90 from ww-rm/dev/wpf
v0.15.12
2025-09-03 21:43:07 +08:00
ww-rm
8e1f586d4f Merge pull request #86 from ww-rm/dev/wpf
fix bug
2025-08-20 22:44:47 +08:00
ww-rm
ad190d8952 Merge pull request #85 from ww-rm/dev/wpf
v0.15.11
2025-08-20 22:36:14 +08:00
ww-rm
40bde84648 Merge pull request #81 from ww-rm/dev/wpf
v0.15.10
2025-08-18 18:54:13 +08:00
ww-rm
a697ccc923 Merge pull request #80 from ww-rm/dev/wpf 2025-08-18 01:26:40 +08:00
ww-rm
65508782c6 Merge pull request #77 from ww-rm/dev/wpf
v0.15.8
2025-08-01 00:03:42 +08:00
ww-rm
7bc82ab318 Merge pull request #75 from ww-rm/dev/wpf
fix bug
2025-07-26 23:06:01 +08:00
ww-rm
eca59dc67b Merge pull request #74 from ww-rm/dev/wpf
v0.15.7
2025-07-26 23:01:21 +08:00
ww-rm
497103bdb6 Merge pull request #69 from ww-rm/dev/wpf 2025-07-25 14:00:15 +08:00
77 changed files with 641 additions and 1035 deletions

View File

@@ -1,21 +1,5 @@
# CHANGELOG
## v0.16.12
- 修复 label 控件文字显示问题
- 增强报错日志输出
- 增加实时帧率显示
- 首选项增加预览画面和投影最大帧率设置,移除用户状态和工作区帧率记忆
- 优化某些性能
## v0.16.11
- 增加 shift 切换缩放倍数
- 改善后台性能
- 修复字体显示颜色问题
- 调整浏览目录参数保存至用户状态
- 调整浏览面板至最后
## v0.16.10
- 增加 Linux 平台 CLI 工具构建

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.0</Version>
<UseWPF>true</UseWPF>

View File

@@ -1,154 +1,139 @@
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
[![GitHub Release](https://img.shields.io/github/v/release/ww-rm/SpineViewer?logo=github\&logoColor=959da5\&label=Release\&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
[![Downloads](https://img.shields.io/github/downloads/ww-rm/SpineViewer/total?logo=github\&logoColor=959da5\&label=Downloads\&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
![Languages](https://img.shields.io/badge/Languages-中文%20%7C%20English%20%7C%20日本語-blue)
[中文](README.md) | [English](README.en.md)
Spine file viewer & exporter, also a dynamic wallpaper program supporting Spine animations.
A simple and user-friendly Spine file viewer and exporter with multi-language support (Chinese/English/Japanese).
![previewer](https://github.com/user-attachments/assets/697ae86f-ddf0-445d-951c-cf04f5206e40)
[https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0](https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0)
---
https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
## Features
- Supports multiple Spine file versions (`2.1.x; 3.4.x - 4.2.-`)
- List-based multi-skeleton view with rendering order management
- Supports multi-track animations
- Supports skin/slot/attachment settings
- Debug rendering support
- Frame rate / model / track time scale adjustment
- Track alpha blending control
- Export single frame / GIF / video
- Custom export via FFmpeg
- Supports non-PNG texture formats
- Desktop dynamic wallpaper with auto-start support
- Multiple versions of Spine files
- Batch file opening via drag-and-drop or copy-paste
- Batch preview
- List-based multi-skeleton viewing and render order management
- Multi-selection in lists for batch skeleton parameter settings
- Multi-track animation settings
- Skin and custom slot attachment settings
- Custom slot visibility
- Debug rendering
- Playback speed adjustment for view/model/track timelines
- Track alpha blending parameter settings
- Fullscreen preview
- Export to single frame, image sequence, animated GIF, or video file
- Automatic resolution batch export
- Custom export with FFmpeg
- Program parameter saving
- File extension association
- Texture images in formats other than PNG
- Launch at startup with persistent dynamic wallpaper
- ......
---
### Supported Spine Versions
| Version | View & Export |
| :-----: | :------------------: |
| `2.1.x` | :white\_check\_mark: |
| `3.4.x` | :white\_check\_mark: |
| `3.5.x` | :white\_check\_mark: |
| `3.6.x` | :white\_check\_mark: |
| `3.7.x` | :white\_check\_mark: |
| `3.8.x` | :white\_check\_mark: |
| `4.0.x` | :white\_check\_mark: |
| `4.1.x` | :white\_check\_mark: |
| `4.2.x` | :white\_check\_mark: |
| `4.3.x` | |
More versions under development \:rocket: \:rocket: \:rocket:
### Supported Export Formats
| Format | Use Case |
| -------------- | ----------------------------------------------------------------------------- |
| Single Frame | Generate high-resolution images of models; manually adjust the desired frame. |
| Frame Sequence | Supports PNG format with transparency and lossless compression. |
| GIF/Video | Export preview animations or common video formats. |
| Custom Export | Supports arbitrary FFmpeg parameters for custom, complex export needs. |
## Installation
Download the compressed package from the [Releases](https://github.com/ww-rm/SpineViewer/releases) page.
Download the compressed package from the [Release](https://github.com/ww-rm/SpineViewer/releases) page.
The program requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) to be installed.
The software requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) to run.
You can also download packages with the `SelfContained` suffix, which can run independently without additional installations.
Alternatively, download the package with the `SelfContained` suffix for standalone execution.
Exporting GIF/MP4 or other animated/video formats requires **ffmpeg** installed locally and added to the system PATH. Download [FFmpeg for Windows](https://ffmpeg.org/download.html#build-windows) or the latest full build [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
---
## Changing Display Language
Currently, the program supports the following interface languages:
- `ZH` (Chinese)
- `EN` (English)
- `JA` (Japanese)
Change the language via the menu: **File → Preferences… → Language**, then confirm.
---
For exporting GIF/MP4 and other animation/video formats, FFmpeg must be installed and added to the system environment variables. Visit the [FFmpeg Windows download page](https://ffmpeg.org/download.html#build-windows) or download the latest version directly: [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
## Usage
### Overview
### How to Change the Display Language
The program uses a left-right layout: the left panel contains controls, the right panel displays the preview.
In the menu, go to "File" -> "Preferences..." -> "Language," select your desired language, and confirm the change.
The left panel contains three sub-panels:
### Basic Overview
- **Models**: Lists imported and rendered models. Set model parameters, rendering order, and other model-related functions here.
- **Browser**: Preview files in a folder without actually importing them. Generate WebP previews or import selected models.
- **Canvas**: Set parameters for the right-side preview display.
The program is organized into a left-right layout:
Most buttons, labels, or input fields show help text on hover.
- **Left Panel:** Functionality panel.
- **Right Panel:** Preview display.
---
The left panel includes three sub-panels:
### Importing Skeletons
- **Browse:** Preview the content of a specified folder without importing files into the program. This panel allows generating `.webp` previews for models or importing selected models.
- **Model:** Lists imported models for rendering. Parameters and rendering order can be adjusted here, along with other model-related functionalities.
- **Display:** Adjust parameters for the right-side preview display.
Drag-and-drop or paste skeleton files/folders directly into the **Models** panel.
Hover your mouse over buttons, labels, or input fields to see help text for most UI elements.
Alternatively, use the right-click menu in the **Browser** panel to import selected items.
### Skeleton Import
---
Drag-and-drop or paste skeleton files/directories into the Model panel.
### Adjusting Content
Alternatively, use the right-click menu in the Browse panel to import selected items.
The **Models** panel supports right-click menus, some hotkeys, and batch editing via multi-selection.
### Content Adjustment
Mouse interactions in the preview panel:
The Model panel supports right-click menus, some shortcuts, and batch adjustments of model parameters through multi-selection.
- **Left click**: select and drag models. Hold `Ctrl` for multi-selection (synchronized with the model list).
- **Right click**: drag the entire canvas.
- **Mouse wheel**: zoom in/out. Hold `Ctrl` to scale selected models together, use `Shift` to switch zoom factor.
- **Render selected only**: preview only the selected models, selection can only be changed via the left panel.
For preview display adjustments:
Playback controls below the preview allow time adjustment, acting as a simple player.
- **Left-click:** Select and drag models. Hold `Ctrl` for multi-selection, synchronized with the left-side list.
- **Right-click:** Drag the entire display.
- **Scroll wheel:** Zoom in/out. Hold `Ctrl` to scale selected models.
- **Render selected-only mode:** In this mode, the preview only shows selected models, and selection status can only be changed via the left-side list.
---
The buttons below the preview display allow time adjustments, serving as a simple playback control.
### Exporting Content
### Content Export
Right-click on models in the list to access export options.
Export follows the **WYSIWYG (What You See Is What You Get)** principle, meaning the preview display reflects the exported output.
Key export parameters:
Use the right-click menu in the Model panel to export selected items.
- **Output folder**: Optional. If not provided, outputs go to each models folder. Otherwise, all outputs go to the specified folder.
- **Single export**: Default exports each model separately. If enabled, all selected models are rendered together in one output.
- **Auto resolution**: Ignores preview canvas resolution; exported resolution matches the actual size of content. For animations or videos, ensures full display of the animation.
Key export parameters include:
---
- **Output folder:** Optional. When not specified, output is saved to the respective model folder; otherwise, all output is saved to the provided folder.
- **Export single:** By default, each model is exported independently. Selecting "Export single" renders all selected models in a single frame, producing a unified output.
- **Auto resolution:** Ignores the preview resolution and viewport parameters, exporting output at the actual size of the content. For animations/videos, the output matches the size required for full visibility.
### Dynamic Wallpaper
The dynamic wallpaper projects the current preview content to the desktop in real time.
Dynamic wallpaper is implemented through desktop projection, allowing the content of the current preview to be projected onto the desktop in real time.
Enable or disable via program preferences or the tray icon menu. Save workspace files to preserve model and canvas settings.
You can enable or disable desktop projection from the program preferences or the right-click menu of the tray icon. After adjusting the model and display parameters, you can save the current configuration as a workspace file for convenient restoration later.
Auto-start with Windows can also be enabled, along with loading a specific workspace on startup.
If you want the wallpaper to stay active after startup, you can enable auto-start in the preferences and specify which workspace file should be loaded when the program launches.
---
### More Information
### Command-line Tool
The project includes a CLI tool `SpineViewerCLI` for simple operations on a single model (querying parameters, exporting, etc.). Windows and Linux binaries are provided in Releases.
```bash
$ SpineViewerCLI -h
Description:
Root Command
Usage:
SpineViewerCLI [command] [options]
Options:
-q, --quiet Suppress console logging (quiet mode).
-?, -h, --help Show help and usage information
--version Show version information
Commands:
query <skel> Query information of single model
preview <skel> Preview a model
export <skel> Export single model
```
---
### More
Detailed instructions and usage guides can be found in the [Wiki](https://github.com/ww-rm/SpineViewer/wiki).
Report issues or bugs via [GitHub Issues](https://github.com/ww-rm/SpineViewer/issues).
---
For detailed usage and documentation, see the [Wiki](https://github.com/ww-rm/SpineViewer/wiki). For usage questions or bug reports, submit an [Issue](https://github.com/ww-rm/SpineViewer/issues).
## Acknowledgements
@@ -162,6 +147,6 @@ Report issues or bugs via [GitHub Issues](https://github.com/ww-rm/SpineViewer/i
---
*If you like this project, please give it a :star: and share it with others! :\)*
*If you find this project helpful, please give it a \:star: and share it with others! :)*
[![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer)

View File

@@ -1,6 +1,6 @@
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
[![GitHub Release](https://img.shields.io/github/v/release/ww-rm/SpineViewer?logo=github&logoColor=959da5&label=Release&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
[![Downloads](https://img.shields.io/github/downloads/ww-rm/SpineViewer/total?logo=github&logoColor=959da5&label=Downloads&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
@@ -16,19 +16,53 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
## 功能
- 支持多版本 spine 文件 (`2.1.x; 3.4.x - 4.2.x`)
- 支持多版本 spine 文件
- 支持拖拽/复制粘贴批量打开文件
- 支持批量预览
- 支持列表式多骨骼查看和渲染层级管理
- 支持多轨道动画
- 支持皮肤/插槽/附件设置
- 支持列表多选批量设置骨骼参数
- 支持多轨道动画设置
- 支持皮肤/自定义插槽附件设置
- 支持自定义插槽可见性
- 支持调试渲染
- 支持画面/模型/轨道时间倍速设置
- 支持设置轨道 Alpha 混合参数
- 支持全屏预览
- 支持单帧/动图/视频文件导出
- 支持自动分辨率批量导出
- 支持 FFmpeg 自定义导出
- 支持非 PNG 格式的纹理图片格式
- 支持程序参数保存
- 支持文件后缀关联
- 支持非 png 格式的纹理图片格式
- 支持开机自启常驻动态壁纸
- ......
### Spine 版本支持
| 版本 | 查看&导出 |
| :---: | :---: |
| `2.1.x` | :white_check_mark: |
| `3.4.x` | :white_check_mark: |
| `3.5.x` | :white_check_mark: |
| `3.6.x` | :white_check_mark: |
| `3.7.x` | :white_check_mark: |
| `3.8.x` | :white_check_mark: |
| `4.0.x` | :white_check_mark: |
| `4.1.x` | :white_check_mark: |
| `4.2.x` | :white_check_mark: |
| `4.3.x` | |
更多版本正在施工 :rocket: :rocket: :rocket:
### 导出格式支持
| 导出格式 | 适用场景 |
| --- | --- |
| 单帧画面 | 支持生成高清模型画面图像, 可手动调节需要的一帧. |
| 帧序列 | 支持 PNG 格式帧序列, 可保留透明通道且无损压缩. |
| 动图/视频 | 可以生成预览动图或者常见格式视频. |
| 自定义导出 | 除上述预设方案, 支持提供任意 FFmpeg 参数进行导出, 满足自定义复杂需求. |
## 安装
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
@@ -39,26 +73,20 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
导出 GIF/MP4 等动图/视频格式需要在本地安装 ffmpeg 命令行, 并且添加至环境变量, [点击前往 FFmpeg-Windows 下载页面](https://ffmpeg.org/download.html#build-windows), 也可以点这个下载最新版本 [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
## 修改显示语言
本项目目前支持以下界面显示语言:
- `ZH` (中文)
- `EN` (English)
- `JA` (日本語)
可以通过窗口菜单的 "文件" -> "首选项..." -> "语言", 选择你需要的语言并确认修改.
## 使用方法
### 如何修改显示语言
窗口菜单的 "文件" -> "首选项..." -> "语言", 选择你需要的语言并确认修改.
### 基本介绍
程序大致是左右布局, 左侧是功能面板, 右侧是画面.
左侧有三个子面板, 分别是:
- **模型**. 该面板记录导入并进行渲染的模型列表, 可以在这个面板设置与模型渲染相关的参数和渲染顺序, 以及一些与模型有关的功能.
- **浏览**. 该面板用于预览指定文件夹的内容, 并没有真正导入文件到程序. 在该面板可以为模型生成 webp 格式的预览图, 或者导入选中的模型.
- **模型**. 该面板记录导入并进行渲染的模型列表, 可以在这个面板设置与模型渲染相关的参数和渲染顺序, 以及一些与模型有关的功能.
- **画面**. 该面板用于设置右侧预览画面的参数.
绝大部分按钮或者标签或者输入框都可以通过鼠标指针悬停来获取帮助文本.
@@ -77,14 +105,16 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
- 左键可以选择和拖拽模型, 按下 `Ctrl` 键可以实现多选, 与左侧列表选择是联动的.
- 右键对整体画面进行拖动.
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放, `Shift` 可以切换缩放倍数.
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放.
- 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态.
预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器.
### 内容导出
在模型列表里, 右键单击选中的模型, 弹出菜单里可以对选中项执行导出操作.
导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面.
在模型面板里, 右键菜单可以对选中项进行导出操作.
导出有以下几个关键参数:
@@ -100,29 +130,6 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
如果希望开机自启常驻壁纸, 也可以在首选项中启用开机自启, 并且设置启动后需要加载的工作区文件.
### 命令行工具
项目附带一个纯命令行工具 `SpineViewerCLI`, 目前支持对单个模型执行一些简单操作, 例如参数值查询以及导出等, 并且 Release 界面提供 Windows 和 Linux 多平台二进制文件.
```bash
$ SpineViewerCLI -h
Description:
Root Command
Usage:
SpineViewerCLI [command] [options]
Options:
-q, --quiet Suppress console logging (quiet mode).
-?, -h, --help Show help and usage information
--version Show version information
Commands:
query <skel> Query information of single model
preview <skel> Preview a model
export <skel> Export single model
```
### 更多
更为详细的使用方法和说明见 [Wiki](https://github.com/ww-rm/SpineViewer/wiki), 有使用上的问题或者 BUG 可以提个 [Issue](https://github.com/ww-rm/SpineViewer/issues).
@@ -139,6 +146,6 @@ Commands:
---
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :\)*
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :)*
[![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer)

View File

@@ -7,7 +7,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using Win32Natives;
namespace SFMLRenderer
{
@@ -20,14 +19,6 @@ namespace SFMLRenderer
SetActive(false);
_timer.Tick += (s, e) => DispatchEvents();
_timer.Start();
SetVisible(false);
var handle = SystemHandle;
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED;
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
RendererCreated?.Invoke(this, EventArgs.Empty);
}

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.6</Version>
<UseWPF>true</UseWPF>
@@ -23,8 +20,4 @@
<PackageReference Include="SFML.Net" Version="2.6.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Win32Natives\Win32Natives.csproj" />
</ItemGroup>
</Project>

View File

@@ -14,14 +14,6 @@ namespace Spine.Exporters
/// </summary>
public abstract class BaseExporter : IDisposable
{
/// <summary>
/// 进度回调函数
/// </summary>
/// <param name="total">任务总量</param>
/// <param name="done">已完成量</param>
/// <param name="promptText">需要设置的进度提示文本</param>
public delegate void ProgressReporterHandler(float total, float done, string promptText);
/// <summary>
/// 日志器
/// </summary>
@@ -64,9 +56,14 @@ namespace Spine.Exporters
/// <summary>
/// 可选的进度回调函数
/// <list type="number">
/// <item><c>total</c>: 任务总量</item>
/// <item><c>done</c>: 已完成量</item>
/// <item><c>progressText</c>: 需要设置的进度提示文本</item>
/// </list>
/// </summary>
public ProgressReporterHandler? ProgressReporter { get => _progressReporter; set => _progressReporter = value; }
protected ProgressReporterHandler? _progressReporter;
public Action<float, float, string>? ProgressReporter { get => _progressReporter; set => _progressReporter = value; }
protected Action<float, float, string>? _progressReporter;
/// <summary>
/// 背景颜色

View File

@@ -92,7 +92,7 @@ namespace Spine.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
}
}

View File

@@ -144,7 +144,7 @@ namespace Spine.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
}
}

View File

@@ -47,7 +47,7 @@ namespace Spine.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to save frame {0}, {1}", savePath, ex.Message);
}
finally

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V21
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V21
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V21
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V21
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V34
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V34
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V34
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V34
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V35
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V35
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V35
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V35
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V36
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V36
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V36
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V36
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V37
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V37
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V37
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V37
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -37,7 +37,7 @@ namespace Spine.Implementations.V38
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -51,7 +51,7 @@ namespace Spine.Implementations.V38
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -63,7 +63,7 @@ namespace Spine.Implementations.V38
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -71,7 +71,7 @@ namespace Spine.Implementations.V38
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V40
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V40
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V40
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V40
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V41
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V41
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V41
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V41
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V42
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V42
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V42
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V42
catch (Exception ex)
{
_atlas.Dispose();
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}

View File

@@ -6,12 +6,9 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.11</Version>
<Version>0.16.10</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -269,7 +269,7 @@ namespace Spine
if (hit && LogHitSlots)
{
_logger.Info("Hit ({0}): [{1}]", self.Name, hitSlotName);
_logger.Debug("Hit ({0}): [{1}]", self.Name, hitSlotName);
}
return hit;
}

View File

@@ -82,7 +82,7 @@ namespace Spine
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Warn("Failed to detect version for skel {0}, try all available versions", skelPath);
}
}
@@ -118,7 +118,7 @@ namespace Spine
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load spine with version '{version}'");
}
}

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>2.1.25</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>3.4.2</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>3.5.51</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>3.6.53</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>3.7.94</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>3.8.99</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>4.0.64</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>4.1.54</Version>
</PropertyGroup>

View File

@@ -6,10 +6,7 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>4.2.74</Version>
</PropertyGroup>

View File

@@ -43,8 +43,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime35", "SpineRunt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime34", "SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj", "{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Win32Natives", "Win32Natives\Win32Natives.csproj", "{48864874-7307-950E-A667-62BB66357C62}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -107,10 +105,6 @@ Global
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Debug|x64.Build.0 = Debug|x64
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.ActiveCfg = Release|x64
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.Build.0 = Release|x64
{48864874-7307-950E-A667-62BB66357C62}.Debug|x64.ActiveCfg = Debug|x64
{48864874-7307-950E-A667-62BB66357C62}.Debug|x64.Build.0 = Debug|x64
{48864874-7307-950E-A667-62BB66357C62}.Release|x64.ActiveCfg = Release|x64
{48864874-7307-950E-A667-62BB66357C62}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,8 +1,7 @@
using Microsoft.Win32;
using NLog;
using Win32Natives;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.MainWindow;
using SpineViewer.Views;
using System.Collections.Frozen;
@@ -15,7 +14,6 @@ using System.IO.Pipes;
using System.Reflection;
using System.Windows;
using System.Windows.Interop;
using SpineViewer.Extensions;
namespace SpineViewer
{
@@ -25,18 +23,16 @@ namespace SpineViewer
public partial class App : Application
{
#if DEBUG
public const bool IsDebug = true;
public const string AppName = "SpineViewer_D";
public const string ProgId = "SpineViewer_D.skel";
#else
public const bool IsDebug = false;
public const string AppName = "SpineViewer";
public const string ProgId = "SpineViewer.skel";
#endif
public const string AutoRunFlag = "--autorun";
private const string MutexName = $"__{AppName}_Instance__";
private const string PipeName = $"_{AppName}_Pipe__";
private const string MutexName = "__SpineViewerInstance__";
private const string PipeName = "__SpineViewerPipe__";
public static readonly string ProcessPath = Environment.ProcessPath;
public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath);
@@ -60,16 +56,13 @@ namespace SpineViewer
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
_logger.Debug(e.ExceptionObject.ToString());
_logger.Fatal("Unhandled exception: {0}", e.ExceptionObject);
MessagePopupService.Error(e.ExceptionObject.ToString());
};
TaskScheduler.UnobservedTaskException += (s, e) =>
{
_logger.Debug(e.Exception.ToString());
_logger.Fatal("Unobserved task exception: {0}", e.Exception.Message);
_logger.Trace(e.Exception.ToString());
_logger.Error("Unobserved task exception: {0}", e.Exception.Message);
e.SetObserved();
MessagePopupService.Error(e.Exception.ToString());
};
// 单例模式加 IPC 通信
@@ -135,7 +128,7 @@ namespace SpineViewer
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to pass command line args to existed instance, {0}", ex.Message);
}
}
@@ -196,7 +189,7 @@ namespace SpineViewer
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to process arguments, {0}", ex.Message);
}
}
@@ -217,10 +210,9 @@ namespace SpineViewer
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
_logger.Debug(e.Exception.ToString());
_logger.Fatal("Dispatcher unhandled exception: {0}", e.Exception.Message);
_logger.Trace(e.Exception.ToString());
_logger.Error("Dispatcher unhandled exception: {0}", e.Exception.Message);
e.Handled = true;
MessagePopupService.Error(e.Exception.ToString());
}
public bool AutoRun
@@ -237,7 +229,7 @@ namespace SpineViewer
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to query autorun registry key, {0}", ex.Message);
return false;
}
@@ -265,7 +257,7 @@ namespace SpineViewer
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to set autorun registry key, {0}", ex.Message);
}
}
@@ -349,7 +341,7 @@ namespace SpineViewer
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to switch language to {0}, {1}", value, ex.Message);
}
}
@@ -366,13 +358,14 @@ namespace SpineViewer
{
Resources.MergedDictionaries.Add(new() { Source = new(uri, UriKind.Relative) });
Resources.MergedDictionaries.Add(new() { Source = new("Resources/Theme.xaml", UriKind.Relative) });
Current.MainWindow.SetWindowTextColor(AppResource.Color_PrimaryText);
Current.MainWindow.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(Current.MainWindow).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
_skin = value;
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to switch skin to {0}, {1}", value, ex.Message);
}
}

View File

@@ -1,4 +1,6 @@
using SkiaSharp;
using SFML.Graphics;
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
@@ -7,31 +9,29 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Win32Natives;
namespace SpineViewer.Extensions
{
public static class WpfExtension
{
public static SFML.Graphics.FloatRect ToFloatRect(this Rect self)
public static FloatRect ToFloatRect(this Rect self)
{
return new((float)self.X, (float)self.Y, (float)self.Width, (float)self.Height);
}
public static SFML.System.Vector2f ToVector2f(this Size self)
public static Vector2f ToVector2f(this Size self)
{
return new((float)self.Width, (float)self.Height);
}
public static SFML.System.Vector2u ToVector2u(this Size self)
public static Vector2u ToVector2u(this Size self)
{
return new((uint)self.Width, (uint)self.Height);
}
public static SFML.System.Vector2i ToVector2i(this Size self)
public static Vector2i ToVector2i(this Size self)
{
return new((int)self.Width, (int)self.Height);
}
@@ -60,18 +60,6 @@ namespace SpineViewer.Extensions
return wb;
}
public static void SetWindowTextColor(this Window self, Color color)
{
var hwnd = new WindowInteropHelper(self).Handle;
Dwmapi.SetWindowTextColor(hwnd, color.R, color.G, color.B);
}
public static void SetWindowCaptionColor(this Window self, Color color)
{
var hwnd = new WindowInteropHelper(self).Handle;
Dwmapi.SetWindowCaptionColor(hwnd, color.R, color.G, color.B);
}
//public static void SaveToFile(this BitmapSource bitmap, string path)
//{
// var ext = Path.GetExtension(path)?.ToLowerInvariant();

View File

@@ -73,22 +73,6 @@ namespace SpineViewer.Models
#endregion
#region
[ObservableProperty]
private bool _renderSelectedOnly;
[ObservableProperty]
private HitTestLevel _hitTestLevel;
[ObservableProperty]
private bool _logHitSlots;
[ObservableProperty]
private uint _maxFps = 30;
#endregion
#region
public RelayCommand Cmd_SelectAutoRunWorkspaceConfigPath => _cmd_SelectAutoRunWorkspaceConfigPath ??= new(() =>
@@ -106,10 +90,16 @@ namespace SpineViewer.Models
private AppSkin _appSkin;
[ObservableProperty]
private bool _wallpaperView;
private bool _renderSelectedOnly;
[ObservableProperty]
private uint _wallpaperMaxFps = 30;
private HitTestLevel _hitTestLevel;
[ObservableProperty]
private bool _logHitSlots;
[ObservableProperty]
private bool _wallpaperView;
[ObservableProperty]
private bool _closeToTray;

View File

@@ -33,16 +33,11 @@ namespace SpineViewer.Models
#endregion
#region
public string? ExploringDirectory { get; set; }
#endregion
#region
public uint ResolutionX { get; set; } = 1500;
public uint ResolutionY { get; set; } = 1000;
public uint MaxFps { get; set; } = 30;
public float Speed { get; set; } = 1f;
public bool ShowAxis { get; set; } = true;
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);

View File

@@ -12,6 +12,7 @@ namespace SpineViewer.Models
{
public class WorkspaceModel
{
public string? ExploringDirectory { get; set; }
public RendererWorkspaceConfigModel RendererConfig { get; set; } = new();
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects { get; set; } = [];
}
@@ -34,6 +35,8 @@ namespace SpineViewer.Models
public bool FlipY { get; set; } = true;
public uint MaxFps { get; set; } = 30;
public float Speed { get; set; } = 1f;
public bool ShowAxis { get; set; } = true;

View File

@@ -4,8 +4,9 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace Win32Natives
namespace SpineViewer.Natives
{
/// <summary>
/// dwmapi.dll 包装类
@@ -23,15 +24,15 @@ namespace Win32Natives
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, uint dwAttribute, ref uint pvAttribute, int cbAttribute);
public static bool SetWindowCaptionColor(IntPtr hwnd, byte r, byte g, byte b)
public static bool SetWindowCaptionColor(IntPtr hwnd, Color color)
{
int c = r | (g << 8) | (b << 16);
int c = color.R | (color.G << 8) | (color.B << 16);
return 0 == DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref c, sizeof(uint));
}
public static bool SetWindowTextColor(IntPtr hwnd, byte r, byte g, byte b)
public static bool SetWindowTextColor(IntPtr hwnd, Color color)
{
int c = r | (g << 8) | (b << 16);
int c = color.R | (color.G << 8) | (color.B << 16);
return 0 == DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, ref c, sizeof(uint));
}

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Win32Natives
namespace SpineViewer.Natives
{
/// <summary>
/// gdi32.dll 包装类

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Win32Natives
namespace SpineViewer.Natives
{
/// <summary>
/// shell32.dll 包装类

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Win32Natives
namespace SpineViewer.Natives
{
/// <summary>
/// user32.dll 包装类
@@ -316,7 +316,7 @@ namespace Win32Natives
workerw = FindWindowEx(progman, IntPtr.Zero, "WorkerW", null);
}
Debug.WriteLine($"HWND(WorkerW): 0x{workerw:x8}");
Debug.WriteLine($"HWND(WorkerW): {workerw:x8}");
return workerw;
}

View File

@@ -6,7 +6,6 @@
<s:String x:Key="Str_Tool">Tools</s:String>
<s:String x:Key="Str_Download">Download</s:String>
<s:String x:Key="Str_Help">Help</s:String>
<s:String x:Key="Str_DownloadFFmpeg">Go to download FFmpeg</s:String>
<s:String x:Key="Str_Diagnostics">Diagnostics Info</s:String>
<s:String x:Key="Str_Abount">About</s:String>
<s:String x:Key="Str_Experiment">Experimental Features</s:String>
@@ -124,8 +123,6 @@
<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_WallpaperView">Wallpaper View</s:String>
<s:String x:Key="Str_WallpaperMaxFps">Max FPS of Wallpaper View</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">Maximum frame rate of the wallpaper view. Set to 0 for no limit.</s:String>
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
<s:String x:Key="Str_HitTestLevel">Hit Test Accuracy Level</s:String>
<s:String x:Key="Str_LogHitSlots">Output Hit Test Slot Names</s:String>
@@ -143,9 +140,6 @@
<s:String x:Key="Str_ForwardFastTooltip">Forward 10 Frames</s:String>
<s:String x:Key="Str_FullScreenTooltip">Window/Fullscreen; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">Real-time FPS: {0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 -->
<s:String x:Key="Str_OK">OK</s:String>
<s:String x:Key="Str_Cancel">Cancel</s:String>

View File

@@ -6,7 +6,6 @@
<s:String x:Key="Str_Tool">ツール</s:String>
<s:String x:Key="Str_Download">ダウンロード</s:String>
<s:String x:Key="Str_Help">ヘルプ</s:String>
<s:String x:Key="Str_DownloadFFmpeg">FFmpeg をダウンロードしに行く</s:String>
<s:String x:Key="Str_Diagnostics">診断情報</s:String>
<s:String x:Key="Str_Abount">バージョン情報</s:String>
<s:String x:Key="Str_Experiment">実験機能</s:String>
@@ -124,8 +123,6 @@
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
<s:String x:Key="Str_PlaySpeed">再生速度</s:String>
<s:String x:Key="Str_WallpaperView">壁紙表示</s:String>
<s:String x:Key="Str_WallpaperMaxFps">壁紙ビューの最大FPS</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">壁紙ビューの最大フレームレート。0に設定すると制限がなし。</s:String>
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
<s:String x:Key="Str_HitTestLevel">ヒットテスト精度レベル</s:String>
<s:String x:Key="Str_LogHitSlots">ヒットテスト結果のスロット名を出力</s:String>
@@ -143,9 +140,6 @@
<s:String x:Key="Str_ForwardFastTooltip">10フレーム進める</s:String>
<s:String x:Key="Str_FullScreenTooltip">ウィンドウ/フルスクリーン; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">リアルタイムFPS{0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 -->
<s:String x:Key="Str_OK">OK</s:String>
<s:String x:Key="Str_Cancel">キャンセル</s:String>

View File

@@ -6,7 +6,6 @@
<s:String x:Key="Str_Tool">工具</s:String>
<s:String x:Key="Str_Download">下载</s:String>
<s:String x:Key="Str_Help">帮助</s:String>
<s:String x:Key="Str_DownloadFFmpeg">前往下载 FFmpeg</s:String>
<s:String x:Key="Str_Diagnostics">诊断信息</s:String>
<s:String x:Key="Str_Abount">关于</s:String>
<s:String x:Key="Str_Experiment">实验性功能</s:String>
@@ -124,8 +123,6 @@
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
<s:String x:Key="Str_PlaySpeed">播放速度</s:String>
<s:String x:Key="Str_WallpaperView">桌面投影</s:String>
<s:String x:Key="Str_WallpaperMaxFps">桌面投影最大帧率</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">桌面投影的最大帧率,设置为 0 时则无帧率限制</s:String>
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
<s:String x:Key="Str_HitTestLevel">命中检测准确度等级</s:String>
<s:String x:Key="Str_LogHitSlots">输出命中检测结果的插槽名称</s:String>
@@ -143,9 +140,6 @@
<s:String x:Key="Str_ForwardFastTooltip">快进 10 帧</s:String>
<s:String x:Key="Str_FullScreenTooltip">窗口/全屏; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">实时帧率:{0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 -->
<s:String x:Key="Str_OK">确认</s:String>
<s:String x:Key="Str_Cancel">取消</s:String>

View File

@@ -11,7 +11,7 @@
<utils:StringFormatMultiValueConverter x:Key="StrFmtCvter"/>
<utils:BackgroundToForegroundConverter x:Key="Bg2FgCvter"/>
<Style x:Key="MyGridSplitterBaseStyle" TargetType="GridSplitter">
<Style x:Key="MyGridSplitterBaseStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="Background" Value="{DynamicResource SecondaryBorderBrush}"/>
<Setter Property="ShowsPreview" Value="False"/>
<Style.Triggers>
@@ -28,17 +28,17 @@
</Style.Triggers>
</Style>
<Style x:Key="MyToggleButtonBaseStyle" TargetType="ToggleButton" BasedOn="{StaticResource ToggleButtonSwitch}">
<Style x:Key="MyToggleButtonBaseStyle" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource ToggleButtonSwitch}">
<Setter Property="hc:VisualElement.HighlightBrush" Value="{DynamicResource DarkSuccessBrush}"/>
</Style>
<Style x:Key="MyListBoxBaseStyle" TargetType="ListBox" BasedOn="{StaticResource ListBoxBaseStyle}">
<Style x:Key="MyListBoxBaseStyle" TargetType="{x:Type ListBox}" BasedOn="{StaticResource ListBoxBaseStyle}">
<Setter Property="SelectionMode" Value="Extended"/>
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Visible"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource ListBoxItemBaseStyle}">
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource ListBoxItemBaseStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
@@ -47,26 +47,26 @@
</Setter>
</Style>
<Style x:Key="MyListViewBaseStyle" TargetType="ListView" BasedOn="{StaticResource ListViewBaseStyle}">
<Style x:Key="MyListViewBaseStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource ListViewBaseStyle}">
<Setter Property="SelectionMode" Value="Extended"/>
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ListViewItemBaseStyle.Small}"/>
</Style>
<Style x:Key="MyGroupBoxBaseStyle" TargetType="GroupBox" BasedOn="{StaticResource GroupBoxTab}">
<Style x:Key="MyGroupBoxBaseStyle" TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="hc:TitleElement.Background" Value="Transparent"/>
</Style>
<Style x:Key="MyLogRichTextBoxStyle" TargetType="RichTextBox" BasedOn="{StaticResource RichTextBoxBaseStyle}">
<Style x:Key="MyLogRichTextBoxStyle" TargetType="{x:Type RichTextBox}" BasedOn="{StaticResource RichTextBoxBaseStyle}">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Block.LineHeight" Value="3"/>
<Setter Property="VerticalScrollBarVisibility" Value="Visible"/>
</Style>
<Style x:Key="MyVerticalScrollViewerBaseStyle" TargetType="ScrollViewer" BasedOn="{StaticResource ScrollViewerNativeBaseStyle}">
<Style x:Key="MyVerticalScrollViewerBaseStyle" TargetType="{x:Type ScrollViewer}" BasedOn="{StaticResource ScrollViewerNativeBaseStyle}">
<Setter Property="Padding" Value="0"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Style.Triggers>
@@ -99,24 +99,10 @@
</Style.Triggers>
</Style>
<Style x:Key="MyLabelStyle" TargetType="Label" BasedOn="{StaticResource LabelDefault}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<!-- 直接复制的原本 LabelDefault 的样式, 但是去除了 RecognizesAccessKey 防止不显示第一个下划线 -->
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="False" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="GridSplitter" BasedOn="{StaticResource MyGridSplitterBaseStyle}"/>
<Style TargetType="ToggleButton" BasedOn="{StaticResource MyToggleButtonBaseStyle}"/>
<Style TargetType="ListBox" BasedOn="{StaticResource MyListBoxBaseStyle}"/>
<Style TargetType="ListView" BasedOn="{StaticResource MyListViewBaseStyle}"/>
<Style TargetType="GroupBox" BasedOn="{StaticResource MyGroupBoxBaseStyle}"/>
<Style TargetType="Label" BasedOn="{StaticResource MyLabelStyle}"/>
<Style TargetType="{x:Type GridSplitter}" BasedOn="{StaticResource MyGridSplitterBaseStyle}"/>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButtonBaseStyle}"/>
<Style TargetType="{x:Type ListBox}" BasedOn="{StaticResource MyListBoxBaseStyle}"/>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MyListViewBaseStyle}"/>
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource MyGroupBoxBaseStyle}"/>
</ResourceDictionary>

View File

@@ -6,12 +6,9 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.12</Version>
<Version>0.16.10</Version>
<OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF>
</PropertyGroup>
@@ -44,6 +41,5 @@
<ProjectReference Include="..\NLog.Windows.Wpf\NLog.Windows.Wpf.csproj" />
<ProjectReference Include="..\SFMLRenderer\SFMLRenderer.csproj" />
<ProjectReference Include="..\Spine\Spine.csproj" />
<ProjectReference Include="..\Win32Natives\Win32Natives.csproj" />
</ItemGroup>
</Project>

View File

@@ -65,7 +65,7 @@ namespace SpineViewer.Utils
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to read json file {0}, {1}", path, ex.Message);
}
}
@@ -86,7 +86,7 @@ namespace SpineViewer.Utils
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to save json file {0}, {1}", path, ex.Message);
return false;
}
@@ -101,7 +101,7 @@ namespace SpineViewer.Utils
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to serialize json object {0}", ex.Message);
return string.Empty;
}

View File

@@ -112,7 +112,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -169,7 +169,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
}

View File

@@ -149,7 +149,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -206,7 +206,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
}

View File

@@ -85,7 +85,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
}
@@ -121,7 +121,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
done++;

View File

@@ -76,7 +76,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -133,7 +133,7 @@ namespace SpineViewer.ViewModels.Exporters
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message);
}
}

View File

@@ -169,7 +169,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message);
}
_logger.LogCurrentProcessMemoryUsage();
@@ -221,7 +221,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message);
error++;
}
@@ -261,7 +261,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message);
}
}
@@ -302,7 +302,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message);
error++;
}
@@ -340,7 +340,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to enumerate files in dir: {0}, {1}", _currentDirectory, ex.Message);
}
}
@@ -408,7 +408,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Warn("Failed to load preview image for {0}, {1}", FullPath, ex.Message);
return null;
}

View File

@@ -5,7 +5,6 @@ using SFMLRenderer;
using SpineViewer.Models;
using SpineViewer.Services;
using SpineViewer.Utils;
using System.Diagnostics;
using System.Windows;
using System.Windows.Shell;
@@ -28,24 +27,7 @@ namespace SpineViewer.ViewModels.MainWindow
_preferenceViewModel = new(this);
}
public bool IsDebug => App.IsDebug;
public string Title => $"{App.AppName} - v{App.Version}";
public Visibility Visibility
{
get => _visibility;
set
{
if (SetProperty(ref _visibility, value))
{
OnPropertyChanged(nameof(IsVisible));
}
}
}
private Visibility _visibility = Visibility.Visible;
public bool IsVisible => _visibility == Visibility.Visible;
public string Title => $"SpineViewer - v{App.Version}";
/// <summary>
/// 指示是否通过托盘图标进行退出
@@ -164,12 +146,6 @@ namespace SpineViewer.ViewModels.MainWindow
JsonHelper.Serialize(Workspace, fileName);
}
/// <summary>
/// 打开 FFmpeg 下载页面
/// </summary>
public RelayCommand Cmd_DownloadFFmpeg => _cmd_DownloadFFmpeg ??= new(() => Process.Start(new ProcessStartInfo("https://ffmpeg.org/download.html") { UseShellExecute = true }));
private RelayCommand? _cmd_DownloadFFmpeg;
/// <summary>
/// 显示诊断信息对话框
/// </summary>
@@ -188,12 +164,14 @@ namespace SpineViewer.ViewModels.MainWindow
{
return new()
{
ExploringDirectory = _explorerListViewModel.CurrentDirectory,
RendererConfig = _sfmlRendererViewModel.WorkspaceConfig,
LoadedSpineObjects = _spineObjectListViewModel.LoadedSpineObjects
};
}
set
{
_explorerListViewModel.CurrentDirectory = value.ExploringDirectory;
_sfmlRendererViewModel.WorkspaceConfig = value.RendererConfig;
_spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects;
}

View File

@@ -5,6 +5,7 @@ using NLog;
using Spine;
using Spine.Implementations;
using SpineViewer.Models;
using SpineViewer.Natives;
using SpineViewer.Services;
using SpineViewer.Utils;
using System;
@@ -75,7 +76,7 @@ namespace SpineViewer.ViewModels.MainWindow
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to load some prefereneces, {0}", ex.Message);
}
}
@@ -107,15 +108,12 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = DebugPoints,
DebugClippings = DebugClippings,
AppLanguage = AppLanguage,
AppSkin = AppSkin,
RenderSelectedOnly = RenderSelectedOnly,
HitTestLevel = HitTestLevel,
LogHitSlots = LogHitSlots,
MaxFps = MaxFps,
AppLanguage = AppLanguage,
AppSkin = AppSkin,
WallpaperView = WallpaperView,
WallpaperMaxFps = WallpaperMaxFps,
CloseToTray = CloseToTray,
AutoRun = AutoRun,
AutoRunWorkspaceConfigPath = AutoRunWorkspaceConfigPath,
@@ -142,15 +140,12 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings;
AppLanguage = value.AppLanguage;
AppSkin = value.AppSkin;
RenderSelectedOnly = value.RenderSelectedOnly;
HitTestLevel = value.HitTestLevel;
LogHitSlots = value.LogHitSlots;
MaxFps = value.MaxFps;
AppLanguage = value.AppLanguage;
AppSkin = value.AppSkin;
WallpaperView = value.WallpaperView;
WallpaperMaxFps = value.WallpaperMaxFps;
CloseToTray = value.CloseToTray;
AutoRun = value.AutoRun;
AutoRunWorkspaceConfigPath = value.AutoRunWorkspaceConfigPath;
@@ -256,10 +251,26 @@ namespace SpineViewer.ViewModels.MainWindow
#endregion
#region
#region
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public static ImmutableArray<AppSkin> AppSkinOptions { get; } = Enum.GetValues<AppSkin>().ToImmutableArray();
public static ImmutableArray<HitTestLevel> HitTestLevelOptions { get; } = Enum.GetValues<HitTestLevel>().ToImmutableArray();
public AppLanguage AppLanguage
{
get => ((App)App.Current).Language;
set => SetProperty(((App)App.Current).Language, value, v => ((App)App.Current).Language = v);
}
public AppSkin AppSkin
{
get => ((App)App.Current).Skin;
set => SetProperty(((App)App.Current).Skin, value, v => ((App)App.Current).Skin = v);
}
public bool RenderSelectedOnly
{
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;
@@ -278,44 +289,12 @@ namespace SpineViewer.ViewModels.MainWindow
set => SetProperty(SpineExtension.LogHitSlots, value, v => SpineExtension.LogHitSlots = v);
}
public uint MaxFps
{
get => _vmMain.SFMLRendererViewModel.MaxFps;
set => SetProperty(_vmMain.SFMLRendererViewModel.MaxFps, value, v => _vmMain.SFMLRendererViewModel.MaxFps = v);
}
#endregion
#region
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public static ImmutableArray<AppSkin> AppSkinOptions { get; } = Enum.GetValues<AppSkin>().ToImmutableArray();
public AppLanguage AppLanguage
{
get => ((App)App.Current).Language;
set => SetProperty(((App)App.Current).Language, value, v => ((App)App.Current).Language = v);
}
public AppSkin AppSkin
{
get => ((App)App.Current).Skin;
set => SetProperty(((App)App.Current).Skin, value, v => ((App)App.Current).Skin = v);
}
public bool WallpaperView
{
get => _vmMain.SFMLRendererViewModel.WallpaperView;
set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperView, value, v => _vmMain.SFMLRendererViewModel.WallpaperView = v);
}
public uint WallpaperMaxFps
{
get => _vmMain.SFMLRendererViewModel.WallpaperMaxFps;
set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperMaxFps, value, v => _vmMain.SFMLRendererViewModel.WallpaperMaxFps = v);
}
public bool CloseToTray
{
get => _vmMain.CloseToTray;

View File

@@ -50,7 +50,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// <summary>
/// 坐标轴顶点缓冲区
/// </summary>
private readonly SFML.Graphics.VertexArray _axisVertices = new(SFML.Graphics.PrimitiveType.Lines, 4); // XXX: 暂时未使用 Dispose 释放
private readonly SFML.Graphics.VertexArray _axisVertices = new(SFML.Graphics.PrimitiveType.Lines, 2); // XXX: 暂时未使用 Dispose 释放
/// <summary>
/// 帧间隔计时器
@@ -61,7 +61,6 @@ namespace SpineViewer.ViewModels.MainWindow
/// 渲染任务
/// </summary>
private Task? _renderTask = null;
private Task? _wallpaperRenderTask = null;
private CancellationTokenSource? _cancelToken = null;
/// <summary>
@@ -88,12 +87,6 @@ namespace SpineViewer.ViewModels.MainWindow
_models = _vmMain.SpineObjects;
_renderer = _vmMain.SFMLRenderer;
_wallpaperRenderer = _vmMain.WallpaperRenderer;
// 画一个很长的坐标轴, 用 1e9 比较合适
_axisVertices[0] = new(new(-1e9f, 0), _axisColor);
_axisVertices[1] = new(new(1e9f, 0), _axisColor);
_axisVertices[2] = new(new(0, -1e9f), _axisColor);
_axisVertices[3] = new(new(0, 1e9f), _axisColor);
}
/// <summary>
@@ -163,24 +156,6 @@ namespace SpineViewer.ViewModels.MainWindow
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = value);
}
public uint WallpaperMaxFps
{
get => _wallpaperRenderer.MaxFps;
set => SetProperty(_wallpaperRenderer.MaxFps, value, v => _wallpaperRenderer.MaxFps = value);
}
public float RealTimeFps => _realTimeFps;
private float _realTimeFps;
private float _accumFpsTime;
private int _accumFpsCount;
public float WallpaperRealTimeFps => _wallpaperRealTimeFps;
private float _wallpaperRealTimeFps;
private int _accumWallpaperFpsCount;
private readonly object _accumWallpaperFpsCountLock = new();
public float Speed
{
get => _speed;
@@ -213,7 +188,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// </summary>
private SFML.Graphics.Color _axisColor = SFML.Graphics.Color.White;
public string? BackgroundImagePath
public string BackgroundImagePath
{
get => _backgroundImagePath;
set => SetProperty(_backgroundImagePath, value, v =>
@@ -262,7 +237,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
});
}
private string? _backgroundImagePath;
private string _backgroundImagePath;
public Stretch BackgroundImageMode
{
@@ -345,8 +320,7 @@ namespace SpineViewer.ViewModels.MainWindow
public void CanvasMouseWheelScrolled(object? s, SFML.Window.MouseWheelScrollEventArgs e)
{
float delta = ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) ? 0.1f : 0.01f;
var factor = e.Delta > 0 ? (1f + delta) : (1f - delta);
var factor = e.Delta > 0 ? 1.1f : 0.9f;
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
{
Zoom = Math.Clamp(Zoom * factor, 0.001f, 1000f); // 滚轮缩放限制一下缩放范围
@@ -472,29 +446,26 @@ namespace SpineViewer.ViewModels.MainWindow
{
if (_renderTask is not null) return;
_cancelToken = new();
_renderTask = new(RenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
_wallpaperRenderTask = new(WallpaperRenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
_renderTask = new Task(RenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
_renderTask.Start();
_wallpaperRenderTask.Start();
IsUpdating = true;
}
public void StopRender()
{
IsUpdating = false;
if (_cancelToken is null || _renderTask is null || _wallpaperRenderTask is null) return;
if (_renderTask is null || _cancelToken is null) return;
_cancelToken.Cancel();
_wallpaperRenderTask.Wait();
_renderTask.Wait();
_wallpaperRenderTask = null;
_renderTask = null;
_cancelToken = null;
_renderTask = null;
}
private void RenderTask()
{
try
{
_wallpaperRenderer.SetActive(true);
_renderer.SetActive(true);
float delta;
@@ -503,211 +474,123 @@ namespace SpineViewer.ViewModels.MainWindow
delta = _clock.ElapsedTime.AsSeconds();
_clock.Restart();
UpdateLogicFrame(delta);
UpdateRenderFrame();
}
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Fatal("Render task stopped, {0}", ex.Message);
MessagePopupService.Error(ex.ToString());
}
finally
{
_renderer.SetActive(false);
}
}
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
if (!_isUpdating) delta = 0;
private void UpdateLogicFrame(float delta)
{
// 计算实时帧率, 1 秒刷新一次
_accumFpsTime += delta;
if (_accumFpsTime > 1f)
{
_realTimeFps = _accumFpsCount / _accumFpsTime;
_accumFpsCount = 0;
lock (_accumWallpaperFpsCountLock)
{
_wallpaperRealTimeFps = _accumWallpaperFpsCount / _accumFpsTime;
_accumWallpaperFpsCount = 0;
}
_accumFpsTime = 0f;
OnPropertyChanged(nameof(RealTimeFps));
OnPropertyChanged(nameof(WallpaperRealTimeFps));
}
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
if (!_isUpdating) delta = 0;
// 加上要快进的量
lock (_forwardDeltaLock)
{
delta += _forwardDelta;
_forwardDelta = 0;
}
// 更新模型对象时间
lock (_models.Lock)
{
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
sp.Update(0); // 避免物理效果出现问题
sp.Update(delta * _speed);
}
}
// 更新背景图位置和缩放
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
using var view = _renderer.GetView();
var bg = _backgroundImageSprite;
var viewSize = view.Size;
var bgSize = bg.Texture.Size;
var scaleX = Math.Abs(viewSize.X / bgSize.X);
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
var signX = Math.Sign(viewSize.X);
var signY = Math.Sign(viewSize.Y);
if (_backgroundImageMode == Stretch.None)
// 加上要快进的量
lock (_forwardDeltaLock)
{
scaleX = scaleY = 1f / _renderer.Zoom;
}
else if (_backgroundImageMode == Stretch.Uniform)
{
scaleX = scaleY = Math.Min(scaleX, scaleY);
}
else if (_backgroundImageMode == Stretch.UniformToFill)
{
scaleX = scaleY = Math.Max(scaleX, scaleY);
}
bg.Scale = new(signX * scaleX, signY * scaleY);
bg.Position = view.Center;
bg.Rotation = view.Rotation;
}
}
}
private void UpdateRenderFrame()
{
if (!_vmMain.IsVisible)
{
// 必须休眠一会, 否则会空转影响整体渲染循环
Thread.Sleep(1);
return;
}
// 清除背景
_renderer.Clear(_backgroundColor);
// 渲染背景
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
_renderer.Draw(_backgroundImageSprite);
}
}
// 渲染坐标轴
if (_showAxis)
{
_renderer.Draw(_axisVertices);
}
// 渲染 Spine
lock (_models.Lock)
{
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
// 为选中对象绘制一个半透明背景
if (sp.IsSelected)
{
var rc = sp.GetCurrentBounds().ToFloatRect();
_selectedBackgroundVertices[0] = new(new(rc.Left, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[1] = new(new(rc.Left + rc.Width, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[2] = new(new(rc.Left + rc.Width, rc.Top + rc.Height), _selectedBackgroundColor);
_selectedBackgroundVertices[3] = new(new(rc.Left, rc.Top + rc.Height), _selectedBackgroundColor);
_renderer.Draw(_selectedBackgroundVertices);
delta += _forwardDelta;
_forwardDelta = 0;
}
// 仅在预览画面临时启用调试模式
sp.EnableDebug = true;
_renderer.Draw(sp);
sp.EnableDebug = false;
}
}
using var v = _renderer.GetView();
_renderer.Clear(_backgroundColor);
// 显示内容
_renderer.Display();
// 帧数加一
_accumFpsCount++;
}
private void WallpaperRenderTask()
{
try
{
_wallpaperRenderer.SetActive(true);
while (!_cancelToken?.IsCancellationRequested ?? false)
{
if (!_wallpaperView)
if (_wallpaperView)
{
Thread.Sleep(10);
continue;
_wallpaperRenderer.SetView(v);
_wallpaperRenderer.Clear(_backgroundColor);
}
// 同步视图
using var view = _renderer.GetView();
_wallpaperRenderer.SetView(view);
// 清除背景
_wallpaperRenderer.Clear(_backgroundColor);
// 渲染背景
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
_wallpaperRenderer.Draw(_backgroundImageSprite);
using var view = _renderer.GetView();
var bg = _backgroundImageSprite;
var viewSize = view.Size;
var bgSize = bg.Texture.Size;
var scaleX = Math.Abs(viewSize.X / bgSize.X);
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
var signX = Math.Sign(viewSize.X);
var signY = Math.Sign(viewSize.Y);
if (_backgroundImageMode == Stretch.None)
{
scaleX = scaleY = 1f / _renderer.Zoom;
}
else if (_backgroundImageMode == Stretch.Uniform)
{
scaleX = scaleY = Math.Min(scaleX, scaleY);
}
else if (_backgroundImageMode == Stretch.UniformToFill)
{
scaleX = scaleY = Math.Max(scaleX, scaleY);
}
bg.Scale = new(signX * scaleX, signY * scaleY);
bg.Position = view.Center;
bg.Rotation = view.Rotation;
_renderer.Draw(bg);
if (_wallpaperView)
{
_wallpaperRenderer.Draw(bg);
}
}
}
if (_showAxis)
{
// 画一个很长的坐标轴, 用 1e9 比较合适
_axisVertices[0] = new(new(-1e9f, 0), _axisColor);
_axisVertices[1] = new(new(1e9f, 0), _axisColor);
_renderer.Draw(_axisVertices);
_axisVertices[0] = new(new(0, -1e9f), _axisColor);
_axisVertices[1] = new(new(0, 1e9f), _axisColor);
_renderer.Draw(_axisVertices);
}
// 渲染 Spine
lock (_models.Lock)
{
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{
if (_cancelToken?.IsCancellationRequested ?? true)
break; // 提前中止
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
_wallpaperRenderer.Draw(sp);
sp.Update(0); // 避免物理效果出现问题
sp.Update(delta * _speed);
// 为选中对象绘制一个半透明背景
if (sp.IsSelected)
{
var rc = sp.GetCurrentBounds().ToFloatRect();
_selectedBackgroundVertices[0] = new(new(rc.Left, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[1] = new(new(rc.Left + rc.Width, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[2] = new(new(rc.Left + rc.Width, rc.Top + rc.Height), _selectedBackgroundColor);
_selectedBackgroundVertices[3] = new(new(rc.Left, rc.Top + rc.Height), _selectedBackgroundColor);
_renderer.Draw(_selectedBackgroundVertices);
}
// 仅在预览画面临时启用调试模式
sp.EnableDebug = true;
_renderer.Draw(sp);
sp.EnableDebug = false;
if (_wallpaperView)
{
_wallpaperRenderer.Draw(sp);
}
}
}
// 显示渲染
_wallpaperRenderer.Display();
_renderer.Display();
// 帧数加一
lock (_accumWallpaperFpsCountLock) _accumWallpaperFpsCount++;
if (_wallpaperView)
{
_wallpaperRenderer.Display();
}
}
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Fatal("Wallpaper render task stopped, {0}", ex.Message);
_logger.Trace(ex.ToString());
_logger.Fatal("Render task stopped, {0}", ex.Message);
MessagePopupService.Error(ex.ToString());
}
finally
{
_renderer.SetActive(false);
_wallpaperRenderer.SetActive(false);
}
}
@@ -726,6 +609,7 @@ namespace SpineViewer.ViewModels.MainWindow
Rotation = Rotation,
FlipX = FlipX,
FlipY = FlipY,
MaxFps = MaxFps,
Speed = Speed,
ShowAxis = ShowAxis,
BackgroundColor = BackgroundColor,
@@ -742,6 +626,7 @@ namespace SpineViewer.ViewModels.MainWindow
Rotation = value.Rotation;
FlipX = value.FlipX;
FlipY = value.FlipY;
MaxFps = value.MaxFps;
Speed = value.Speed;
ShowAxis = value.ShowAxis;
BackgroundColor = value.BackgroundColor;

View File

@@ -213,7 +213,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
}
return false;
@@ -340,7 +340,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
}
}
@@ -401,7 +401,7 @@ namespace SpineViewer.ViewModels.MainWindow
catch (Exception ex)
{
error++;
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
}
}
@@ -718,7 +718,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
}
return false;

View File

@@ -35,7 +35,7 @@ namespace SpineViewer.ViewModels
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Error("Failed to finish work: {0}, {1}", _title, ex.Message);
WorkFinished?.Invoke(this, false);
}

View File

@@ -19,7 +19,7 @@
<Grid Margin="30">
<Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using System;
using System.Collections.Generic;
@@ -14,7 +14,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views
{
@@ -31,8 +30,9 @@ namespace SpineViewer.Views
private void AboutDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
}
}

View File

@@ -24,7 +24,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using System;
using System.Collections.Generic;
@@ -14,7 +14,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views
{
@@ -31,8 +30,9 @@ namespace SpineViewer.Views
private void DiagnosticsDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
}
}

View File

@@ -31,7 +31,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>

View File

@@ -1,4 +1,4 @@
using Win32Natives;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using SpineViewer.Extensions;
namespace SpineViewer.Views.ExporterDialogs
{
@@ -33,8 +32,9 @@ namespace SpineViewer.Views.ExporterDialogs
private void CustomFFmpegExporterDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs
{
@@ -33,8 +32,9 @@ namespace SpineViewer.Views.ExporterDialogs
private void FFmpegVideoExporterDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs
{
@@ -33,8 +32,9 @@ namespace SpineViewer.Views.ExporterDialogs
private void FrameExporterDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs
{
@@ -33,8 +32,9 @@ namespace SpineViewer.Views.ExporterDialogs
private void FrameSequenceExporterDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -15,7 +15,6 @@
Height="720"
Background="{DynamicResource RegionBrush}"
WindowStartupLocation="CenterScreen"
Visibility="{Binding Visibility, Mode=OneWayToSource}"
PreviewKeyDown="MainWindow_PreviewKeyDown"
LocationChanged="MainWindow_LocationChanged"
SizeChanged="MainWindow_SizeChanged">
@@ -32,7 +31,6 @@
MouseDoubleClick="_notifyIcon_MouseDoubleClick">
<hc:NotifyIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click" Visibility="{Binding IsDebug, Mode=OneWay, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<MenuItem Header="{DynamicResource Str_WallpaperView}" Command="{Binding Cmd_SwitchWallpaperView}" IsChecked="{Binding PreferenceViewModel.WallpaperView}"/>
<Separator/>
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_ExitFromTray}"/>
@@ -62,10 +60,10 @@
<!--<MenuItem Header="{DynamicResource Str_Tool}"/>-->
<!--<MenuItem Header="{DynamicResource Str_Download}"/>-->
<MenuItem Header="{DynamicResource Str_Help}">
<MenuItem Header="{DynamicResource Str_DownloadFFmpeg}" Command="{Binding Cmd_DownloadFFmpeg}"/>
<MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/>
<Separator/>
<MenuItem Header="{DynamicResource Str_Abount}" Command="{Binding Cmd_ShowAboutDialog}"/>
<MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click" Visibility="{Binding IsDebug, Mode=OneWay, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click"/>
</MenuItem>
<!--<MenuItem Header="{DynamicResource Str_Experiment}"/>-->
</Menu>
@@ -239,7 +237,7 @@
TabStripPlacement="Bottom"
DataContext="{Binding SpineObjectTabViewModel}">
<TabControl.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
@@ -684,6 +682,125 @@
</Grid>
</TabItem>
<!-- 浏览页 -->
<TabItem DataContext="{Binding ExplorerListViewModel}">
<TabItem.Header>
<Border Style="{StaticResource MyTabItemHeaderContainerStyle}"
MouseLeftButtonDown="MainTabControlHeader_MouseLeftButtonDown">
<Viewbox Width="24" Height="24" ToolTip="{DynamicResource Str_Explorer}">
<Path Data="{StaticResource Geo_Image}" Style="{StaticResource MyTabItemHeaderPathStyle}"/>
</Viewbox>
</Border>
</TabItem.Header>
<Grid x:Name="_explorerGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<hc:TextBox hc:InfoElement.Placeholder="{DynamicResource Str_Filter}"
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1"
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
Command="{Binding Cmd_ChangeCurrentDirectory}"
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
<Button Grid.Column="2"
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
Command="{Binding Cmd_RefreshItems}"
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
</Grid>
<StatusBar DockPanel.Dock="Bottom">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBar>
<ListBox x:Name="_spineFilesListBox"
VirtualizingPanel.IsVirtualizing="True"
ItemsSource="{Binding ShownItems}"
DisplayMemberPath="FileName"
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
Command="{Binding Cmd_AddSelectedItems}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<Separator/>
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
Command="{Binding Cmd_GeneratePreviews}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="{DynamicResource Str_DeletePreviewsForSelected}"
Command="{Binding Cmd_DeletePreviews}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
<Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 文件目录 -->
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding FileDirectory, Mode=OneWay}"
IsReadOnly="True"
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
<!-- 文件名 -->
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding FileName, Mode=OneWay}"
IsReadOnly="True"/>
<!-- 预览图 -->
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="{DynamicResource DarkDefaultBrush}">
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
</Border>
</Grid>
</Grid>
</TabItem>
<!-- 画面参数页 -->
<TabItem DataContext="{Binding SFMLRendererViewModel}">
<TabItem.Header>
@@ -695,7 +812,7 @@
</Border>
</TabItem.Header>
<TabItem.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
@@ -793,6 +910,16 @@
<Separator Margin="0 5"/>
<!-- 最大帧率 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
</Grid>
<!-- 播放速度 -->
<Grid>
<Grid.ColumnDefinitions>
@@ -864,128 +991,6 @@
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- 浏览页 -->
<TabItem DataContext="{Binding ExplorerListViewModel}">
<TabItem.Header>
<Border Style="{StaticResource MyTabItemHeaderContainerStyle}"
MouseLeftButtonDown="MainTabControlHeader_MouseLeftButtonDown">
<Viewbox Width="24" Height="24" ToolTip="{DynamicResource Str_Explorer}">
<Path Data="{StaticResource Geo_Image}" Style="{StaticResource MyTabItemHeaderPathStyle}"/>
</Viewbox>
</Border>
</TabItem.Header>
<Grid x:Name="_explorerGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<hc:TextBox hc:InfoElement.Placeholder="{DynamicResource Str_Filter}"
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1"
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
Command="{Binding Cmd_ChangeCurrentDirectory}"
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
<Button Grid.Column="2"
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
Command="{Binding Cmd_RefreshItems}"
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
</Grid>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Foreground="{DynamicResource PrimaryTextBrush}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBarItem>
</StatusBar>
<ListBox x:Name="_spineFilesListBox"
VirtualizingPanel.IsVirtualizing="True"
ItemsSource="{Binding ShownItems}"
DisplayMemberPath="FileName"
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
Command="{Binding Cmd_AddSelectedItems}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<Separator/>
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
Command="{Binding Cmd_GeneratePreviews}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="{DynamicResource Str_DeletePreviewsForSelected}"
Command="{Binding Cmd_DeletePreviews}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
<Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 文件目录 -->
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding FileDirectory, Mode=OneWay}"
IsReadOnly="True"
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
<!-- 文件名 -->
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding FileName, Mode=OneWay}"
IsReadOnly="True"/>
<!-- 预览图 -->
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="{DynamicResource DarkDefaultBrush}">
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
</Border>
</Grid>
</Grid>
</TabItem>
</TabControl>
</Border>
@@ -1044,23 +1049,7 @@
<!-- 日志框容器 -->
<Border Grid.Row="2" Name="_loggerBoxContainer">
<Border x:Name="_loggerBoxPanel" DataContext="{Binding SFMLRendererViewModel}">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Foreground="{DynamicResource PrimaryTextBrush}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_RealTimeFps">
<Binding Path="RealTimeFps"/>
<Binding Path="WallpaperRealTimeFps"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBarItem>
</StatusBar>
<RichTextBox x:Name="_loggerRichTextBox" Grid.Row="2" Style="{StaticResource MyLogRichTextBoxStyle}"/>
</DockPanel>
</Border>
<RichTextBox x:Name="_loggerRichTextBox" Grid.Row="2" Style="{StaticResource MyLogRichTextBoxStyle}"/>
</Border>
</Grid>
</Border>

View File

@@ -1,8 +1,8 @@
using NLog;
using SFMLRenderer;
using Spine;
using SpineViewer.Extensions;
using SpineViewer.Models;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.Utils;
@@ -24,7 +24,6 @@ using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Win32Natives;
namespace SpineViewer.Views;
@@ -48,7 +47,7 @@ public partial class MainWindow : Window
private readonly List<IDisposable> _userStateWatchers = [];
private DispatcherTimer _saveUserStateTimer;
private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(1);
private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(3);
public bool RootGridCol0Folded
{
@@ -56,19 +55,8 @@ public partial class MainWindow : Window
set
{
var mainTabContentHost = (ContentPresenter)_mainTabControl.Template.FindName("PART_SelectedContentHost", _mainTabControl);
if (mainTabContentHost is null)
{
_mainTabControl.ApplyTemplate();
mainTabContentHost = (ContentPresenter)_mainTabControl.Template.FindName("PART_SelectedContentHost", _mainTabControl);
}
if (mainTabContentHost is null)
{
_logger.Warn("Failed to set property {0}", nameof(RootGridCol0Folded));
return;
}
if ((mainTabContentHost.Visibility != Visibility.Visible) == value)
return;
if (value)
{
// 寄存折叠前的宽度比例
@@ -99,13 +87,13 @@ public partial class MainWindow : Window
// Initialize Wallpaper RenderWindow
_wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None);
_wallpaperRenderWindow.MaxFps = 30;
_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_TOOLWINDOW;
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);
@@ -154,8 +142,9 @@ public partial class MainWindow : Window
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
@@ -178,9 +167,29 @@ public partial class MainWindow : Window
// 加载首选项
_vm.PreferenceViewModel.LoadPreference();
// 还原上一次用户历史状态并开启监听器
// 还原上一次用户历史状态
LoadUserState();
AddUserStateListeners();
// 添加用户状态监听器
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.LeftProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.TopProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WindowStateProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[0], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[2], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_vm.SFMLRendererViewModel.PropertyChanged += SFMLRendererUserStateChanged;
}
private void MainWindow_ContentRendered(object? sender, EventArgs e)
@@ -220,10 +229,14 @@ public partial class MainWindow : Window
}
}
// 移除监听器并保存当前用户状态
RemoveUserStateListensers();
// 保存当前用户状态
SaveUserState();
// 撤除所有状态监听器
_vm.SFMLRendererViewModel.PropertyChanged -= SFMLRendererUserStateChanged;
foreach (var w in _userStateWatchers) w.Dispose();
_userStateWatchers.Clear();
_vm.SFMLRendererViewModel.StopRender();
}
@@ -271,9 +284,8 @@ public partial class MainWindow : Window
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height, GridUnitType.Star);
_rightPanelGrid.RowDefinitions[2].Height = new(m.RightPanelGridRow2Height, GridUnitType.Star);
_vm.ExplorerListViewModel.CurrentDirectory = m.ExploringDirectory;
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
_vm.SFMLRendererViewModel.Speed = m.Speed;
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
@@ -305,10 +317,9 @@ public partial class MainWindow : Window
RightPanelGridRow0Height = _rightPanelGrid.RowDefinitions[0].Height.Value,
RightPanelGridRow2Height = _rightPanelGrid.RowDefinitions[2].Height.Value,
ExploringDirectory = _vm.ExplorerListViewModel.CurrentDirectory,
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
@@ -345,59 +356,13 @@ public partial class MainWindow : Window
_saveUserStateTimer.Start();
}
private void AddUserStateListeners()
{
// 添加用户状态监听器
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.LeftProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.TopProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WindowStateProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[0], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[2], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_vm.ExplorerListViewModel.PropertyChanged += ExplorerListUserStateChanged;
_vm.SFMLRendererViewModel.PropertyChanged += SFMLRendererUserStateChanged;
}
private void RemoveUserStateListensers()
{
// 撤除所有状态监听器
_vm.SFMLRendererViewModel.PropertyChanged -= SFMLRendererUserStateChanged;
_vm.ExplorerListViewModel.PropertyChanged -= ExplorerListUserStateChanged;
foreach (var w in _userStateWatchers) w.Dispose();
_userStateWatchers.Clear();
}
private void ExplorerListUserStateChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(ExplorerListViewModel.CurrentDirectory):
DelayedSaveUserState();
break;
default:
break;
}
}
private void SFMLRendererUserStateChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SFMLRendererViewModel.ResolutionX):
case nameof(SFMLRendererViewModel.ResolutionY):
case nameof(SFMLRendererViewModel.MaxFps):
case nameof(SFMLRendererViewModel.Speed):
case nameof(SFMLRendererViewModel.ShowAxis):
case nameof(SFMLRendererViewModel.BackgroundColor):
@@ -437,7 +402,6 @@ public partial class MainWindow : Window
private void SFMLRendererViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// XXX: 资源管理器重启后窗口会有问题无法重新显示, 需要重启应用, 否则要重新创建窗口
if (e.PropertyName == nameof(SFMLRendererViewModel.WallpaperView))
{
var wnd = _wallpaperRenderWindow;
@@ -449,25 +413,17 @@ public partial class MainWindow : Window
_logger.Error("Failed to enable wallpaper view, WorkerW not found");
return;
}
User32.GetPrimaryScreenResolution(out var sw, out var sh);
_vm.SFMLRendererViewModel.SetResolution(sw, sh);
var handle = wnd.SystemHandle;
// 每次都进行设置, 确保会成为顶层子窗口
var lastParent = User32.SetParent(handle, workerw);
Debug.WriteLine($"0x{lastParent:x8} = SetParent(0x{handle:x8}, 0x{workerw:x8})");
User32.GetPrimaryScreenResolution(out var sw, out var sh);
User32.SetParent(handle, workerw);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
// XXX: 每次新设置成桌面子窗口之后, 要确保窗口 Size 发生一次变化来触发 SFML 内部的渲染视图更新
var ssize = new SFML.System.Vector2u(sw, sh);
if (lastParent != workerw && ssize == wnd.Size)
{
wnd.Size = new(sw + 1, sh);
}
_vm.SFMLRendererViewModel.SetResolution(sw, sh);
wnd.Position = new(0, 0);
wnd.Size = ssize;
wnd.Size = new(sw + 1, sh);
wnd.Size = new(sw, sh);
wnd.SetVisible(true);
}
else
@@ -700,7 +656,7 @@ public partial class MainWindow : Window
_renderPanelButtonsPopupContainer.Child = _renderPanelButtonsPanel;
_loggerBoxContainer.Child = null;
_loggerBoxPopupContainer.Child = _loggerBoxPanel;
_loggerBoxPopupContainer.Child = _loggerRichTextBox;
}
private void SwitchToNormalLayout()
@@ -710,7 +666,7 @@ public partial class MainWindow : Window
HandyControl.Controls.IconElement.SetGeometry(_fullScreenButton, AppResource.Geo_ArrowsMaximize);
_loggerBoxPopupContainer.Child = null;
_loggerBoxContainer.Child = _loggerBoxPanel;
_loggerBoxContainer.Child = _loggerRichTextBox;
_renderPanelButtonsPopupContainer.Child = null;
_renderPanelButtonsContainer.Child = _renderPanelButtonsPanel;

View File

@@ -31,7 +31,7 @@
<Border>
<Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
@@ -195,8 +195,30 @@
</StackPanel>
</GroupBox>
<GroupBox Header="{DynamicResource Str_RendererPreference}">
<GroupBox Header="{DynamicResource Str_AppPreference}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Skin}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppSkin}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppSkinOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
@@ -226,41 +248,6 @@
<ToggleButton Grid.Column="1" IsChecked="{Binding LogHitSlots}" ToolTip="{DynamicResource Str_LogHitSlotsTooltip}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
</Grid>
</StackPanel>
</GroupBox>
<GroupBox Header="{DynamicResource Str_AppPreference}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Skin}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppSkin}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppSkinOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
@@ -270,15 +257,6 @@
<ToggleButton Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_WallpaperMaxFps}" ToolTip="{DynamicResource Str_WallpaperMaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding WallpaperMaxFps}" ToolTip="{DynamicResource Str_WallpaperMaxFpsTooltip}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views
{
@@ -33,8 +32,9 @@ namespace SpineViewer.Views
private void PreferenceDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -1,4 +1,4 @@
using SpineViewer.Extensions;
using SpineViewer.Natives;
using SpineViewer.Resources;
using SpineViewer.ViewModels;
using System;
@@ -16,7 +16,6 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views
{
@@ -34,19 +33,29 @@ namespace SpineViewer.Views
private void ProgressDialog_SourceInitialized(object? sender, EventArgs e)
{
this.SetWindowTextColor(AppResource.Color_PrimaryText);
this.SetWindowCaptionColor(AppResource.Color_Region);
var hwnd = new WindowInteropHelper(this).Handle;
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
}
private void ProgressWindow_Loaded(object sender, RoutedEventArgs e)
{
var hwnd = new WindowInteropHelper(this).Handle;
int currentStyle = User32.GetWindowLong(hwnd, User32.GWL_STYLE);
User32.SetWindowLong(hwnd, User32.GWL_STYLE, currentStyle & ~User32.WS_SYSMENU);
int currentStyle = GetWindowLong(hwnd, GWL_STYLE);
SetWindowLong(hwnd, GWL_STYLE, currentStyle & ~WS_SYSMENU);
var vm = (ProgressDialogViewModel)DataContext;
vm.WorkFinished += (s, e) => Dispatcher.Invoke(() => { DialogResult = e; });
vm.Start();
}
private const int GWL_STYLE = -16;
private const int WS_SYSMENU = 0x80000;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
}
}

View File

@@ -42,7 +42,7 @@ namespace SpineViewerCLI
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Trace(ex.ToString());
_logger.Fatal("Failed to execute, {0}", ex.Message);
return -1;
}

View File

@@ -6,12 +6,9 @@
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.12</Version>
<Version>0.16.10</Version>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\$(Configuration)\$(PlatformTarget)</OutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.0</Version>
</PropertyGroup>
</Project>