diff --git a/src/TeamsISO.App.WinUI/Models/MockParticipant.cs b/src/TeamsISO.App.WinUI/Models/MockParticipant.cs new file mode 100644 index 0000000..88c8e06 --- /dev/null +++ b/src/TeamsISO.App.WinUI/Models/MockParticipant.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace TeamsISO.App.WinUI.Models; + +/// +/// Stand-in for the real ParticipantViewModel until the view-model bindings +/// migrate over from the WPF host. Lets the redesigned MainWindow render with +/// representative data so the visual design can be validated independently +/// of the engine layer. Removed in the view-model wiring commit. +/// +public sealed class MockParticipant +{ + public string DisplayName { get; init; } = ""; + public string Initials { get; init; } = ""; + public string SourceCodec { get; init; } = "MS Teams · 1920x1080 · 30fps"; + public string SignalState { get; init; } = "locked"; // locked | degraded | offline + public string OutputName { get; init; } = ""; + public string IsoState { get; init; } = "OFF"; // LIVE | OFF | ERROR + public double AudioLevel { get; init; } = 0.0; // 0..1 + public bool IsActiveSpeaker { get; init; } = false; + + public static List Sample() + { + return new List + { + new() + { + DisplayName = "Maya Rodriguez", Initials = "MA", + SourceCodec = "MS Teams · 1920x1080 · 30fps", + SignalState = "locked", OutputName = "TEAMSISO_maya", + IsoState = "LIVE", AudioLevel = 0.82, IsActiveSpeaker = true, + }, + new() + { + DisplayName = "Daniel Chen", Initials = "DC", + SourceCodec = "MS Teams · 1280x720 · 30fps", + SignalState = "locked", OutputName = "TEAMSISO_daniel", + IsoState = "LIVE", AudioLevel = 0.35, + }, + new() + { + DisplayName = "Aicha Kone", Initials = "AK", + SourceCodec = "MS Teams · 1920x1080 · 30fps", + SignalState = "degraded", OutputName = "TEAMSISO_aicha", + IsoState = "OFF", AudioLevel = 0.12, + }, + new() + { + DisplayName = "Sam Park", Initials = "SP", + SourceCodec = "MS Teams · 1920x1080 · 30fps", + SignalState = "locked", OutputName = "TEAMSISO_sam", + IsoState = "LIVE", AudioLevel = 0.48, + }, + }; + } +} diff --git a/src/TeamsISO.App.WinUI/Program.cs b/src/TeamsISO.App.WinUI/Program.cs new file mode 100644 index 0000000..03d4e1c --- /dev/null +++ b/src/TeamsISO.App.WinUI/Program.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.Windows.ApplicationModel.DynamicDependency; + +namespace TeamsISO.App.WinUI; + +/// +/// Custom Main for the unpackaged WinUI 3 host. +/// +/// The default XAML-compiler-generated Main (disabled here via the +/// DISABLE_XAML_GENERATED_MAIN compile constant) calls Application.Start +/// directly. That's fine for MSIX-packaged WinUI 3 apps where the OS +/// activates the package and the runtime is found via the package +/// dependency, but for an unpackaged .exe the Windows App Runtime has to +/// be bootstrapped explicitly before any WinUI 3 type is touched. +/// +/// Bootstrap.TryInitialize(0x00010006) targets WindowsAppSDK 1.6 (the LTS +/// branch we ship against). The major nibble 0x0001 is the runtime major; +/// the minor 0x0006 is the runtime minor. If the user's machine has a +/// compatible 1.6.x framework installed (the broadcast-tool installer +/// will eventually ensure that as a prereq), Bootstrap connects and the +/// rest of the WinUI 3 surface comes alive. If not, the call returns a +/// negative HResult that we surface via Environment.Exit so the .exe +/// dies cleanly rather than throwing an opaque "this application could +/// not be started" dialog. +/// +public static class Program +{ + /// WindowsAppSDK 1.6 major/minor packed as 0x00010006. + private const uint WindowsAppSdkMajorMinor = 0x00010006; + + [STAThread] + public static int Main(string[] args) + { + if (!Bootstrap.TryInitialize(WindowsAppSdkMajorMinor, out int hr)) + { + // The runtime isn't installed (or this build's bootstrap can't + // find a compatible package version). Surface a Win32 error code + // so postmortem inspection of the launch failure has more than + // "application could not be started" to go on. + return hr; + } + + try + { + WinRT.ComWrappersSupport.InitializeComWrappers(); + Application.Start(p => + { + var context = new DispatcherQueueSynchronizationContext( + DispatcherQueue.GetForCurrentThread()); + System.Threading.SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); + } + finally + { + Bootstrap.Shutdown(); + } + + return 0; + } +} diff --git a/src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj b/src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj index 03de33e..2aaff00 100644 --- a/src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj +++ b/src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj @@ -25,7 +25,14 @@ 10.0.17763.0 TeamsISO.App.WinUI TeamsISO - app.manifest + x64;ARM64 win-x64;win-arm64 true @@ -38,6 +45,16 @@ having to upgrade the .NET SDK on the build host. --> 10.0.19041.38 + + $(DefineConstants);DISABLE_XAML_GENERATED_MAIN enable enable Assets\teamsiso.ico diff --git a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml index 72289b5..788fc85 100644 --- a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml +++ b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml @@ -2,11 +2,481 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:models="using:TeamsISO.App.WinUI.Models"> + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs index ee66b88..7df0918 100644 --- a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs +++ b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs @@ -1,4 +1,9 @@ +using Microsoft.UI; +using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; +using TeamsISO.App.WinUI.Models; +using Windows.Graphics; +using Windows.UI; namespace TeamsISO.App.WinUI.Views; @@ -7,6 +12,78 @@ public sealed partial class MainWindow : Window public MainWindow() { InitializeComponent(); + Title = "TeamsISO"; + + // ── Custom title bar wiring ─────────────────────────────────────── + // ExtendsContentIntoTitleBar=true tells WindowsAppSDK to draw the + // window chrome over our content instead of reserving a Windows-default + // caption strip. SetTitleBar marks AppTitleBar as the drag region — + // clicks on it route to the system drag handler, everything else stays + // hit-testable as a normal XAML element. The system min/max/close + // buttons render on top of the right edge regardless; we just provide + // their colors so they match our palette. + ExtendsContentIntoTitleBar = true; + SetTitleBar(AppTitleBar); + + AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; + AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + AppWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0x33, 0x34, 0x3A); + AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0x3F, 0x3F, 0x47); + AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF6); + AppWindow.TitleBar.ButtonHoverForegroundColor = Colors.White; + + // ── Initial size & position ─────────────────────────────────────── + // 1280x780 matches the WPF host's default — fits comfortably on a + // 14-inch laptop while giving the participants table 600+ pixels + // of vertical breathing room. + AppWindow.Resize(new SizeInt32(1280, 780)); + + // ── Mock data wiring (interim) ──────────────────────────────────── + // Until ParticipantViewModel binds in the engine wiring commit, the + // table is populated from a static sample list so the visual design + // can be validated end-to-end against representative data. + ParticipantsRepeater.ItemsSource = MockParticipant.Sample(); + } + + /// + /// Cycle the active theme between Dark and Light. The actual swap target + /// is the window's root visual element — RequestedTheme on that element + /// propagates the {ThemeResource} swap across the whole tree, with no + /// flicker. The system title-bar buttons aren't part of the visual tree, + /// so their colors are recomputed inline. + /// + /// Persistence to UIPreferences.Theme happens in the view-model wiring + /// commit (the WinUI host doesn't yet share UIPreferences with the WPF + /// host; both will once the WinUI host owns the .NET startup). + /// + private void OnThemeToggleClick(object sender, RoutedEventArgs e) + { + if (Content is FrameworkElement root) + { + var next = root.ActualTheme == ElementTheme.Dark + ? ElementTheme.Light + : ElementTheme.Dark; + root.RequestedTheme = next; + + // Title-bar system buttons read from AppWindow.TitleBar directly, + // not from the XAML resource dictionary, so we set them inline + // for both themes. Dark-mode chrome lands on near-white glyphs; + // light-mode chrome lands on near-black glyphs. + if (next == ElementTheme.Dark) + { + AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF6); + AppWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0x33, 0x34, 0x3A); + AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0x3F, 0x3F, 0x47); + ThemeToggleIcon.Glyph = ""; // sun + } + else + { + AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0x0A, 0x0A, 0x0A); + AppWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0xEC, 0xEE, 0xF1); + AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0xE0, 0xE3, 0xE7); + ThemeToggleIcon.Glyph = ""; // moon + } + } } }