add:展台

This commit is contained in:
2026-02-19 18:24:55 +08:00
parent 432cc3825e
commit d2abadd69b
3 changed files with 204 additions and 1 deletions
+16
View File
@@ -10558,6 +10558,22 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleButton
x:Name="BtnToggleVideoPresenterLiveOnCanvas"
Width="90"
Height="40"
Margin="3,0"
Background="{DynamicResource FloatBarBackground}"
BorderBrush="{DynamicResource FloatBarBorderBrush}"
BorderThickness="1"
Checked="BtnToggleVideoPresenterLiveOnCanvas_Checked"
Unchecked="BtnToggleVideoPresenterLiveOnCanvas_Unchecked">
<TextBlock
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource FloatBarForeground}"
Text="上屏" />
</ToggleButton>
<ToggleButton
x:Name="CheckBoxEnablePhotoCorrection"
Width="110"
+11 -1
View File
@@ -1,4 +1,4 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -47,6 +47,10 @@ namespace Ink_Canvas
{
if (child is Image || child is MediaElement)
{
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
{
continue;
}
if (!elementsInHistory.Contains(child))
{
timeMachine.CommitElementInsertHistory(child);
@@ -288,12 +292,14 @@ namespace Ink_Canvas
currentSelectedElement = null;
}
VideoPresenter_BeforePageLeave();
SaveStrokes();
ClearStrokes(true);
CurrentWhiteboardIndex--;
RestoreStrokes();
VideoPresenter_OnPageChanged();
UpdateIndexInfoDisplay();
}
@@ -322,12 +328,14 @@ namespace Ink_Canvas
currentSelectedElement = null;
}
VideoPresenter_BeforePageLeave();
SaveStrokes();
ClearStrokes(true);
CurrentWhiteboardIndex++;
RestoreStrokes();
VideoPresenter_OnPageChanged();
UpdateIndexInfoDisplay();
}
@@ -349,6 +357,7 @@ namespace Ink_Canvas
currentSelectedElement = null;
}
VideoPresenter_BeforePageLeave();
SaveStrokes();
ClearStrokes(true);
@@ -364,6 +373,7 @@ namespace Ink_Canvas
// 恢复新页面(这会清空画布,因为历史记录为null)
RestoreStrokes();
VideoPresenter_OnPageChanged();
UpdateIndexInfoDisplay();
@@ -18,12 +18,22 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
// 标记:用于在保存/恢复白板内容时排除“展台实时上屏”画面
private const string VideoPresenterLiveFrameTag = "__VideoPresenterLiveFrame";
private CameraService _cameraService;
private readonly object _videoPresenterFrameLock = new object();
private Bitmap _lastFrame;
private readonly List<CapturedImage> _capturedPhotos = new List<CapturedImage>();
// 按页绑定:每一页对应一个“实时画面”元素与布局/设备信息
private readonly Dictionary<int, System.Windows.Controls.Image> _liveFrameImageByPage = new Dictionary<int, System.Windows.Controls.Image>();
private readonly HashSet<int> _liveEnabledPages = new HashSet<int>();
private readonly Dictionary<int, int> _cameraIndexByPage = new Dictionary<int, int>();
private readonly Dictionary<int, (double left, double top, double width)> _liveFrameLayoutByPage =
new Dictionary<int, (double left, double top, double width)>();
private DateTime _lastCaptureTime = DateTime.MinValue;
private const int VideoPresenterCaptureCooldownMs = 1000;
@@ -53,6 +63,12 @@ namespace Ink_Canvas
{
CheckBoxEnablePhotoCorrection.IsChecked = Settings?.Automation?.IsEnablePhotoCorrection ?? false;
}
// 同步“上屏”按钮状态(按页绑定)
if (BtnToggleVideoPresenterLiveOnCanvas != null)
{
BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(GetCurrentPageIndex());
}
}
private void BtnCloseVideoPresenter_Click(object sender, RoutedEventArgs e)
@@ -119,6 +135,9 @@ namespace Ink_Canvas
{
BtnCapturePhoto.IsEnabled = true;
}
// 实时上屏:刷新当前页的画面元素
TryUpdateLiveFrameOnCanvas(preview);
}));
}
catch
@@ -127,6 +146,68 @@ namespace Ink_Canvas
}
}
private int GetCurrentPageIndex()
{
return Math.Max(1, CurrentWhiteboardIndex);
}
private void TryUpdateLiveFrameOnCanvas(BitmapImage preview)
{
try
{
int page = GetCurrentPageIndex();
if (!_liveEnabledPages.Contains(page)) return;
if (inkCanvas == null) return;
if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return;
if (!inkCanvas.Children.Contains(img))
{
inkCanvas.Children.Add(img);
}
img.Source = preview;
img.Visibility = Visibility.Visible;
}
catch { }
}
private System.Windows.Controls.Image EnsureLiveFrameElementForPage(int page)
{
if (_liveFrameImageByPage.TryGetValue(page, out var existing) && existing != null) return existing;
var img = new System.Windows.Controls.Image
{
Tag = VideoPresenterLiveFrameTag,
Stretch = System.Windows.Media.Stretch.Uniform,
Width = 520,
Visibility = Visibility.Visible,
Opacity = 1.0
};
_liveFrameImageByPage[page] = img;
return img;
}
private void ApplyLiveFrameLayoutForPage(int page, System.Windows.Controls.Image img)
{
if (img == null) return;
if (_liveFrameLayoutByPage.TryGetValue(page, out var layout))
{
if (!double.IsNaN(layout.width) && layout.width > 10) img.Width = layout.width;
InkCanvas.SetLeft(img, Math.Max(0, layout.left));
InkCanvas.SetTop(img, Math.Max(0, layout.top));
return;
}
// 默认位置:居中偏上
double x = (inkCanvas?.ActualWidth ?? 0) / 2 - img.Width / 2;
double y = (inkCanvas?.ActualHeight ?? 0) / 2 - 200;
if (double.IsNaN(x) || double.IsInfinity(x)) x = 100;
if (double.IsNaN(y) || double.IsInfinity(y)) y = 100;
InkCanvas.SetLeft(img, Math.Max(0, x));
InkCanvas.SetTop(img, Math.Max(0, y));
}
private void RefreshVideoPresenterDeviceList()
{
if (_cameraService == null) return;
@@ -184,6 +265,7 @@ namespace Ink_Canvas
try
{
EnsureCameraService();
_cameraIndexByPage[GetCurrentPageIndex()] = cameraIndex;
if (_cameraService.StartPreview(cameraIndex))
{
if (BtnCapturePhoto != null) BtnCapturePhoto.IsEnabled = true;
@@ -195,6 +277,101 @@ namespace Ink_Canvas
}
}
private void BtnToggleVideoPresenterLiveOnCanvas_Checked(object sender, RoutedEventArgs e)
{
int page = GetCurrentPageIndex();
_liveEnabledPages.Add(page);
var img = EnsureLiveFrameElementForPage(page);
ApplyLiveFrameLayoutForPage(page, img);
if (inkCanvas != null && !inkCanvas.Children.Contains(img))
{
inkCanvas.Children.Add(img);
}
// 立即用侧栏预览刷新一次
if (VideoPresenterPreviewImage?.Source is BitmapImage bi)
{
img.Source = bi;
}
}
private void BtnToggleVideoPresenterLiveOnCanvas_Unchecked(object sender, RoutedEventArgs e)
{
int page = GetCurrentPageIndex();
_liveEnabledPages.Remove(page);
if (_liveFrameImageByPage.TryGetValue(page, out var img) && img != null)
{
try
{
if (inkCanvas != null && inkCanvas.Children.Contains(img))
{
inkCanvas.Children.Remove(img);
}
}
catch { }
}
}
// 翻页前调用:保存当前页实时画面的位置/大小
private void VideoPresenter_BeforePageLeave()
{
try
{
int page = GetCurrentPageIndex();
if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return;
double left = InkCanvas.GetLeft(img);
double top = InkCanvas.GetTop(img);
if (double.IsNaN(left)) left = 0;
if (double.IsNaN(top)) top = 0;
_liveFrameLayoutByPage[page] = (left, top, img.Width);
}
catch { }
}
// 翻页后调用:根据该页状态恢复实时画面,并同步设备选择
private void VideoPresenter_OnPageChanged()
{
try
{
int page = GetCurrentPageIndex();
// 同步“上屏”按钮状态
if (BtnToggleVideoPresenterLiveOnCanvas != null)
{
BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(page);
}
// 若该页上屏,恢复画面元素(RestoreStrokes 会清空 inkCanvas.Children
if (_liveEnabledPages.Contains(page))
{
var img = EnsureLiveFrameElementForPage(page);
ApplyLiveFrameLayoutForPage(page, img);
if (inkCanvas != null && !inkCanvas.Children.Contains(img))
{
inkCanvas.Children.Add(img);
}
if (VideoPresenterPreviewImage?.Source is BitmapImage bi)
{
img.Source = bi;
}
}
// 按页摄像头索引:切页后自动切回该页的摄像头
if (_cameraIndexByPage.TryGetValue(page, out int idx))
{
EnsureCameraService();
_cameraService?.StartPreview(idx);
}
}
catch { }
}
private void BtnCapturePhoto_Click(object sender, RoutedEventArgs e)
{
try