678 lines
25 KiB
C#
678 lines
25 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HandyControl.Expression.Shapes;
|
|
using NLog;
|
|
using SFMLRenderer;
|
|
using SpineViewer.Extensions;
|
|
using SpineViewer.Models;
|
|
using SpineViewer.Resources;
|
|
using SpineViewer.Services;
|
|
using SpineViewer.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
namespace SpineViewer.ViewModels.MainWindow
|
|
{
|
|
public class SFMLRendererViewModel : ObservableObject
|
|
{
|
|
public static ImmutableArray<Stretch> StretchOptions { get; } = Enum.GetValues<Stretch>().ToImmutableArray();
|
|
|
|
/// <summary>
|
|
/// 日志器
|
|
/// </summary>
|
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
|
|
|
private readonly MainWindowViewModel _vmMain;
|
|
private readonly ObservableCollectionWithLock<SpineObjectModel> _models;
|
|
private readonly ISFMLRenderer _renderer;
|
|
private readonly ISFMLRenderer _wallpaperRenderer;
|
|
|
|
/// <summary>
|
|
/// 被选中对象的背景颜色
|
|
/// </summary>
|
|
private static readonly SFML.Graphics.Color _selectedBackgroundColor = new(255, 255, 255, 50);
|
|
|
|
/// <summary>
|
|
/// 被选中对象背景顶点缓冲区
|
|
/// </summary>
|
|
private readonly SFML.Graphics.VertexArray _selectedBackgroundVertices = new(SFML.Graphics.PrimitiveType.Quads, 4); // XXX: 暂时未使用 Dispose 释放
|
|
|
|
/// <summary>
|
|
/// 坐标轴顶点缓冲区
|
|
/// </summary>
|
|
private readonly SFML.Graphics.VertexArray _axisVertices = new(SFML.Graphics.PrimitiveType.Lines, 4); // XXX: 暂时未使用 Dispose 释放
|
|
|
|
/// <summary>
|
|
/// 帧间隔计时器
|
|
/// </summary>
|
|
private readonly SFML.System.Clock _clock = new();
|
|
|
|
/// <summary>
|
|
/// 渲染任务
|
|
/// </summary>
|
|
private Task? _renderTask = null;
|
|
private CancellationTokenSource? _cancelToken = null;
|
|
|
|
/// <summary>
|
|
/// 快进时间量
|
|
/// </summary>
|
|
private float _forwardDelta = 0;
|
|
private readonly object _forwardDeltaLock = new();
|
|
|
|
/// <summary>
|
|
/// 背景图片
|
|
/// </summary>
|
|
private SFML.Graphics.Sprite? _backgroundImageSprite; // XXX: 暂时未使用 Dispose 释放
|
|
private SFML.Graphics.Texture? _backgroundImageTexture; // XXX: 暂时未使用 Dispose 释放
|
|
private readonly object _bgLock = new();
|
|
|
|
/// <summary>
|
|
/// 临时变量, 记录拖放世界源点
|
|
/// </summary>
|
|
private SFML.System.Vector2f? _draggingSrc = null;
|
|
|
|
public SFMLRendererViewModel(MainWindowViewModel vmMain)
|
|
{
|
|
_vmMain = vmMain;
|
|
_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>
|
|
/// 请求选中项发生变化
|
|
/// </summary>
|
|
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
|
|
|
public void SetResolution(uint x, uint y)
|
|
{
|
|
var lastRes = _renderer.Resolution;
|
|
_renderer.Resolution = new(x, y);
|
|
if (lastRes.X != x) OnPropertyChanged(nameof(ResolutionX));
|
|
if (lastRes.Y != y) OnPropertyChanged(nameof(ResolutionY));
|
|
}
|
|
|
|
public uint ResolutionX
|
|
{
|
|
get => _renderer.Resolution.X;
|
|
set => SetProperty(_renderer.Resolution.X, value, v => _renderer.Resolution = new(v, _renderer.Resolution.Y));
|
|
}
|
|
|
|
public uint ResolutionY
|
|
{
|
|
get => _renderer.Resolution.Y;
|
|
set => SetProperty(_renderer.Resolution.Y, value, v => _renderer.Resolution = new(_renderer.Resolution.X, v));
|
|
}
|
|
|
|
public float CenterX
|
|
{
|
|
get => _renderer.Center.X;
|
|
set => SetProperty(_renderer.Center.X, value, v => _renderer.Center = new(v, _renderer.Center.Y));
|
|
}
|
|
|
|
public float CenterY
|
|
{
|
|
get => _renderer.Center.Y;
|
|
set => SetProperty(_renderer.Center.Y, value, v => _renderer.Center = new(_renderer.Center.X, v));
|
|
}
|
|
|
|
public float Zoom
|
|
{
|
|
get => _renderer.Zoom;
|
|
set => SetProperty(_renderer.Zoom, value, v => _renderer.Zoom = value);
|
|
}
|
|
|
|
public float Rotation
|
|
{
|
|
get => _renderer.Rotation;
|
|
set => SetProperty(_renderer.Rotation, value, v => _renderer.Rotation = value);
|
|
}
|
|
|
|
public bool FlipX
|
|
{
|
|
get => _renderer.FlipX;
|
|
set => SetProperty(_renderer.FlipX, value, v => _renderer.FlipX = value);
|
|
}
|
|
|
|
public bool FlipY
|
|
{
|
|
get => _renderer.FlipY;
|
|
set => SetProperty(_renderer.FlipY, value, v => _renderer.FlipY = value);
|
|
}
|
|
|
|
public uint MaxFps
|
|
{
|
|
get => _renderer.MaxFps;
|
|
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = _wallpaperRenderer.MaxFps = value);
|
|
}
|
|
|
|
public float RealTimeFps => _realTimeFps;
|
|
private float _realTimeFps;
|
|
|
|
private float _accumFpsTime;
|
|
private int _accumFpsCount;
|
|
|
|
public float Speed
|
|
{
|
|
get => _speed;
|
|
set => SetProperty(ref _speed, Math.Clamp(value, 0.01f, 100f));
|
|
}
|
|
private float _speed = 1f;
|
|
|
|
public bool ShowAxis
|
|
{
|
|
get => _showAxis;
|
|
set => SetProperty(ref _showAxis, value);
|
|
}
|
|
private bool _showAxis = true;
|
|
|
|
public Color BackgroundColor
|
|
{
|
|
get => Color.FromRgb(_backgroundColor.R, _backgroundColor.G, _backgroundColor.B);
|
|
set
|
|
{
|
|
if (!SetProperty(BackgroundColor, value, v => _backgroundColor = new(value.R, value.G, value.B)))
|
|
return;
|
|
var b = (0.299 * value.R + 0.587 * value.G + 0.114 * value.B) / 255.0;
|
|
_axisColor = b < 0.5 ? SFML.Graphics.Color.White : SFML.Graphics.Color.Black;
|
|
}
|
|
}
|
|
private SFML.Graphics.Color _backgroundColor = new(105, 105, 105);
|
|
|
|
/// <summary>
|
|
/// 预览画面坐标轴颜色
|
|
/// </summary>
|
|
private SFML.Graphics.Color _axisColor = SFML.Graphics.Color.White;
|
|
|
|
public string? BackgroundImagePath
|
|
{
|
|
get => _backgroundImagePath;
|
|
set => SetProperty(_backgroundImagePath, value, v =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(v))
|
|
{
|
|
lock (_bgLock)
|
|
{
|
|
_backgroundImageSprite?.Dispose();
|
|
_backgroundImageTexture?.Dispose();
|
|
_backgroundImageTexture = null;
|
|
_backgroundImageSprite = null;
|
|
}
|
|
_backgroundImagePath = v;
|
|
}
|
|
else
|
|
{
|
|
if (!File.Exists(v))
|
|
{
|
|
_logger.Warn("Omit non-existed background image path, {0}", v);
|
|
return;
|
|
}
|
|
SFML.Graphics.Texture tex = null;
|
|
SFML.Graphics.Sprite sprite = null;
|
|
try
|
|
{
|
|
tex = new(v);
|
|
sprite = new(tex) { Origin = new(tex.Size.X / 2f, tex.Size.Y / 2f) };
|
|
lock (_bgLock)
|
|
{
|
|
_backgroundImageSprite?.Dispose();
|
|
_backgroundImageTexture?.Dispose();
|
|
_backgroundImageTexture = tex;
|
|
_backgroundImageSprite = sprite;
|
|
}
|
|
_backgroundImagePath = v;
|
|
_logger.Info("Load background image from {0}", v);
|
|
_logger.LogCurrentProcessMemoryUsage();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sprite?.Dispose();
|
|
tex?.Dispose();
|
|
_logger.Error("Failed to load background image from path: {0}, {1}", v, ex.Message);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
private string? _backgroundImagePath;
|
|
|
|
public Stretch BackgroundImageMode
|
|
{
|
|
get => _backgroundImageMode;
|
|
set => SetProperty(ref _backgroundImageMode, value);
|
|
}
|
|
private Stretch _backgroundImageMode = Stretch.Uniform;
|
|
|
|
/// <summary>
|
|
/// 仅渲染选中对象
|
|
/// </summary>
|
|
public bool RenderSelectedOnly
|
|
{
|
|
get => _renderSelectedOnly;
|
|
set => SetProperty(ref _renderSelectedOnly, value);
|
|
}
|
|
private bool _renderSelectedOnly;
|
|
|
|
/// <summary>
|
|
/// 启用桌面投影
|
|
/// </summary>
|
|
public bool WallpaperView
|
|
{
|
|
get => _wallpaperView;
|
|
set => SetProperty(ref _wallpaperView, value);
|
|
}
|
|
private bool _wallpaperView;
|
|
|
|
public bool IsUpdating
|
|
{
|
|
get => _isUpdating;
|
|
private set
|
|
{
|
|
if (value == _isUpdating) return;
|
|
_isUpdating = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(Geo_PlayPause));
|
|
}
|
|
}
|
|
private bool _isUpdating = true;
|
|
|
|
public RelayCommand Cmd_SelectBackgroundImage => _cmd_SelectBackgroundImage ??= new(() =>
|
|
{
|
|
if (!DialogService.ShowOpenSFMLImageDialog(out var fileName))
|
|
return;
|
|
BackgroundImagePath = fileName;
|
|
});
|
|
private RelayCommand? _cmd_SelectBackgroundImage;
|
|
|
|
public RelayCommand Cmd_Stop => _cmd_Stop ??= new(() =>
|
|
{
|
|
IsUpdating = false;
|
|
lock (_models) foreach (var sp in _models) sp.ResetAnimationsTime();
|
|
});
|
|
private RelayCommand? _cmd_Stop;
|
|
|
|
public RelayCommand Cmd_PlayPause => _cmd_PlayPause ??= new(() => IsUpdating = !IsUpdating);
|
|
private RelayCommand? _cmd_PlayPause;
|
|
|
|
public Geometry Geo_PlayPause => _isUpdating ? AppResource.Geo_Pause : AppResource.Geo_Play;
|
|
|
|
public RelayCommand Cmd_Restart => _cmd_Restart ??= new(() =>
|
|
{
|
|
lock (_models) foreach (var sp in _models) sp.ResetAnimationsTime();
|
|
IsUpdating = true;
|
|
});
|
|
private RelayCommand? _cmd_Restart;
|
|
|
|
public RelayCommand Cmd_ForwardStep => _cmd_ForwardStep ??= new(() =>
|
|
{
|
|
lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 1f / _renderer.MaxFps : 0.001f;
|
|
});
|
|
private RelayCommand? _cmd_ForwardStep;
|
|
|
|
public RelayCommand Cmd_ForwardFast => _cmd_ForwardFast ??= new(() =>
|
|
{
|
|
lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 10f / _renderer.MaxFps : 0.01f;
|
|
});
|
|
private RelayCommand? _cmd_ForwardFast;
|
|
|
|
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);
|
|
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
|
|
{
|
|
Zoom = Math.Clamp(Zoom * factor, 0.001f, 1000f); // 滚轮缩放限制一下缩放范围
|
|
}
|
|
else
|
|
{
|
|
lock (_models.Lock)
|
|
{
|
|
foreach (var sp in _models.Where(it => it.IsShown && it.IsSelected))
|
|
{
|
|
sp.Scale = Math.Clamp(sp.Scale * factor, 0.001f, 1000f); // 滚轮缩放限制一下缩放范围
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CanvasMouseButtonPressed(object? s, SFML.Window.MouseButtonEventArgs e)
|
|
{
|
|
if (e.Button == SFML.Window.Mouse.Button.Right)
|
|
{
|
|
_draggingSrc = _renderer.MapPixelToCoords(new(e.X, e.Y));
|
|
}
|
|
else if (e.Button == SFML.Window.Mouse.Button.Left && !SFML.Window.Mouse.IsButtonPressed(SFML.Window.Mouse.Button.Right))
|
|
{
|
|
var src = _renderer.MapPixelToCoords(new(e.X, e.Y));
|
|
_draggingSrc = src;
|
|
|
|
lock (_models.Lock)
|
|
{
|
|
// 仅渲染选中模式禁止在画面里选择对象
|
|
if (_renderSelectedOnly)
|
|
{
|
|
bool hit = false;
|
|
|
|
// 只在被选中的对象里判断是否有效命中
|
|
hit = _models.Any(m => m.IsSelected && m.HitTest(src.X, src.Y));
|
|
|
|
// 如果没点到被选中的模型, 则不允许拖动
|
|
if (!hit) _draggingSrc = null;
|
|
}
|
|
else
|
|
{
|
|
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
|
|
{
|
|
// 没按 Ctrl 的情况下, 如果命中了已选中对象, 则就算普通命中
|
|
bool hit = false;
|
|
|
|
foreach (var sp in _models.Where(m => m.IsShown))
|
|
{
|
|
if (!sp.HitTest(src.X, src.Y)) continue;
|
|
|
|
hit = true;
|
|
|
|
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
|
if (!sp.IsSelected)
|
|
{
|
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 如果点了空白的地方, 就清空选中列表
|
|
if (!hit) RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
|
}
|
|
else
|
|
{
|
|
// 按下 Ctrl 的情况就执行多选, 并且点空白处也不会清空选中, 如果点击了本来就是选中的则取消选中
|
|
if (_models.FirstOrDefault(m => m.IsShown && m.HitTest(src.X, src.Y), null) is SpineObjectModel sp)
|
|
{
|
|
if (sp.IsSelected)
|
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Remove, sp));
|
|
else
|
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CanvasMouseMove(object? s, SFML.Window.MouseMoveEventArgs e)
|
|
{
|
|
if (_draggingSrc is null) return;
|
|
|
|
var src = (SFML.System.Vector2f)_draggingSrc;
|
|
var dst = _renderer.MapPixelToCoords(new(e.X, e.Y));
|
|
var delta = dst - src;
|
|
|
|
if (SFML.Window.Mouse.IsButtonPressed(SFML.Window.Mouse.Button.Right))
|
|
{
|
|
_renderer.Center -= delta;
|
|
OnPropertyChanged(nameof(CenterX));
|
|
OnPropertyChanged(nameof(CenterY));
|
|
}
|
|
else if (SFML.Window.Mouse.IsButtonPressed(SFML.Window.Mouse.Button.Left))
|
|
{
|
|
lock (_models.Lock)
|
|
{
|
|
foreach (var sp in _models.Where(it => it.IsShown && it.IsSelected))
|
|
{
|
|
sp.X += delta.X;
|
|
sp.Y += delta.Y;
|
|
}
|
|
}
|
|
_draggingSrc = dst;
|
|
}
|
|
}
|
|
|
|
public void CanvasMouseButtonReleased(object? s, SFML.Window.MouseButtonEventArgs e)
|
|
{
|
|
if (e.Button == SFML.Window.Mouse.Button.Right)
|
|
{
|
|
_draggingSrc = null;
|
|
}
|
|
else if (e.Button == SFML.Window.Mouse.Button.Left && !SFML.Window.Mouse.IsButtonPressed(SFML.Window.Mouse.Button.Right))
|
|
{
|
|
_draggingSrc = null;
|
|
}
|
|
}
|
|
|
|
public void StartRender()
|
|
{
|
|
if (_renderTask is not null) return;
|
|
_cancelToken = new();
|
|
_renderTask = new Task(RenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
|
|
_renderTask.Start();
|
|
IsUpdating = true;
|
|
}
|
|
|
|
public void StopRender()
|
|
{
|
|
IsUpdating = false;
|
|
if (_renderTask is null || _cancelToken is null) return;
|
|
_cancelToken.Cancel();
|
|
_renderTask.Wait();
|
|
_cancelToken = null;
|
|
_renderTask = null;
|
|
}
|
|
|
|
private void RenderTask()
|
|
{
|
|
try
|
|
{
|
|
_wallpaperRenderer.SetActive(true);
|
|
_renderer.SetActive(true);
|
|
|
|
float delta;
|
|
while (!_cancelToken?.IsCancellationRequested ?? false)
|
|
{
|
|
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);
|
|
_wallpaperRenderer.SetActive(false);
|
|
}
|
|
}
|
|
|
|
private void UpdateLogicFrame(float delta)
|
|
{
|
|
// 计算实时帧率, 1 秒刷新一次
|
|
_accumFpsCount++;
|
|
_accumFpsTime += delta;
|
|
if (_accumFpsTime > 1f)
|
|
{
|
|
_realTimeFps = _accumFpsCount / _accumFpsTime;
|
|
_accumFpsTime = 0f;
|
|
_accumFpsCount = 0;
|
|
OnPropertyChanged(nameof(RealTimeFps));
|
|
}
|
|
|
|
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateRenderFrame()
|
|
{
|
|
// 同步视图
|
|
if (_wallpaperView)
|
|
{
|
|
using var view = _renderer.GetView();
|
|
_wallpaperRenderer.SetView(view);
|
|
}
|
|
|
|
// 更新背景图位置和缩放
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 清除背景
|
|
if (_vmMain.IsVisible) _renderer.Clear(_backgroundColor);
|
|
if (_wallpaperView) _wallpaperRenderer.Clear(_backgroundColor);
|
|
|
|
// 渲染背景
|
|
lock (_bgLock)
|
|
{
|
|
if (_backgroundImageSprite is not null)
|
|
{
|
|
if (_vmMain.IsVisible) _renderer.Draw(_backgroundImageSprite);
|
|
if (_wallpaperView) _wallpaperRenderer.Draw(_backgroundImageSprite);
|
|
}
|
|
}
|
|
|
|
// 渲染坐标轴
|
|
if (_showAxis && _vmMain.IsVisible)
|
|
{
|
|
_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 (_vmMain.IsVisible)
|
|
{
|
|
// 为选中对象绘制一个半透明背景
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 显示内容
|
|
if (_vmMain.IsVisible) _renderer.Display();
|
|
if (_wallpaperView) _wallpaperRenderer.Display();
|
|
}
|
|
|
|
public RendererWorkspaceConfigModel WorkspaceConfig
|
|
{
|
|
get
|
|
{
|
|
return new()
|
|
{
|
|
ResolutionX = ResolutionX,
|
|
ResolutionY = ResolutionY,
|
|
CenterX = CenterX,
|
|
CenterY = CenterY,
|
|
Zoom = Zoom,
|
|
Rotation = Rotation,
|
|
FlipX = FlipX,
|
|
FlipY = FlipY,
|
|
MaxFps = MaxFps,
|
|
Speed = Speed,
|
|
ShowAxis = ShowAxis,
|
|
BackgroundColor = BackgroundColor,
|
|
BackgroundImagePath = BackgroundImagePath,
|
|
BackgroundImageMode = BackgroundImageMode,
|
|
};
|
|
}
|
|
set
|
|
{
|
|
SetResolution(value.ResolutionX, value.ResolutionY);
|
|
CenterX = value.CenterX;
|
|
CenterY = value.CenterY;
|
|
Zoom = value.Zoom;
|
|
Rotation = value.Rotation;
|
|
FlipX = value.FlipX;
|
|
FlipY = value.FlipY;
|
|
MaxFps = value.MaxFps;
|
|
Speed = value.Speed;
|
|
ShowAxis = value.ShowAxis;
|
|
BackgroundColor = value.BackgroundColor;
|
|
BackgroundImagePath = value.BackgroundImagePath;
|
|
BackgroundImageMode = value.BackgroundImageMode;
|
|
}
|
|
}
|
|
}
|
|
}
|