Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc2cb61219 | ||
|
|
decbb10fcb | ||
|
|
c538fd8960 | ||
|
|
cc884f7f5b | ||
|
|
dcfe48912b | ||
|
|
3c77365f60 | ||
|
|
baff9579e5 | ||
|
|
44f4367f3e | ||
|
|
5823d58dca | ||
|
|
7bb76d508e | ||
|
|
b4c7579d24 | ||
|
|
aafe487d96 | ||
|
|
dc2f6a2dad | ||
|
|
10b691d897 | ||
|
|
50219946ec | ||
|
|
659f6fb690 | ||
|
|
af5cb97f1a | ||
|
|
1be9e9e75f | ||
|
|
b0308db977 | ||
|
|
2dbcfe4ea7 | ||
|
|
9040e02025 | ||
|
|
b3ba073368 | ||
|
|
332019a667 | ||
|
|
add9cf157d | ||
|
|
8b0ea750d8 | ||
|
|
733739921d | ||
|
|
e0f46f521a | ||
|
|
aa4245ef2a | ||
|
|
a262538eba | ||
|
|
2e4a5a75c0 | ||
|
|
9331656431 |
@@ -11,6 +11,10 @@ jobs:
|
||||
build-release:
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
version: ${{ steps.extract_version.outputs.version }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
env:
|
||||
PROJECT_NAME: SpineViewer
|
||||
PROJ_CLI_NAME: SpineViewerCLI
|
||||
@@ -27,21 +31,15 @@ jobs:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Extract version from csproj
|
||||
id: extract_version
|
||||
shell: pwsh
|
||||
run: |
|
||||
[xml]$proj = Get-Content "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj"
|
||||
$VERSION_NUM = $proj.Project.PropertyGroup.Version
|
||||
$VERSION_TAG = "v$VERSION_NUM".Trim()
|
||||
"VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Check Version Tag
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not $env:VERSION) {
|
||||
Write-Error "Version tag not found in csproj file."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Version tag found: $env:VERSION"
|
||||
echo "Version tag found: $VERSION_TAG"
|
||||
echo "version=$VERSION_TAG" >> $env:GITHUB_OUTPUT
|
||||
echo "VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Tag merge commit
|
||||
shell: pwsh
|
||||
@@ -63,19 +61,11 @@ jobs:
|
||||
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
|
||||
dotnet publish "$env:PROJ_CLI_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
|
||||
|
||||
- name: Create release directory
|
||||
- name: Compress Windows builds
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path release -Force | Out-Null
|
||||
|
||||
- name: Compress FrameworkDependent version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION.zip" -Force
|
||||
|
||||
- name: Compress SelfContained version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION-SelfContained.zip" -Force
|
||||
|
||||
- name: Create GitHub Release
|
||||
@@ -89,7 +79,7 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload FrameworkDependent zip
|
||||
- name: Upload Windows FrameworkDependent zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -99,7 +89,7 @@ jobs:
|
||||
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload SelfContained zip
|
||||
- name: Upload Windows SelfContained zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -108,3 +98,43 @@ jobs:
|
||||
asset_path: release/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained.zip
|
||||
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
|
||||
build-release-linux:
|
||||
needs: build-release
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PROJ_CLI_NAME: SpineViewerCLI
|
||||
VERSION: ${{ needs.build-release.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Publish Linux SelfContained version
|
||||
run: |
|
||||
dotnet publish "$PROJ_CLI_NAME/$PROJ_CLI_NAME.csproj" -c Release -r linux-x64 --sc true -o "publish/${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained"
|
||||
|
||||
- name: Compress Linux build
|
||||
run: |
|
||||
mkdir -p release
|
||||
cd publish
|
||||
zip -r "../release/${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained.zip" "${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained"
|
||||
|
||||
- name: Upload Linux zip to GitHub Release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.build-release.outputs.upload_url }}
|
||||
asset_path: release/${{ env.PROJ_CLI_NAME }}-${{ env.VERSION }}-Linux-SelfContained.zip
|
||||
asset_name: ${{ env.PROJ_CLI_NAME }}-${{ env.VERSION }}-Linux-SelfContained.zip
|
||||
asset_content_type: application/zip
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.16.11
|
||||
|
||||
- 增加 shift 切换缩放倍数
|
||||
- 改善后台性能
|
||||
- 修复字体显示颜色问题
|
||||
- 调整浏览目录参数保存至用户状态
|
||||
- 调整浏览面板至最后
|
||||
|
||||
## v0.16.10
|
||||
|
||||
- 增加 Linux 平台 CLI 工具构建
|
||||
|
||||
## v0.16.9
|
||||
|
||||
- 重构 CLI 工具
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
|
||||
187
README.en.md
187
README.en.md
@@ -1,139 +1,154 @@
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
|
||||

|
||||
|
||||
[中文](README.md) | [English](README.en.md)
|
||||
|
||||
A simple and user-friendly Spine file viewer and exporter with multi-language support (Chinese/English/Japanese).
|
||||
Spine file viewer & exporter, also a dynamic wallpaper program supporting Spine animations.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
- 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
|
||||
- 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
|
||||
- ......
|
||||
|
||||
### 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 [Release](https://github.com/ww-rm/SpineViewer/releases) page.
|
||||
Download the compressed package from the [Releases](https://github.com/ww-rm/SpineViewer/releases) page.
|
||||
|
||||
The software requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) to run.
|
||||
The program requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) to be installed.
|
||||
|
||||
Alternatively, download the package with the `SelfContained` suffix for standalone execution.
|
||||
You can also download packages with the `SelfContained` suffix, which can run independently without additional installations.
|
||||
|
||||
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).
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### How to Change the Display Language
|
||||
### Overview
|
||||
|
||||
In the menu, go to "File" -> "Preferences..." -> "Language," select your desired language, and confirm the change.
|
||||
The program uses a left-right layout: the left panel contains controls, the right panel displays the preview.
|
||||
|
||||
### Basic Overview
|
||||
The left panel contains three sub-panels:
|
||||
|
||||
The program is organized into a left-right layout:
|
||||
- **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.
|
||||
|
||||
- **Left Panel:** Functionality panel.
|
||||
- **Right Panel:** Preview display.
|
||||
Most buttons, labels, or input fields show help text on hover.
|
||||
|
||||
The left panel includes three sub-panels:
|
||||
---
|
||||
|
||||
- **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.
|
||||
### Importing Skeletons
|
||||
|
||||
Hover your mouse over buttons, labels, or input fields to see help text for most UI elements.
|
||||
Drag-and-drop or paste skeleton files/folders directly into the **Models** panel.
|
||||
|
||||
### Skeleton Import
|
||||
Alternatively, use the right-click menu in the **Browser** panel to import selected items.
|
||||
|
||||
Drag-and-drop or paste skeleton files/directories into the Model panel.
|
||||
---
|
||||
|
||||
Alternatively, use the right-click menu in the Browse panel to import selected items.
|
||||
### Adjusting Content
|
||||
|
||||
### Content Adjustment
|
||||
The **Models** panel supports right-click menus, some hotkeys, and batch editing via multi-selection.
|
||||
|
||||
The Model panel supports right-click menus, some shortcuts, and batch adjustments of model parameters through multi-selection.
|
||||
Mouse interactions in the preview panel:
|
||||
|
||||
For preview display adjustments:
|
||||
- **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.
|
||||
|
||||
- **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.
|
||||
Playback controls below the preview allow time adjustment, acting as a simple player.
|
||||
|
||||
The buttons below the preview display allow time adjustments, serving as a simple playback control.
|
||||
---
|
||||
|
||||
### Content Export
|
||||
### Exporting Content
|
||||
|
||||
Export follows the **WYSIWYG (What You See Is What You Get)** principle, meaning the preview display reflects the exported output.
|
||||
Right-click on models in the list to access export options.
|
||||
|
||||
Use the right-click menu in the Model panel to export selected items.
|
||||
Key export parameters:
|
||||
|
||||
Key export parameters include:
|
||||
- **Output folder**: Optional. If not provided, outputs go to each model’s 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.
|
||||
|
||||
- **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
|
||||
|
||||
Dynamic wallpaper is implemented through desktop projection, allowing the content of the current preview to be projected onto the desktop in real time.
|
||||
The dynamic wallpaper projects the current preview content to the desktop in real time.
|
||||
|
||||
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.
|
||||
Enable or disable via program preferences or the tray icon menu. Save workspace files to preserve model and canvas settings.
|
||||
|
||||
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.
|
||||
Auto-start with Windows can also be enabled, along with loading a specific workspace on startup.
|
||||
|
||||
### More Information
|
||||
---
|
||||
|
||||
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).
|
||||
### 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).
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
@@ -147,6 +162,6 @@ For detailed usage and documentation, see the [Wiki](https://github.com/ww-rm/Sp
|
||||
|
||||
---
|
||||
|
||||
*If you find this project helpful, please give it a \:star: and share it with others! :)*
|
||||
*If you like this project, please give it a :star: and share it with others! :\)*
|
||||
|
||||
[](https://starchart.cc/ww-rm/SpineViewer)
|
||||
|
||||
91
README.md
91
README.md
@@ -1,6 +1,6 @@
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-release.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
|
||||
@@ -16,53 +16,19 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
|
||||
|
||||
## 功能
|
||||
|
||||
- 支持多版本 spine 文件
|
||||
- 支持拖拽/复制粘贴批量打开文件
|
||||
- 支持批量预览
|
||||
- 支持多版本 spine 文件 (`2.1.x; 3.4.x - 4.2.x`)
|
||||
- 支持列表式多骨骼查看和渲染层级管理
|
||||
- 支持列表多选批量设置骨骼参数
|
||||
- 支持多轨道动画设置
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持自定义插槽可见性
|
||||
- 支持多轨道动画
|
||||
- 支持皮肤/插槽/附件设置
|
||||
- 支持调试渲染
|
||||
- 支持画面/模型/轨道时间倍速设置
|
||||
- 支持设置轨道 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) 界面下载压缩包.
|
||||
@@ -73,20 +39,26 @@ 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 格式的预览图, 或者导入选中的模型.
|
||||
- **模型**. 该面板记录导入并进行渲染的模型列表, 可以在这个面板设置与模型渲染相关的参数和渲染顺序, 以及一些与模型有关的功能.
|
||||
- **浏览**. 该面板用于预览指定文件夹的内容, 并没有真正导入文件到程序. 在该面板可以为模型生成 webp 格式的预览图, 或者导入选中的模型.
|
||||
- **画面**. 该面板用于设置右侧预览画面的参数.
|
||||
|
||||
绝大部分按钮或者标签或者输入框都可以通过鼠标指针悬停来获取帮助文本.
|
||||
@@ -105,16 +77,14 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
|
||||
|
||||
- 左键可以选择和拖拽模型, 按下 `Ctrl` 键可以实现多选, 与左侧列表选择是联动的.
|
||||
- 右键对整体画面进行拖动.
|
||||
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放.
|
||||
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放, `Shift` 可以切换缩放倍数.
|
||||
- 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态.
|
||||
|
||||
预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器.
|
||||
|
||||
### 内容导出
|
||||
|
||||
导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面.
|
||||
|
||||
在模型面板里, 右键菜单可以对选中项进行导出操作.
|
||||
在模型列表里, 右键单击选中的模型, 弹出菜单里可以对选中项执行导出操作.
|
||||
|
||||
导出有以下几个关键参数:
|
||||
|
||||
@@ -130,6 +100,29 @@ 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).
|
||||
@@ -146,6 +139,6 @@ https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
|
||||
|
||||
---
|
||||
|
||||
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :)*
|
||||
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :\)*
|
||||
|
||||
[](https://starchart.cc/ww-rm/SpineViewer)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.16.9</Version>
|
||||
<Version>0.16.11</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -21,6 +22,10 @@
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' Or '$(RuntimeIdentifier)' == 'linux-arm64'">
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime21\SpineRuntime21.csproj" />
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj" />
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Spine.Utils
|
||||
/// </summary>
|
||||
private const string FRAGMENT_VertexAlpha =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
|
||||
"p.rgb *= p.a * gl_Color.a;" +
|
||||
"gl_FragColor = gl_Color * p; }"
|
||||
;
|
||||
@@ -24,7 +24,7 @@ namespace Spine.Utils
|
||||
/// </summary>
|
||||
private const string FRAGMENT_VertexAlphaPma =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
|
||||
"p.rgb *= gl_Color.a;" +
|
||||
"gl_FragColor = gl_Color * p; }"
|
||||
;
|
||||
@@ -34,8 +34,8 @@ namespace Spine.Utils
|
||||
/// </summary>
|
||||
private const string FRAGMENT_InvPma =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
||||
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
|
||||
"if (p.a > 0.0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
||||
"gl_FragColor = p; }"
|
||||
;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>2.1.25</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>3.4.2</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>3.5.51</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>3.6.53</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>3.7.94</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>3.8.99</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>4.0.64</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>4.1.54</Version>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>4.2.74</Version>
|
||||
|
||||
@@ -23,9 +23,11 @@ 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
|
||||
@@ -86,6 +88,7 @@ namespace SpineViewer
|
||||
Encoding = System.Text.Encoding.UTF8,
|
||||
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${processid} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}",
|
||||
AutoFlush = true,
|
||||
CreateDirs = true,
|
||||
FileName = "${basedir}/logs/app.log",
|
||||
ArchiveFileName = "${basedir}/logs/app.{#}.log",
|
||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace SpineViewer.Models
|
||||
|
||||
#endregion
|
||||
|
||||
#region 浏览页面状态
|
||||
|
||||
public string? ExploringDirectory { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 预览画面状态
|
||||
|
||||
public uint ResolutionX { get; set; } = 1500;
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace SpineViewer.Models
|
||||
{
|
||||
public class WorkspaceModel
|
||||
{
|
||||
public string? ExploringDirectory { get; set; }
|
||||
public RendererWorkspaceConfigModel RendererConfig { get; set; } = new();
|
||||
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.16.9</Version>
|
||||
<Version>0.16.11</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -27,7 +27,24 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
_preferenceViewModel = new(this);
|
||||
}
|
||||
|
||||
public string Title => $"SpineViewer - v{App.Version}";
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 指示是否通过托盘图标进行退出
|
||||
@@ -164,14 +181,12 @@ 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;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
public uint MaxFps
|
||||
{
|
||||
get => _renderer.MaxFps;
|
||||
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = value);
|
||||
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = _wallpaperRenderer.MaxFps = value);
|
||||
}
|
||||
|
||||
public float Speed
|
||||
@@ -320,7 +320,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
public void CanvasMouseWheelScrolled(object? s, SFML.Window.MouseWheelScrollEventArgs e)
|
||||
{
|
||||
var factor = e.Delta > 0 ? 1.1f : 0.9f;
|
||||
float delta = ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) ? 0.1f : 0.01f;
|
||||
var factor = e.Delta > 0 ? (1f + delta) : (1f - delta);
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
|
||||
{
|
||||
Zoom = Math.Clamp(Zoom * factor, 0.001f, 1000f); // 滚轮缩放限制一下缩放范围
|
||||
@@ -484,21 +485,17 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
_forwardDelta = 0;
|
||||
}
|
||||
|
||||
using var v = _renderer.GetView();
|
||||
_renderer.Clear(_backgroundColor);
|
||||
using var view = _renderer.GetView();
|
||||
_wallpaperRenderer.SetView(view);
|
||||
|
||||
if (_wallpaperView)
|
||||
{
|
||||
_wallpaperRenderer.SetView(v);
|
||||
_wallpaperRenderer.Clear(_backgroundColor);
|
||||
}
|
||||
if (_vmMain.IsVisible) _renderer.Clear(_backgroundColor);
|
||||
if (_wallpaperView) _wallpaperRenderer.Clear(_backgroundColor);
|
||||
|
||||
// 渲染背景
|
||||
lock (_bgLock)
|
||||
{
|
||||
if (_backgroundImageSprite is not null)
|
||||
{
|
||||
using var view = _renderer.GetView();
|
||||
var bg = _backgroundImageSprite;
|
||||
var viewSize = view.Size;
|
||||
var bgSize = bg.Texture.Size;
|
||||
@@ -521,16 +518,13 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
bg.Scale = new(signX * scaleX, signY * scaleY);
|
||||
bg.Position = view.Center;
|
||||
bg.Rotation = view.Rotation;
|
||||
_renderer.Draw(bg);
|
||||
|
||||
if (_wallpaperView)
|
||||
{
|
||||
_wallpaperRenderer.Draw(bg);
|
||||
}
|
||||
if (_vmMain.IsVisible) _renderer.Draw(bg);
|
||||
if (_wallpaperView) _wallpaperRenderer.Draw(bg);
|
||||
}
|
||||
}
|
||||
|
||||
if (_showAxis)
|
||||
if (_showAxis && _vmMain.IsVisible)
|
||||
{
|
||||
// 画一个很长的坐标轴, 用 1e9 比较合适
|
||||
_axisVertices[0] = new(new(-1e9f, 0), _axisColor);
|
||||
@@ -551,35 +545,30 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
sp.Update(0); // 避免物理效果出现问题
|
||||
sp.Update(delta * _speed);
|
||||
|
||||
// 为选中对象绘制一个半透明背景
|
||||
if (sp.IsSelected)
|
||||
if (_vmMain.IsVisible)
|
||||
{
|
||||
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);
|
||||
}
|
||||
// 为选中对象绘制一个半透明背景
|
||||
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);
|
||||
// 仅在预览画面临时启用调试模式
|
||||
sp.EnableDebug = true;
|
||||
_renderer.Draw(sp);
|
||||
sp.EnableDebug = false;
|
||||
}
|
||||
if (_wallpaperView) _wallpaperRenderer.Draw(sp);
|
||||
}
|
||||
}
|
||||
|
||||
_renderer.Display();
|
||||
|
||||
if (_wallpaperView)
|
||||
{
|
||||
_wallpaperRenderer.Display();
|
||||
}
|
||||
if (_vmMain.IsVisible) _renderer.Display();
|
||||
if (_wallpaperView) _wallpaperRenderer.Display();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
Height="720"
|
||||
Background="{DynamicResource RegionBrush}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Visibility="{Binding Visibility, Mode=OneWayToSource}"
|
||||
PreviewKeyDown="MainWindow_PreviewKeyDown"
|
||||
LocationChanged="MainWindow_LocationChanged"
|
||||
SizeChanged="MainWindow_SizeChanged">
|
||||
@@ -31,6 +32,7 @@
|
||||
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}"/>
|
||||
@@ -63,7 +65,7 @@
|
||||
<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"/>
|
||||
<MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click" Visibility="{Binding IsDebug, Mode=OneWay, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</MenuItem>
|
||||
<!--<MenuItem Header="{DynamicResource Str_Experiment}"/>-->
|
||||
</Menu>
|
||||
@@ -682,125 +684,6 @@
|
||||
</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>
|
||||
@@ -991,6 +874,126 @@
|
||||
</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">
|
||||
<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>
|
||||
</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>
|
||||
|
||||
</TabControl>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -55,8 +55,19 @@ 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)
|
||||
{
|
||||
// 寄存折叠前的宽度比例
|
||||
@@ -167,29 +178,9 @@ public partial class MainWindow : Window
|
||||
// 加载首选项
|
||||
_vm.PreferenceViewModel.LoadPreference();
|
||||
|
||||
// 还原上一次用户历史状态
|
||||
// 还原上一次用户历史状态并开启监听器
|
||||
LoadUserState();
|
||||
|
||||
// 添加用户状态监听器
|
||||
_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;
|
||||
AddUserStateListeners();
|
||||
}
|
||||
|
||||
private void MainWindow_ContentRendered(object? sender, EventArgs e)
|
||||
@@ -229,14 +220,10 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前用户状态
|
||||
// 移除监听器并保存当前用户状态
|
||||
RemoveUserStateListensers();
|
||||
SaveUserState();
|
||||
|
||||
// 撤除所有状态监听器
|
||||
_vm.SFMLRendererViewModel.PropertyChanged -= SFMLRendererUserStateChanged;
|
||||
foreach (var w in _userStateWatchers) w.Dispose();
|
||||
_userStateWatchers.Clear();
|
||||
|
||||
_vm.SFMLRendererViewModel.StopRender();
|
||||
}
|
||||
|
||||
@@ -284,6 +271,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;
|
||||
@@ -317,6 +306,8 @@ 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,
|
||||
@@ -356,6 +347,53 @@ 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)
|
||||
|
||||
@@ -5,11 +5,14 @@ using Spine;
|
||||
using Spine.Exporters;
|
||||
using System.CommandLine;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SpineViewerCLI
|
||||
{
|
||||
public static class SpineViewerCLI
|
||||
{
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public static Option<bool> OptQuiet { get; } = new("--quiet", "-q")
|
||||
{
|
||||
Description = "Suppress console logging (quiet mode).",
|
||||
@@ -33,17 +36,28 @@ namespace SpineViewerCLI
|
||||
if (!result.GetValue(OptQuiet))
|
||||
InitializeConsoleLog();
|
||||
|
||||
return result.Invoke();
|
||||
try
|
||||
{
|
||||
return result.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Fatal("Failed to execute, {0}", ex.Message);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeFileLog()
|
||||
{
|
||||
// XXX: δ֪ԭ<D6AA><D4AD> linux ƽ̨<C6BD><CCA8><EFBFBD><EFBFBD><DEB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD>ļ<EFBFBD>
|
||||
var config = new NLog.Config.LoggingConfiguration();
|
||||
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
|
||||
{
|
||||
Encoding = System.Text.Encoding.UTF8,
|
||||
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${processid} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}",
|
||||
AutoFlush = true,
|
||||
CreateDirs = true,
|
||||
FileName = "${basedir}/logs/cli.log",
|
||||
ArchiveFileName = "${basedir}/logs/cli.{#}.log",
|
||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
||||
@@ -71,13 +85,15 @@ namespace SpineViewerCLI
|
||||
DetectOutputRedirected = true,
|
||||
};
|
||||
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Trace", NLog.Targets.ConsoleOutputColor.DarkGray, NLog.Targets.ConsoleOutputColor.NoChange));
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Debug", NLog.Targets.ConsoleOutputColor.DarkGray, NLog.Targets.ConsoleOutputColor.NoChange));
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Info", NLog.Targets.ConsoleOutputColor.DarkGray, NLog.Targets.ConsoleOutputColor.NoChange));
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Warn", NLog.Targets.ConsoleOutputColor.DarkYellow, NLog.Targets.ConsoleOutputColor.NoChange));
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Error", NLog.Targets.ConsoleOutputColor.Red, NLog.Targets.ConsoleOutputColor.NoChange));
|
||||
consoleTarget.RowHighlightingRules.Add(new("level == LogLevel.Fatal", NLog.Targets.ConsoleOutputColor.White, NLog.Targets.ConsoleOutputColor.DarkRed));
|
||||
|
||||
config.AddTarget(consoleTarget);
|
||||
config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget);
|
||||
config.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget);
|
||||
LogManager.Configuration = config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.16.9</Version>
|
||||
<Version>0.16.11</Version>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -22,7 +23,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SFMLRenderer\SFMLRenderer.csproj" />
|
||||
<ProjectReference Include="..\Spine\Spine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user