mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 17:24:27 +08:00
feat.airapp与融合桌面
This commit is contained in:
32
LanMountainDesktop.AirAppHost/AirApp.axaml
Normal file
32
LanMountainDesktop.AirAppHost/AirApp.axaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="LanMountainDesktop.AirAppHost.AirApp"
|
||||
RequestedThemeVariant="Default">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<FontFamily x:Key="AppFontFamily">MiSans VF, avares://LanMountainDesktop.AirAppHost/Assets/Fonts#MiSans</FontFamily>
|
||||
<Color x:Key="AirAppWindowBackgroundColor">#FFF7F9FC</Color>
|
||||
<Color x:Key="AirAppWindowBorderColor">#22000000</Color>
|
||||
<Color x:Key="AirAppTitleTextColor">#FF171A20</Color>
|
||||
<Color x:Key="AirAppSecondaryTextColor">#FF657080</Color>
|
||||
<Color x:Key="AirAppAccentColor">#FF2D73E5</Color>
|
||||
<SolidColorBrush x:Key="AirAppWindowBackgroundBrush" Color="{StaticResource AirAppWindowBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="AirAppWindowBorderBrush" Color="{StaticResource AirAppWindowBorderColor}" />
|
||||
<SolidColorBrush x:Key="AirAppTitleTextBrush" Color="{StaticResource AirAppTitleTextColor}" />
|
||||
<SolidColorBrush x:Key="AirAppSecondaryTextBrush" Color="{StaticResource AirAppSecondaryTextColor}" />
|
||||
<SolidColorBrush x:Key="AirAppAccentBrush" Color="{StaticResource AirAppAccentColor}" />
|
||||
<SolidColorBrush x:Key="AdaptiveSurfaceRaisedBrush" Color="#FFF1F4F9" />
|
||||
<SolidColorBrush x:Key="AdaptiveButtonBorderBrush" Color="#16000000" />
|
||||
<SolidColorBrush x:Key="AdaptiveSurfaceBaseBrush" Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="SystemControlForegroundBaseMediumLowBrush" Color="#55000000" />
|
||||
<SolidColorBrush x:Key="AdaptiveAccentBrush" Color="#FF2D73E5" />
|
||||
<SolidColorBrush x:Key="AdaptiveOnAccentBrush" Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="AdaptiveTextPrimaryBrush" Color="#FF0F172A" />
|
||||
<CornerRadius x:Key="DesignCornerRadiusComponent">18</CornerRadius>
|
||||
<CornerRadius x:Key="DesignCornerRadiusSm">10</CornerRadius>
|
||||
<CornerRadius x:Key="DesignCornerRadiusXs">8</CornerRadius>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
24
LanMountainDesktop.AirAppHost/AirApp.axaml.cs
Normal file
24
LanMountainDesktop.AirAppHost/AirApp.axaml.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public sealed partial class AirApp : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var options = AirAppLaunchOptions.Parse(desktop.Args ?? []);
|
||||
desktop.MainWindow = new AirAppWindow(options);
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
64
LanMountainDesktop.AirAppHost/AirAppLaunchOptions.cs
Normal file
64
LanMountainDesktop.AirAppHost/AirAppLaunchOptions.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public sealed record AirAppLaunchOptions(
|
||||
string AppId,
|
||||
string SessionId,
|
||||
string? SourceComponentId,
|
||||
string? SourcePlacementId,
|
||||
string? LauncherPipeName,
|
||||
string? InstanceKey)
|
||||
{
|
||||
public const string WorldClockAppId = "world-clock";
|
||||
public const string WhiteboardAppId = "whiteboard";
|
||||
|
||||
public static AirAppLaunchOptions Parse(IReadOnlyList<string> args)
|
||||
{
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var index = 0; index < args.Count; index++)
|
||||
{
|
||||
var arg = args[index];
|
||||
if (!arg.StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = arg[2..].Trim();
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index + 1 < args.Count && !args[index + 1].StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
values[key] = args[index + 1];
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
values[key] = "true";
|
||||
}
|
||||
}
|
||||
|
||||
return new AirAppLaunchOptions(
|
||||
GetValue(values, "app-id", WorldClockAppId),
|
||||
GetValue(values, "session-id", Guid.NewGuid().ToString("N")),
|
||||
GetOptionalValue(values, "source-component-id"),
|
||||
GetOptionalValue(values, "source-placement-id"),
|
||||
GetOptionalValue(values, "launcher-pipe"),
|
||||
GetOptionalValue(values, "instance-key"));
|
||||
}
|
||||
|
||||
private static string GetValue(IReadOnlyDictionary<string, string> values, string key, string fallback)
|
||||
{
|
||||
return values.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)
|
||||
? value.Trim()
|
||||
: fallback;
|
||||
}
|
||||
|
||||
private static string? GetOptionalValue(IReadOnlyDictionary<string, string> values, string key)
|
||||
{
|
||||
return values.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)
|
||||
? value.Trim()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
64
LanMountainDesktop.AirAppHost/AirAppWindow.axaml
Normal file
64
LanMountainDesktop.AirAppHost/AirAppWindow.axaml
Normal file
@@ -0,0 +1,64 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="LanMountainDesktop.AirAppHost.AirAppWindow"
|
||||
Width="520"
|
||||
Height="360"
|
||||
MinWidth="360"
|
||||
MinHeight="260"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowDecorations="None"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
TransparencyLevelHint="Transparent"
|
||||
Background="Transparent"
|
||||
FontFamily="{DynamicResource AppFontFamily}"
|
||||
Title="Air APP">
|
||||
<Border x:Name="WindowShell"
|
||||
Background="{DynamicResource AirAppWindowBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource AirAppWindowBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="18"
|
||||
ClipToBounds="True"
|
||||
BoxShadow="0 18 44 #22000000">
|
||||
<Grid RowDefinitions="52,*">
|
||||
<Grid x:Name="TitleBar"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Background="Transparent"
|
||||
PointerPressed="OnTitleBarPointerPressed">
|
||||
<StackPanel Margin="18,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2">
|
||||
<TextBlock x:Name="TitleTextBlock"
|
||||
Text="Air APP"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AirAppTitleTextBrush}" />
|
||||
<TextBlock x:Name="SubtitleTextBlock"
|
||||
Text="LanMountainDesktop"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource AirAppSecondaryTextBrush}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0,8,10,8"
|
||||
Padding="0"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="OnCloseClick">
|
||||
<TextBlock Text="X"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AirAppTitleTextBrush}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ContentControl x:Name="ContentHost"
|
||||
Grid.Row="1" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
228
LanMountainDesktop.AirAppHost/AirAppWindow.axaml.cs
Normal file
228
LanMountainDesktop.AirAppHost/AirAppWindow.axaml.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public sealed partial class AirAppWindow : Window
|
||||
{
|
||||
private readonly AirAppLaunchOptions _options;
|
||||
private readonly AirAppWindowDescriptor _descriptor;
|
||||
private string _instanceKey = string.Empty;
|
||||
|
||||
public AirAppWindow()
|
||||
: this(AirAppLaunchOptions.Parse([]))
|
||||
{
|
||||
}
|
||||
|
||||
public AirAppWindow(AirAppLaunchOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_descriptor = AirAppWindowDescriptor.Create(options);
|
||||
InitializeComponent();
|
||||
ConfigureWindow();
|
||||
}
|
||||
|
||||
private void ConfigureWindow()
|
||||
{
|
||||
ApplyWindowDescriptor(_descriptor);
|
||||
|
||||
if (string.Equals(_options.AppId, AirAppLaunchOptions.WorldClockAppId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ContentHost.Content = new WorldClockAirAppView(_options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(_options.AppId, AirAppLaunchOptions.WhiteboardAppId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ConfigureWhiteboardWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentHost.Content = new TextBlock
|
||||
{
|
||||
Text = $"Unsupported Air APP: {_options.AppId}",
|
||||
Margin = new Avalonia.Thickness(18)
|
||||
};
|
||||
}
|
||||
|
||||
private void ApplyWindowDescriptor(AirAppWindowDescriptor descriptor)
|
||||
{
|
||||
Title = descriptor.Title;
|
||||
TitleTextBlock.Text = descriptor.TitleText;
|
||||
SubtitleTextBlock.Text = descriptor.SubtitleText;
|
||||
Width = descriptor.Width;
|
||||
Height = descriptor.Height;
|
||||
MinWidth = descriptor.MinWidth;
|
||||
MinHeight = descriptor.MinHeight;
|
||||
ShowInTaskbar = descriptor.ShowInTaskbar;
|
||||
CanResize = descriptor.CanResize;
|
||||
WindowDecorations = WindowDecorations.None;
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
ExtendClientAreaTitleBarHeightHint = -1;
|
||||
|
||||
TitleBar.IsVisible = true;
|
||||
Grid.SetRow(ContentHost, 1);
|
||||
Grid.SetRowSpan(ContentHost, 1);
|
||||
WindowState = WindowState.Normal;
|
||||
|
||||
switch (descriptor.ChromeMode)
|
||||
{
|
||||
case AirAppWindowChromeMode.Standard:
|
||||
break;
|
||||
|
||||
case AirAppWindowChromeMode.Borderless:
|
||||
HideCustomTitleBar();
|
||||
break;
|
||||
|
||||
case AirAppWindowChromeMode.FullScreen:
|
||||
HideCustomTitleBar();
|
||||
WindowShell.CornerRadius = new Avalonia.CornerRadius(0);
|
||||
WindowShell.BorderThickness = new Avalonia.Thickness(0);
|
||||
WindowShell.BoxShadow = default;
|
||||
WindowState = WindowState.FullScreen;
|
||||
break;
|
||||
|
||||
case AirAppWindowChromeMode.Tool:
|
||||
ShowInTaskbar = false;
|
||||
CanResize = false;
|
||||
break;
|
||||
|
||||
case AirAppWindowChromeMode.BackgroundOnly:
|
||||
// Reserved for future background-only Air APPs. Keep a normal window for now
|
||||
// so accidental launches remain visible and debuggable.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideCustomTitleBar()
|
||||
{
|
||||
TitleBar.IsVisible = false;
|
||||
Grid.SetRow(ContentHost, 0);
|
||||
Grid.SetRowSpan(ContentHost, 2);
|
||||
}
|
||||
|
||||
private void ConfigureWhiteboardWindow()
|
||||
{
|
||||
var componentId = string.IsNullOrWhiteSpace(_options.SourceComponentId)
|
||||
? BuiltInComponentIds.DesktopWhiteboard
|
||||
: _options.SourceComponentId.Trim();
|
||||
var baseWidthCells = string.Equals(componentId, BuiltInComponentIds.DesktopBlackboardLandscape, StringComparison.OrdinalIgnoreCase)
|
||||
? 4
|
||||
: 2;
|
||||
var widget = new WhiteboardWidget(baseWidthCells);
|
||||
widget.SetComponentPlacementContext(componentId, _options.SourcePlacementId);
|
||||
widget.SetSurfaceMode(
|
||||
WhiteboardWidgetSurfaceMode.AirApp,
|
||||
() =>
|
||||
{
|
||||
widget.ForceSaveNote();
|
||||
Close();
|
||||
});
|
||||
|
||||
ContentHost.Content = widget;
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
_ = RegisterWithLauncherAsync();
|
||||
AppLogger.Info(
|
||||
"AirAppWindow",
|
||||
$"Opened. WindowRole=AirApp; AppId='{_options.AppId}'; ForegroundActivationRequested=True.");
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Activate();
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_ = UnregisterWithLauncherAsync();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private async Task RegisterWithLauncherAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_options.LauncherPipeName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_instanceKey = ResolveInstanceKey();
|
||||
try
|
||||
{
|
||||
using var client = new LanMountainDesktopIpcClient();
|
||||
await client.ConnectAsync(_options.LauncherPipeName).ConfigureAwait(false);
|
||||
var proxy = client.CreateProxy<IAirAppLifecycleService>();
|
||||
_ = await proxy.RegisterAsync(new AirAppRegistrationRequest(
|
||||
_instanceKey,
|
||||
_options.AppId,
|
||||
_options.SessionId,
|
||||
Environment.ProcessId,
|
||||
Title ?? "Air APP",
|
||||
_options.SourceComponentId,
|
||||
_options.SourcePlacementId)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Registration is best-effort; Launcher also tracks the process it started.
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnregisterWithLauncherAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_options.LauncherPipeName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var instanceKey = string.IsNullOrWhiteSpace(_instanceKey) ? ResolveInstanceKey() : _instanceKey;
|
||||
try
|
||||
{
|
||||
using var client = new LanMountainDesktopIpcClient();
|
||||
await client.ConnectAsync(_options.LauncherPipeName).ConfigureAwait(false);
|
||||
var proxy = client.CreateProxy<IAirAppLifecycleService>();
|
||||
_ = await proxy.UnregisterAsync(instanceKey, Environment.ProcessId).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Unregister is best-effort; Launcher prunes dead processes.
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveInstanceKey()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_options.InstanceKey))
|
||||
{
|
||||
return _options.InstanceKey.Trim();
|
||||
}
|
||||
|
||||
var componentId = string.IsNullOrWhiteSpace(_options.SourceComponentId)
|
||||
? "none"
|
||||
: _options.SourceComponentId.Trim();
|
||||
var placementId = string.IsNullOrWhiteSpace(_options.SourcePlacementId)
|
||||
? "none"
|
||||
: _options.SourcePlacementId.Trim();
|
||||
return $"{_options.AppId}:{componentId}:{placementId}";
|
||||
}
|
||||
}
|
||||
10
LanMountainDesktop.AirAppHost/AirAppWindowChromeMode.cs
Normal file
10
LanMountainDesktop.AirAppHost/AirAppWindowChromeMode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public enum AirAppWindowChromeMode
|
||||
{
|
||||
Standard,
|
||||
Borderless,
|
||||
FullScreen,
|
||||
Tool,
|
||||
BackgroundOnly
|
||||
}
|
||||
137
LanMountainDesktop.AirAppHost/AirAppWindowDescriptor.cs
Normal file
137
LanMountainDesktop.AirAppHost/AirAppWindowDescriptor.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public sealed record AirAppWindowDescriptor(
|
||||
string WindowTitle,
|
||||
string TitleBarTitle,
|
||||
string TitleBarSubtitle,
|
||||
AirAppWindowChromeMode ChromeMode,
|
||||
bool CanResize,
|
||||
bool ShowInTaskbar,
|
||||
double Width,
|
||||
double Height,
|
||||
double MinWidth,
|
||||
double MinHeight)
|
||||
{
|
||||
public string Title => WindowTitle;
|
||||
|
||||
public string TitleText => TitleBarTitle;
|
||||
|
||||
public string SubtitleText => TitleBarSubtitle;
|
||||
|
||||
public static AirAppWindowDescriptor Create(AirAppLaunchOptions options)
|
||||
{
|
||||
if (string.Equals(options.AppId, AirAppLaunchOptions.WorldClockAppId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Standard(
|
||||
"World Clock - Air APP",
|
||||
"World Clock",
|
||||
"Air APP");
|
||||
}
|
||||
|
||||
if (string.Equals(options.AppId, AirAppLaunchOptions.WhiteboardAppId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FullScreen(
|
||||
"Whiteboard - Air APP",
|
||||
"Whiteboard",
|
||||
"Air APP");
|
||||
}
|
||||
|
||||
return Standard(
|
||||
"Air APP",
|
||||
"Air APP",
|
||||
options.AppId);
|
||||
}
|
||||
|
||||
public static AirAppWindowDescriptor Standard(
|
||||
string windowTitle,
|
||||
string titleBarTitle,
|
||||
string titleBarSubtitle,
|
||||
double width = 520,
|
||||
double height = 360,
|
||||
double minWidth = 360,
|
||||
double minHeight = 260)
|
||||
{
|
||||
return new AirAppWindowDescriptor(
|
||||
windowTitle,
|
||||
titleBarTitle,
|
||||
titleBarSubtitle,
|
||||
AirAppWindowChromeMode.Standard,
|
||||
CanResize: true,
|
||||
ShowInTaskbar: true,
|
||||
width,
|
||||
height,
|
||||
minWidth,
|
||||
minHeight);
|
||||
}
|
||||
|
||||
public static AirAppWindowDescriptor FullScreen(
|
||||
string windowTitle,
|
||||
string titleBarTitle,
|
||||
string titleBarSubtitle)
|
||||
{
|
||||
return new AirAppWindowDescriptor(
|
||||
windowTitle,
|
||||
titleBarTitle,
|
||||
titleBarSubtitle,
|
||||
AirAppWindowChromeMode.FullScreen,
|
||||
CanResize: false,
|
||||
ShowInTaskbar: true,
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
MinWidth: 360,
|
||||
MinHeight: 260);
|
||||
}
|
||||
|
||||
public static AirAppWindowDescriptor Borderless(
|
||||
string windowTitle,
|
||||
double width = 520,
|
||||
double height = 360)
|
||||
{
|
||||
return new AirAppWindowDescriptor(
|
||||
windowTitle,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
AirAppWindowChromeMode.Borderless,
|
||||
CanResize: true,
|
||||
ShowInTaskbar: true,
|
||||
width,
|
||||
height,
|
||||
MinWidth: 240,
|
||||
MinHeight: 180);
|
||||
}
|
||||
|
||||
public static AirAppWindowDescriptor Tool(
|
||||
string windowTitle,
|
||||
string titleBarTitle,
|
||||
string titleBarSubtitle,
|
||||
double width = 360,
|
||||
double height = 260)
|
||||
{
|
||||
return new AirAppWindowDescriptor(
|
||||
windowTitle,
|
||||
titleBarTitle,
|
||||
titleBarSubtitle,
|
||||
AirAppWindowChromeMode.Tool,
|
||||
CanResize: false,
|
||||
ShowInTaskbar: false,
|
||||
width,
|
||||
height,
|
||||
MinWidth: 240,
|
||||
MinHeight: 180);
|
||||
}
|
||||
|
||||
public static AirAppWindowDescriptor BackgroundOnly(string appId)
|
||||
{
|
||||
return new AirAppWindowDescriptor(
|
||||
$"{appId} - Air APP",
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
AirAppWindowChromeMode.BackgroundOnly,
|
||||
CanResize: false,
|
||||
ShowInTaskbar: false,
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
MinWidth: 1,
|
||||
MinHeight: 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RollForward>LatestMajor</RollForward>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ApplicationIcon>..\LanMountainDesktop\Assets\logo_nightly.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="..\LanMountainDesktop\Assets\Fonts\**" Link="Assets\Fonts\%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
<AvaloniaResource Include="..\LanMountainDesktop\Assets\logo_nightly.png" Link="Assets\logo_nightly.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LanMountainDesktop\LanMountainDesktop.csproj"
|
||||
AdditionalProperties="SkipAirAppHostBuild=true" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" />
|
||||
<PackageReference Include="Avalonia.Desktop" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||
<PackageReference Include="FluentAvaloniaUI" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
21
LanMountainDesktop.AirAppHost/Program.cs
Normal file
21
LanMountainDesktop.AirAppHost/Program.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Avalonia;
|
||||
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
private static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return AppBuilder.Configure<AirApp>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
}
|
||||
39
LanMountainDesktop.AirAppHost/WorldClockAirAppView.axaml
Normal file
39
LanMountainDesktop.AirAppHost/WorldClockAirAppView.axaml
Normal file
@@ -0,0 +1,39 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="LanMountainDesktop.AirAppHost.WorldClockAirAppView">
|
||||
<Grid RowDefinitions="*,Auto"
|
||||
Margin="24,8,24,24">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<TextBlock x:Name="TimeTextBlock"
|
||||
Text="00:00:00"
|
||||
FontSize="58"
|
||||
FontWeight="SemiBold"
|
||||
LetterSpacing="0"
|
||||
Foreground="{DynamicResource AirAppTitleTextBrush}"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="DateTextBlock"
|
||||
Text="0000-00-00"
|
||||
FontSize="17"
|
||||
FontWeight="Medium"
|
||||
Foreground="{DynamicResource AirAppSecondaryTextBrush}"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="TimeZoneTextBlock"
|
||||
Text="Local Time"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource AirAppSecondaryTextBrush}"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="12,7"
|
||||
CornerRadius="999"
|
||||
Background="#112D73E5">
|
||||
<TextBlock x:Name="SessionTextBlock"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource AirAppAccentBrush}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
52
LanMountainDesktop.AirAppHost/WorldClockAirAppView.axaml.cs
Normal file
52
LanMountainDesktop.AirAppHost/WorldClockAirAppView.axaml.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
public sealed partial class WorldClockAirAppView : UserControl
|
||||
{
|
||||
private readonly DispatcherTimer _timer = new()
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
private readonly AirAppLaunchOptions _options;
|
||||
|
||||
public WorldClockAirAppView()
|
||||
: this(AirAppLaunchOptions.Parse([]))
|
||||
{
|
||||
}
|
||||
|
||||
public WorldClockAirAppView(AirAppLaunchOptions options)
|
||||
{
|
||||
_options = options;
|
||||
InitializeComponent();
|
||||
|
||||
SessionTextBlock.Text = string.IsNullOrWhiteSpace(_options.SourcePlacementId)
|
||||
? "World Clock"
|
||||
: $"World Clock / {_options.SourcePlacementId}";
|
||||
|
||||
_timer.Tick += OnTimerTick;
|
||||
AttachedToVisualTree += (_, _) =>
|
||||
{
|
||||
UpdateTime();
|
||||
_timer.Start();
|
||||
};
|
||||
DetachedFromVisualTree += (_, _) => _timer.Stop();
|
||||
UpdateTime();
|
||||
}
|
||||
|
||||
private void OnTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateTime();
|
||||
}
|
||||
|
||||
private void UpdateTime()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
TimeTextBlock.Text = now.ToString("HH:mm:ss", CultureInfo.CurrentCulture);
|
||||
DateTextBlock.Text = now.ToString("yyyy-MM-dd dddd", CultureInfo.CurrentCulture);
|
||||
TimeZoneTextBlock.Text = TimeZoneInfo.Local.DisplayName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user