Brand logo: one locked image layer per scene above the background, image from App Settings
- Source.IsBrandLogo persisted (additive migration); layer healed on load, removal blocked - Image stored as an asset; settings keep the asset id and file name - App Settings: Brand Logo file field with Browse - Default placement top-left; per-scene eye uses the existing element visibility Test: BrandLogoTests; layout/background tests pass (34). Not pushed.
This commit is contained in:
@@ -438,6 +438,7 @@ public partial class MainViewModel
|
||||
StampDefaultBackgroundAsset(background, scene.Name);
|
||||
}
|
||||
SeedOnAirImage();
|
||||
EnsureBrandLogos();
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.IO;
|
||||
using System.Windows.Input;
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
|
||||
namespace ytLive.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// The permanent brand logo: one image layer in every scene, directly above the background.
|
||||
/// Its image comes from App Settings; each scene keeps its own visibility (the eye toggle).
|
||||
/// </summary>
|
||||
public partial class MainViewModel
|
||||
{
|
||||
internal const double BrandLogoX = 32;
|
||||
internal const double BrandLogoY = 32;
|
||||
internal const double BrandLogoWidth = 240;
|
||||
internal const double BrandLogoHeight = 120;
|
||||
|
||||
private ICommand? _browseBrandLogo;
|
||||
|
||||
public ICommand BrowseBrandLogoCommand => _browseBrandLogo ??= new RelayCommand(_ => BrowseBrandLogo());
|
||||
|
||||
public string BrandLogoFileDisplay => _layoutStore.GetBrandLogoFileName() ?? "None";
|
||||
|
||||
private void BrowseBrandLogo()
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "Choose brand logo",
|
||||
Filter = "Images|*.png;*.jpg;*.jpeg;*.bmp;*.gif",
|
||||
};
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
_layoutStore.SetBrandLogo(File.ReadAllBytes(dialog.FileName), Path.GetFileName(dialog.FileName));
|
||||
EnsureBrandLogos();
|
||||
OnPropertyChanged(nameof(BrandLogoFileDisplay));
|
||||
ScheduleSave();
|
||||
}
|
||||
|
||||
internal static Source CreateBrandLogo(string? assetId) => new()
|
||||
{
|
||||
Name = "Brand logo",
|
||||
Type = SourceType.Image,
|
||||
IsBrandLogo = true,
|
||||
IsEnabled = true,
|
||||
AssetId = assetId,
|
||||
X = BrandLogoX,
|
||||
Y = BrandLogoY,
|
||||
Width = BrandLogoWidth,
|
||||
Height = BrandLogoHeight,
|
||||
};
|
||||
|
||||
/// <summary>Exactly one brand logo per scene, placed directly above the background, carrying
|
||||
/// the current App Settings image.</summary>
|
||||
internal void EnsureBrandLogos()
|
||||
{
|
||||
var assetId = _layoutStore.GetBrandLogoAssetId();
|
||||
foreach (var scene in Scenes)
|
||||
{
|
||||
var logos = scene.Elements.OfType<Source>().Where(s => s.IsBrandLogo).ToList();
|
||||
foreach (var extra in logos.Skip(1))
|
||||
scene.Elements.Remove(extra);
|
||||
var logo = logos.FirstOrDefault();
|
||||
if (logo == null)
|
||||
{
|
||||
var background = scene.Elements.OfType<Source>().FirstOrDefault(s => s.IsBackground);
|
||||
var index = background == null ? 0 : scene.Elements.IndexOf(background) + 1;
|
||||
logo = CreateBrandLogo(assetId);
|
||||
scene.Elements.Insert(index, logo);
|
||||
}
|
||||
else
|
||||
{
|
||||
logo.AssetId = assetId;
|
||||
}
|
||||
_sceneGraph.InvalidateBake(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,11 +237,11 @@ public partial class MainViewModel
|
||||
// session when the last using scene lets go). The webcam identity survives —
|
||||
// it's the app-level default, so removing a scene's placement re-enables Add
|
||||
// for the same camera instead of dropping it.
|
||||
private void RemoveElement(SceneElement? element)
|
||||
internal void RemoveElement(SceneElement? element)
|
||||
{
|
||||
var scene = StagedScene;
|
||||
if (scene == null || element == null) return;
|
||||
if (element is Source { IsBackground: true }) return;
|
||||
if (element is Source { IsBackground: true } or Source { IsBrandLogo: true }) return;
|
||||
if (element is WebcamSceneConfig && _webcam != null)
|
||||
{
|
||||
if (SelectedElement == element)
|
||||
|
||||
Reference in New Issue
Block a user