Code/InteractiveComputer/UI/ComputerDesktop.razor
@namespace PaneOS.InteractiveComputer
@using System
@using System.Collections.Generic
@using System.Linq
@using PaneOS.InteractiveComputer
@using PaneOS.InteractiveComputer.Core
@using Sandbox
@using Sandbox.UI
@inherits PanelComponent
@if ( Computer is null || Runtime is null )
{
return;
}
<root class="computer-root @(Runtime.State.DarkModeEnabled ? "dark-mode" : "") @(Runtime.State.IsSleeping ? "is-sleeping" : "") @(!ShouldShowDesktop ? "is-hidden" : "")"
style="width: 100%; height: 100%; @GetDesktopBackgroundStyle()">
@if ( ShouldRenderWallpaperImageLayer() )
{
<div class="wallpaper-image-layer"></div>
}
@if ( ShouldShowDesktop )
{
<div @ref="desktopSurfacePanel" class="desktop" onclick="@CloseStartMenu">
@if ( Runtime.State.IsSleeping )
{
<div class="sleep-overlay" onclick="@Wake">
<div class="sleep-card">
<div class="system-logo-badge">PaneOS</div>
<label>Computer is sleeping</label>
<span>Click to wake</span>
</div>
</div>
}
else if ( Runtime.State.RestartLogSecondsRemaining > 0f )
{
<div class="restart-overlay">
<div class="restart-log-window">
<label>PaneOS restart sequence</label>
<div class="restart-log-lines">
@foreach ( var line in Runtime.State.RestartLogLines )
{
<div class="restart-log-line">@line</div>
}
</div>
</div>
</div>
}
else if ( Runtime.State.BootSplashSecondsRemaining > 0f )
{
<div class="boot-overlay">
<div class="boot-card">
<div class="system-logo-badge large">PaneOS</div>
<label>PaneOS</label>
<span>Starting up...</span>
</div>
</div>
}
else if ( Runtime.State.IsLocked )
{
<div class="sleep-overlay" onclick="@Unlock">
<div class="sleep-card">
<div class="system-logo-badge">PaneOS</div>
<label>PaneOS Locked</label>
<span>Click to unlock</span>
</div>
</div>
}
else
{
<div class="shortcut-grid">
@foreach ( var shortcut in GetDesktopShortcuts() )
{
<button class="desktop-shortcut @(selectedDesktopShortcutIds.Contains( shortcut.Id ) ? "selected" : "")"
style="@GetShortcutStyle( shortcut.Index )"
onclick="@(() => ActivateDesktopShortcut( shortcut.Id ))"
@onclick:stopPropagation="true"
@onmousedown:stopPropagation="true"
ondblclick="@(() => ActivateDesktopShortcut( shortcut.Id ))"
oncontextmenu="@(() => OpenDesktopContextMenu( shortcut.Id ))">
<div class="desktop-icon @(string.IsNullOrWhiteSpace( shortcut.IconTexture ) ? "" : "has-texture")"
style="@GetShortcutIconStyle( shortcut )">@GetShortcutIconText( shortcut )</div>
<label>@shortcut.Label</label>
</button>
}
</div>
@if ( desktopContextMenuShortcutId is not null )
{
var contextShortcut = GetDesktopShortcutById( desktopContextMenuShortcutId );
if ( contextShortcut is not null )
{
<div class="desktop-context-menu" style="@GetDesktopContextMenuStyle()">
<button onclick="@(() => ActivateDesktopShortcut( contextShortcut.Id ))">Open</button>
@if ( contextShortcut.CanRename )
{
<button onclick="@PromptRenameSelectedDesktopShortcut">Rename</button>
}
@if ( contextShortcut.CanRestore )
{
<button onclick="@RestoreSelectedDesktopShortcut">Restore</button>
}
@if ( contextShortcut.CanDelete )
{
<button onclick="@DeleteSelectedDesktopShortcuts">Delete</button>
}
</div>
}
}
@foreach ( var app in Runtime.OpenApps )
{
if ( !app.Descriptor.HasWindow || app.State.IsMinimized )
continue;
<div @key="app.State.InstanceId"
class="xp-window @(app.State.IsMinimized ? "minimized" : "") @(Runtime.FocusedApp == app ? "focused" : "") @(Runtime.GetEffectiveStatus( app.State.InstanceId ) == ComputerProcessStatus.NotResponding ? "not-responding" : "")"
style="left: @(app.State.X)px; top: @(app.State.Y)px; width: @(app.State.Width)px; height: @(app.State.Height)px; z-index: @(app.State.ZIndex);"
onclick="@(() => Focus( app.State.InstanceId ))"
@onmousedown:stopPropagation="true">
<div class="title-bar"
@onmousedown="@(() => StartWindowDrag( app.State.InstanceId ))"
@onmousedown:stopPropagation="true">
<div class="title-text">
<span>@app.State.Icon</span>
<label>@app.State.Title</label>
</div>
<div class="window-controls"
@onclick:stopPropagation="true"
@onmousedown:stopPropagation="true">
@if ( app.Session.CanMinimize )
{
<button onclick="@(() => Minimize( app.State.InstanceId ))"
@onclick:stopPropagation="true">_</button>
}
@if ( app.Session.CanClose )
{
<button class="close"
onclick="@(() => Close( app.State.InstanceId ))"
@onclick:stopPropagation="true">x</button>
}
</div>
</div>
<ComputerAppHost @key="app.State.InstanceId" Instance=@app />
<div class="window-resize-grip"
@onmousedown="@(() => StartWindowResize( app.State.InstanceId ))"
@onmousedown:stopPropagation="true"></div>
@if ( Runtime.ShouldBlockInput( app.State.InstanceId ) )
{
<div class="window-input-overlay">
<label>PaneOS is busy...</label>
</div>
}
</div>
}
}
</div>
@if ( Runtime.State.StartMenuOpen && !Runtime.State.IsSleeping && !Runtime.State.IsLocked && !Runtime.IsRestarting )
{
<div class="start-menu">
<div class="start-header">
<div class="user-tile">e</div>
<label>@Computer.ComputerId</label>
</div>
<div class="start-apps">
@foreach ( var app in Runtime.Apps )
{
if ( !app.ShowInStartMenu )
continue;
<div class="start-app" onclick="@(() => OpenApp( app.Id ))">
<span>@app.Icon</span>
<label>@app.Title</label>
</div>
}
</div>
<div class="start-power">
<button onclick="@Lock">Lock</button>
<button onclick="@Restart">Restart</button>
<button onclick="@Sleep">Sleep</button>
</div>
</div>
}
@if ( !Runtime.State.IsSleeping && !Runtime.State.IsLocked && !Runtime.IsRestarting )
{
<div class="taskbar">
<button class="start-button @(Runtime.State.StartMenuOpen ? "active" : "")" onclick="@ToggleStart">
<span>P</span>
<label>start</label>
</button>
<div class="task-list">
@foreach ( var app in Runtime.OpenApps )
{
if ( !app.Descriptor.ShowInTaskbar || !app.Descriptor.HasWindow )
continue;
<button class="task-button @(Runtime.FocusedApp == app && !app.State.IsMinimized ? "active" : "")"
onclick="@(() => ToggleTaskbarApp( app.State.InstanceId ))">
<span>@app.State.Icon</span>
<label>@app.State.Title</label>
</button>
}
</div>
<div class="tray">
<div class="tray-icons">
<span>NET</span>
<span>VOL</span>
</div>
<div class="tray-clock">
<label>@System.DateTime.Now.ToString( "h:mm tt" )</label>
<span>@System.DateTime.Now.ToString( "dd MMM yyyy" )</span>
</div>
</div>
</div>
}
@if ( Runtime.Notifications.Count > 0 )
{
<div class="notification-stack">
@foreach ( var notification in Runtime.Notifications )
{
<div class="notification-card">
<div class="notification-icon">@notification.Icon</div>
<div class="notification-copy">
<label>@notification.Title</label>
<span>@notification.Message</span>
</div>
</div>
}
</div>
}
@if ( Runtime.ActiveMessageBox is not null )
{
var messageBox = Runtime.ActiveMessageBox;
<div class="message-box-overlay">
<div class="message-box-window"
@onclick:stopPropagation="true"
@onmousedown:stopPropagation="true">
<div class="message-box-title-bar">
<label>@messageBox.Options.Title</label>
</div>
<div class="message-box-body">
<div class="message-box-icon">@messageBox.Options.Icon</div>
<div class="message-box-copy">
<label>@messageBox.Options.Message</label>
@if ( messageBox.Options.HasTextInput )
{
<ComputerMessageBoxTextEntry MessageBox=@messageBox OnTextChanged=@UpdateMessageBoxText />
}
</div>
</div>
<div class="message-box-actions">
@foreach ( var button in messageBox.Options.Buttons )
{
<button onclick="@(() => CloseMessageBox( button ))">@button</button>
}
</div>
</div>
</div>
}
@if ( Runtime.ActiveFileDialog is not null )
{
var fileDialog = Runtime.ActiveFileDialog;
<div class="message-box-overlay">
<div class="file-dialog-window"
@onclick:stopPropagation="true"
@onmousedown:stopPropagation="true">
<div class="message-box-title-bar">
<label>@fileDialog.Options.Title</label>
</div>
<div class="file-dialog-toolbar">
<button onclick="@FileDialogUp">Up</button>
<button onclick="@(() => NavigateFileDialogTo( Computer?.Runtime?.GetDefaultDocumentsPath() ?? "" ))">My Documents</button>
<label>@fileDialog.CurrentPathDisplay</label>
</div>
<div class="file-dialog-list">
@foreach ( var item in fileDialog.VisibleItems )
{
<div class="file-dialog-row @(string.Equals( fileDialog.SelectedVirtualPath, item.VirtualPath, StringComparison.OrdinalIgnoreCase ) ? "selected" : "")"
onclick="@(() => SelectFileDialogItem( item.VirtualPath ))"
ondblclick="@(() => ActivateFileDialogItem( item.VirtualPath ))">
<label>@item.Name</label>
<span>@(item.IsDirectory ? "Folder" : item.Extension)</span>
</div>
}
</div>
@if ( fileDialog.Options.Mode == ComputerFileDialogMode.Save )
{
<div class="file-dialog-name-row">
<label>File name</label>
<ComputerFileDialogTextEntry FileDialog=@fileDialog OnTextChanged=@UpdateFileDialogFileName />
</div>
}
<div class="message-box-actions">
<button onclick="@ConfirmFileDialog">@(fileDialog.Options.ConfirmButtonText == "" ? (fileDialog.Options.Mode == ComputerFileDialogMode.Open ? "Open" : "Save") : fileDialog.Options.ConfirmButtonText)</button>
<button onclick="@CancelFileDialog">Cancel</button>
</div>
</div>
</div>
}
@if ( Runtime.IsScreenSaverActive && !Computer.IsPlayerInteracting )
{
var saver = Runtime.State.ScreenSaver;
<div class="screen-saver" onclick="@ScreenSaverActivity">
<div class="screen-saver-logo" style="@GetScreenSaverLogoStyle( saver )">
<div class="screen-saver-logo-image"></div>
</div>
</div>
}
}
else
{
<div class="desktop desktop-hidden"></div>
}
</root>
@code
{
[Property] public InteractiveComputerComponent? Computer { get; set; }
[Property] public bool VisibleOnlyWhenInteracting { get; set; } = true;
private const float TitleBarHeight = 29f;
private const float WindowControlInset = 64f;
private const float ResizeGripSize = 18f;
private const float DesktopDoubleClickSeconds = 0.35f;
private ComputerRuntime? Runtime => Computer?.Runtime;
private int lastRenderHash;
private string? draggingWindowId;
private Vector2 draggingWindowOffset;
private string? resizingWindowId;
private Vector2 resizingStartMouse;
private Vector2 resizingStartSize;
private bool wasDraggingWindow;
private Vector2 desktopContextMenuPosition;
private string? desktopContextMenuShortcutId;
private string? lastDesktopShortcutClickedId;
private float lastDesktopShortcutClickedAt;
private Panel? desktopSurfacePanel;
private readonly HashSet<string> selectedDesktopShortcutIds = new( StringComparer.OrdinalIgnoreCase );
private bool ShouldShowDesktop => Computer is not null && Runtime is not null && (!VisibleOnlyWhenInteracting || Computer.IsPlayerInteracting);
protected override int BuildHash()
{
unchecked
{
var hash = 17;
hash = hash * 31 + ((Computer?.IsPlayerInteracting ?? false) ? 1 : 0);
hash = hash * 31 + (Runtime?.DesktopVersion ?? 0);
hash = hash * 31 + ((Runtime?.State.ScreenSaver.IsActive ?? false) ? 1 : 0);
hash = hash * 31 + System.DateTime.Now.Minute;
return hash;
}
}
protected override void OnUpdate()
{
EnsureAttachedPanelMatchesResolution();
if ( draggingWindowId is not null )
{
ContinueWindowDrag( GetLocalMousePosition() );
wasDraggingWindow = true;
}
else
{
wasDraggingWindow = false;
}
var hash = BuildHash();
if ( hash == lastRenderHash )
return;
lastRenderHash = hash;
StateHasChanged();
}
private void OpenApp( string appId )
{
Runtime?.OpenApp( appId );
StateHasChanged();
}
private void Focus( string instanceId )
{
if ( wasDraggingWindow )
return;
Runtime?.Focus( instanceId );
StateHasChanged();
}
private void Minimize( string instanceId )
{
Runtime?.Minimize( instanceId );
StateHasChanged();
}
private void Close( string instanceId )
{
Runtime?.Close( instanceId );
StateHasChanged();
}
private void ToggleTaskbarApp( string instanceId )
{
Runtime?.ToggleTaskbarApp( instanceId );
StateHasChanged();
}
private void ToggleStart()
{
Runtime?.ToggleStartMenu();
StateHasChanged();
}
private void CloseStartMenu()
{
if ( Runtime is null )
return;
Runtime.NotifyUserActivity();
HideDesktopContextMenu();
if ( !Runtime.State.StartMenuOpen )
{
StateHasChanged();
return;
}
Runtime.State.StartMenuOpen = false;
Runtime.MarkChanged();
StateHasChanged();
}
private void StartWindowDrag( string instanceId )
{
if ( Runtime is null )
return;
var app = Runtime.OpenApps.FirstOrDefault( x => x.State.InstanceId == instanceId );
if ( app is null )
return;
Runtime.Focus( instanceId );
var localMousePosition = GetLocalMousePosition();
draggingWindowId = instanceId;
draggingWindowOffset = localMousePosition - new Vector2( app.State.X, app.State.Y );
StateHasChanged();
}
private void StartWindowResize( string instanceId )
{
if ( Runtime is null )
return;
var app = Runtime.OpenApps.FirstOrDefault( x => x.State.InstanceId == instanceId );
if ( app is null )
return;
Runtime.Focus( instanceId );
resizingWindowId = instanceId;
resizingStartMouse = GetLocalMousePosition();
resizingStartSize = new Vector2( app.State.Width, app.State.Height );
StateHasChanged();
}
private void Sleep()
{
Runtime?.Sleep();
StateHasChanged();
}
private void Wake()
{
Runtime?.Wake();
StateHasChanged();
}
private void Restart()
{
Runtime?.Restart();
StateHasChanged();
}
private void Lock()
{
Runtime?.Lock();
StateHasChanged();
}
private void Unlock()
{
Runtime?.Unlock();
StateHasChanged();
}
private void ScreenSaverActivity()
{
Runtime?.NotifyUserActivity();
StateHasChanged();
}
private void CloseMessageBox( string button )
{
Runtime?.CloseMessageBox( button );
StateHasChanged();
}
private void UpdateMessageBoxText( string value )
{
Runtime?.UpdateMessageBoxText( value );
}
private void NavigateFileDialogTo( string virtualPath )
{
Runtime?.NavigateFileDialogTo( virtualPath );
StateHasChanged();
}
private void FileDialogUp()
{
Runtime?.MoveFileDialogUp();
StateHasChanged();
}
private void SelectFileDialogItem( string virtualPath )
{
Runtime?.SelectFileDialogItem( virtualPath );
StateHasChanged();
}
private void ActivateFileDialogItem( string virtualPath )
{
Runtime?.ActivateFileDialogItem( virtualPath );
StateHasChanged();
}
private void ConfirmFileDialog()
{
Runtime?.ConfirmFileDialog();
StateHasChanged();
}
private void UpdateFileDialogFileName( string value )
{
Runtime?.UpdateFileDialogFileName( value );
}
private void CancelFileDialog()
{
Runtime?.CancelFileDialog();
StateHasChanged();
}
protected override void OnMouseDown( MousePanelEvent e )
{
base.OnMouseDown( e );
if ( !ShouldShowDesktop || Runtime is null || Runtime.State.IsSleeping || Runtime.State.IsLocked || Runtime.IsRestarting || Runtime.ActiveMessageBox is not null )
return;
HideDesktopContextMenu();
selectedDesktopShortcutIds.Clear();
StateHasChanged();
e.StopPropagation();
}
protected override void OnMouseMove( MousePanelEvent e )
{
base.OnMouseMove( e );
if ( resizingWindowId is not null )
{
ContinueWindowResize( GetLocalMousePosition( e ) );
e.StopPropagation();
return;
}
if ( draggingWindowId is null )
return;
ContinueWindowDrag( GetLocalMousePosition( e ) );
e.StopPropagation();
}
protected override void OnMouseUp( MousePanelEvent e )
{
base.OnMouseUp( e );
if ( draggingWindowId is null )
{
if ( resizingWindowId is null )
return;
resizingWindowId = null;
e.StopPropagation();
return;
}
draggingWindowId = null;
e.StopPropagation();
}
private void ContinueWindowDrag( Vector2 localMousePosition )
{
if ( Runtime is null || draggingWindowId is null )
return;
var nextPosition = localMousePosition - draggingWindowOffset;
Runtime.MoveWindow( draggingWindowId, (int)nextPosition.x, (int)nextPosition.y );
StateHasChanged();
}
private void ContinueWindowResize( Vector2 localMousePosition )
{
if ( Runtime is null || resizingWindowId is null )
return;
var delta = localMousePosition - resizingStartMouse;
var width = resizingStartSize.x + delta.x;
var height = resizingStartSize.y + delta.y;
Runtime.ResizeWindow( resizingWindowId, (int)width, (int)height );
StateHasChanged();
}
private ComputerRunningApp? FindTopWindowAt( Vector2 localMousePosition )
{
if ( Runtime is null )
return null;
foreach ( var app in Runtime.OpenApps.OrderByDescending( x => x.State.ZIndex ) )
{
if ( app.State.IsMinimized )
continue;
if ( IsInsideWindowBounds( app, localMousePosition ) )
return app;
}
return null;
}
private static bool IsInsideWindowBounds( ComputerRunningApp app, Vector2 localMousePosition )
{
return localMousePosition.x >= app.State.X &&
localMousePosition.x <= app.State.X + app.State.Width &&
localMousePosition.y >= app.State.Y &&
localMousePosition.y <= app.State.Y + app.State.Height;
}
private static bool IsInsideWindowTitleBar( ComputerRunningApp app, Vector2 localMousePosition )
{
return localMousePosition.x >= app.State.X &&
localMousePosition.x <= app.State.X + app.State.Width &&
localMousePosition.y >= app.State.Y &&
localMousePosition.y <= app.State.Y + TitleBarHeight;
}
private static bool IsInsideWindowControls( ComputerRunningApp app, Vector2 localMousePosition )
{
return IsInsideWindowTitleBar( app, localMousePosition ) &&
localMousePosition.x >= app.State.X + app.State.Width - WindowControlInset;
}
private static bool IsInsideResizeGrip( ComputerRunningApp app, Vector2 localMousePosition )
{
return localMousePosition.x >= app.State.X + app.State.Width - ResizeGripSize &&
localMousePosition.x <= app.State.X + app.State.Width &&
localMousePosition.y >= app.State.Y + app.State.Height - ResizeGripSize &&
localMousePosition.y <= app.State.Y + app.State.Height;
}
private Vector2 GetLocalMousePosition( MousePanelEvent? e = null )
{
var desktopSurface = GetDesktopSurfacePanel();
if ( e is not null && desktopSurface is not null )
{
var screenPosition = e.Target is not null
? e.Target.PanelPositionToScreenPosition( e.LocalPosition )
: e.LocalPosition;
return desktopSurface.ScreenPositionToPanelPosition( screenPosition );
}
return desktopSurface?.MousePosition ?? Panel?.MousePosition ?? Vector2.Zero;
}
private Panel? GetDesktopSurfacePanel()
{
if ( desktopSurfacePanel?.IsValid == true )
return desktopSurfacePanel;
return Panel?.Children.FirstOrDefault( child => child.HasClass( "desktop" ) );
}
private string GetDesktopBackgroundStyle()
{
return Runtime is null
? ""
: ComputerWallpaperPolicy.GetBackgroundStyle( Runtime.State.DesktopWallpaper );
}
private bool ShouldRenderWallpaperImageLayer()
{
return Runtime is not null && ComputerWallpaperPolicy.Normalize( Runtime.State.DesktopWallpaper ) == "default";
}
private IReadOnlyList<DesktopShortcutItem> GetDesktopShortcuts()
{
if ( Runtime is null )
return Array.Empty<DesktopShortcutItem>();
var shortcuts = new List<DesktopShortcutItem>();
foreach ( var app in Runtime.Apps.Where( x => x.ShowOnDesktop ) )
{
shortcuts.Add( new DesktopShortcutItem
{
Id = $"app:{app.Id}",
Label = app.Title,
Icon = app.Icon,
IconTexture = ResolveAppTexturePath( app ),
AppId = app.Id
} );
}
shortcuts.Add( new DesktopShortcutItem
{
Id = "special:recycle",
Label = "Recycle Bin",
Icon = "RB",
IconTexture = ResolveThemeTexturePath( "App_recycleBin" ),
VirtualPath = "/C:/Recycle Bin",
IsDirectory = true
} );
foreach ( var item in PaneArchiveFileSystem.GetItems( Runtime.GetArchivePath(), ParseVirtualPath( Runtime.GetDefaultDocumentsPath() ) ) )
{
shortcuts.Add( new DesktopShortcutItem
{
Id = $"doc:{item.VirtualPath}",
Label = item.Name,
Icon = item.IsDirectory ? "FD" : "FI",
IconTexture = ResolveArchiveItemTexturePath( item ),
VirtualPath = item.VirtualPath,
IsDirectory = item.IsDirectory,
CanRename = true,
CanDelete = true
} );
}
for ( var index = 0; index < shortcuts.Count; index++ )
shortcuts[index].Index = index;
return shortcuts;
}
private DesktopShortcutItem? GetDesktopShortcutById( string shortcutId )
{
return GetDesktopShortcuts().FirstOrDefault( x => x.Id.Equals( shortcutId, StringComparison.OrdinalIgnoreCase ) );
}
private string GetShortcutStyle( int index )
{
var position = GetShortcutPosition( index );
return $"left: {position.x}px; top: {position.y}px;";
}
private static string GetShortcutIconStyle( DesktopShortcutItem shortcut )
{
return string.IsNullOrWhiteSpace( shortcut.IconTexture )
? ""
: $"background-image: url(\"{shortcut.IconTexture}\");";
}
private static string GetShortcutIconText( DesktopShortcutItem shortcut )
{
return string.IsNullOrWhiteSpace( shortcut.IconTexture ) ? shortcut.Icon : "";
}
private Vector2 GetShortcutPosition( int index )
{
var position = DesktopShortcutLayoutPolicy.GetPosition( index, Runtime?.State.ResolutionY ?? 768 );
return new Vector2( position.X, position.Y );
}
private bool TryGetDesktopShortcutAt( Vector2 localMousePosition, out DesktopShortcutItem? shortcut )
{
var resolutionY = Runtime?.State.ResolutionY ?? 768;
foreach ( var item in GetDesktopShortcuts() )
{
if ( !DesktopShortcutLayoutPolicy.HitTest( item.Index, resolutionY, localMousePosition.x, localMousePosition.y ) )
continue;
shortcut = item;
return true;
}
shortcut = null;
return false;
}
private void SelectDesktopShortcut( string shortcutId )
{
var clickedAt = Time.Now;
if ( selectedDesktopShortcutIds.Contains( shortcutId ) &&
string.Equals( lastDesktopShortcutClickedId, shortcutId, StringComparison.OrdinalIgnoreCase ) &&
clickedAt - lastDesktopShortcutClickedAt <= DesktopDoubleClickSeconds )
{
lastDesktopShortcutClickedId = null;
lastDesktopShortcutClickedAt = 0f;
ActivateDesktopShortcut( shortcutId );
return;
}
selectedDesktopShortcutIds.Clear();
selectedDesktopShortcutIds.Add( shortcutId );
lastDesktopShortcutClickedId = shortcutId;
lastDesktopShortcutClickedAt = clickedAt;
HideDesktopContextMenu();
StateHasChanged();
}
private void ActivateDesktopShortcut( string shortcutId )
{
var shortcut = GetDesktopShortcutById( shortcutId );
if ( shortcut is null || Runtime is null )
return;
if ( !string.IsNullOrWhiteSpace( shortcut.AppId ) )
{
Runtime.OpenApp( shortcut.AppId );
}
else if ( shortcut.IsDirectory )
{
Runtime.OpenApp( "system.paneexplorer", new Dictionary<string, string>
{
["path"] = shortcut.VirtualPath ?? Runtime.GetDefaultDocumentsPath()
} );
}
else if ( !string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
{
Runtime.OpenVirtualPath( shortcut.VirtualPath );
}
StateHasChanged();
}
private void OpenDesktopContextMenu( string shortcutId )
{
selectedDesktopShortcutIds.Clear();
selectedDesktopShortcutIds.Add( shortcutId );
desktopContextMenuShortcutId = shortcutId;
desktopContextMenuPosition = GetLocalMousePosition();
StateHasChanged();
}
private string GetDesktopContextMenuStyle()
{
return $"left: {desktopContextMenuPosition.x}px; top: {desktopContextMenuPosition.y}px;";
}
private void HideDesktopContextMenu()
{
desktopContextMenuShortcutId = null;
}
private void PromptRenameSelectedDesktopShortcut()
{
if ( Runtime is null || selectedDesktopShortcutIds.Count != 1 )
return;
var shortcut = GetDesktopShortcutById( selectedDesktopShortcutIds.First() );
if ( shortcut is null || !shortcut.CanRename || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
return;
HideDesktopContextMenu();
Runtime.ShowMessageBox(
new ComputerMessageBoxOptions
{
Title = "Rename Shortcut",
Message = "Enter a new file or folder name.",
Icon = "R",
HasTextInput = true,
TextInputValue = shortcut.Label,
TextInputPlaceholder = shortcut.Label,
Buttons = new[] { "Rename", "Cancel" }
},
result =>
{
if ( !result.ButtonPressed.Equals( "Rename", StringComparison.OrdinalIgnoreCase ) )
return;
var newName = result.TextValue.Trim();
if ( string.IsNullOrWhiteSpace( newName ) )
return;
PaneArchiveFileSystem.Rename( Runtime.GetArchivePath(), ParseVirtualPath( shortcut.VirtualPath ), newName );
selectedDesktopShortcutIds.Clear();
Runtime.RefreshTransientUi();
StateHasChanged();
} );
}
private void DeleteSelectedDesktopShortcuts()
{
if ( Runtime is null )
return;
foreach ( var shortcutId in selectedDesktopShortcutIds.ToArray() )
{
var shortcut = GetDesktopShortcutById( shortcutId );
if ( shortcut is null || !shortcut.CanDelete || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
continue;
Runtime.DeleteVirtualPath( shortcut.VirtualPath );
}
selectedDesktopShortcutIds.Clear();
HideDesktopContextMenu();
StateHasChanged();
}
private void RestoreSelectedDesktopShortcut()
{
if ( Runtime is null )
return;
foreach ( var shortcutId in selectedDesktopShortcutIds.ToArray() )
{
var shortcut = GetDesktopShortcutById( shortcutId );
if ( shortcut is null || !shortcut.CanRestore || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
continue;
Runtime.RestoreVirtualPath( shortcut.VirtualPath );
}
selectedDesktopShortcutIds.Clear();
HideDesktopContextMenu();
StateHasChanged();
}
private static IReadOnlyList<string> ParseVirtualPath( string virtualPath )
{
return virtualPath
.Trim()
.TrimStart( '/' )
.Split( '/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
}
private string ResolveAppTexturePath( ComputerAppDescriptor app )
{
if ( app.Id.Equals( "system.mediaplayer", StringComparison.OrdinalIgnoreCase ) )
return ResolveThemeTexturePath( "Ext_mp4" );
return ResolveThemeTexturePath( $"App_{ResolveAppTextureKey( app )}" );
}
private string ResolveArchiveItemTexturePath( PaneArchiveItem item )
{
if ( item.IsDirectory )
return ResolveThemeTexturePath( "folder" );
var extension = item.Extension.TrimStart( '.' ).ToLowerInvariant();
if ( string.IsNullOrWhiteSpace( extension ) )
return "";
return ResolveThemeTexturePath( $"Ext_{extension}" );
}
private string ResolveThemeTexturePath( string textureName )
{
if ( Computer is null || string.IsNullOrWhiteSpace( textureName ) )
return "";
var themeName = string.IsNullOrWhiteSpace( Computer.ThemeName ) ? "default" : Computer.ThemeName.Trim();
var path = $"textures/themes/{themeName}/{textureName}.png";
try
{
return FileSystem.Mounted.FileExists( path ) ? path : "";
}
catch ( Exception )
{
return "";
}
}
private static string ResolveAppTextureKey( ComputerAppDescriptor app )
{
return app.Id switch
{
"system.about" => "about",
"system.calculator" => "calculator",
"system.settings" => "controlPanel",
"system.notepad" => "notepad",
"system.paint" => "paint",
"system.paneexplorer" => "paneExplorer",
"system.ridge" => "ridge",
"system.taskmanager" => "taskManager",
_ => app.ResolvedExecutableName.EndsWith( ".exe", StringComparison.OrdinalIgnoreCase )
? app.ResolvedExecutableName[..^4]
: app.ResolvedExecutableName
};
}
private static string GetScreenSaverLogoStyle( ComputerScreenSaverState saver )
{
return $"left: {saver.LogoX}px; top: {saver.LogoY}px; width: {saver.LogoWidth}px; height: {saver.LogoHeight}px;";
}
private void EnsureAttachedPanelMatchesResolution()
{
if ( Runtime is null )
return;
var worldPanel = GameObject.Components.Get<Sandbox.WorldPanel>( FindMode.InSelf );
if ( worldPanel is null )
return;
var targetSize = new Vector2( Runtime.State.ResolutionX, Runtime.State.ResolutionY );
if ( worldPanel.PanelSize == targetSize )
return;
worldPanel.PanelSize = targetSize;
}
private sealed class DesktopShortcutItem
{
public string Id { get; set; } = "";
public int Index { get; set; }
public string Label { get; set; } = "";
public string Icon { get; set; } = "";
public string IconTexture { get; set; } = "";
public string? AppId { get; set; }
public string? VirtualPath { get; set; }
public bool IsDirectory { get; set; }
public bool CanRename { get; set; }
public bool CanDelete { get; set; }
public bool CanRestore { get; set; }
}
}