// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
using Cosmos.System;
using Cosmos.System.Graphics;
using CMLeonOS;
using CMLeonOS.Gui.ShellComponents.Dock;
using CMLeonOS.Gui.SmoothMono;
using CMLeonOS.UILib.Animations;
using System;
using System.Drawing;
namespace CMLeonOS.Gui.UILib
{
internal class AppWindow : Window
{
internal AppWindow(Process process, int x, int y, int width, int height) : base(process, x, y, width, height)
{
wm = ProcessManager.GetProcess();
decorationWindow = new Window(process, 0, -titlebarHeight, width, titlebarHeight);
wm.AddWindow(decorationWindow);
decorationWindow.RelativeTo = this;
decorationWindow.OnClick = DecorationClicked;
decorationWindow.OnDown = DecorationDown;
Icon = defaultAppIconBitmap;
RenderDecoration();
StartOpenAnimation();
}
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Close.bmp")]
private static byte[] closeBytes;
private static Bitmap closeBitmap = new Bitmap(closeBytes);
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Maximise.bmp")]
private static byte[] maximiseBytes;
private static Bitmap maximiseBitmap = new Bitmap(maximiseBytes);
/*[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Minimise.bmp")]
private static byte[] minimiseBytes;
private static Bitmap minimiseBitmap = new Bitmap(minimiseBytes);*/
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Restore.bmp")]
private static byte[] restoreBytes;
private static Bitmap restoreBitmap = new Bitmap(restoreBytes);
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Default.bmp")]
private static byte[] defaultAppIconBytes;
private static Bitmap defaultAppIconBitmap = new Bitmap(defaultAppIconBytes);
internal Action Closing;
private Bitmap _icon;
private Bitmap _smallIcon;
internal Bitmap Icon
{
get
{
return _icon;
}
set
{
_icon = value;
_smallIcon = _icon.Resize(20, 20);
RenderDecoration();
ProcessManager.GetProcess()?.UpdateWindows();
}
}
private string _title = "Window";
internal string Title
{
get
{
return _title;
}
set
{
_title = value;
RenderDecoration();
}
}
private bool _canResize = false;
internal bool CanResize
{
get
{
return _canResize;
}
set
{
_canResize = value;
RenderDecoration();
}
}
private bool _canClose = true;
internal bool CanClose
{
get
{
return _canClose;
}
set
{
_canClose = value;
RenderDecoration();
}
}
private bool _canMove = true;
internal bool CanMove
{
get
{
return _canMove;
}
set
{
_canMove = value;
RenderDecoration();
}
}
private const int titlebarHeight = 24;
private Window decorationWindow;
private WindowManager wm;
private bool maximised = false;
private bool closeAnimationRunning = false;
private int originalX;
private int originalY;
private int originalWidth;
private int originalHeight;
private void RefreshWindowTree()
{
wm.Update(this);
RenderDecoration();
foreach (Window child in wm.Windows)
{
if (child.RelativeTo == this)
{
if (child is Control control)
{
control.Render();
}
else
{
wm.Update(child);
}
}
}
}
private void StartOpenAnimation()
{
int targetY = Y;
int offsetY = Math.Min(18, Math.Max(8, Height / 12));
Move(X, targetY + offsetY, sendWMEvent: false);
MovementAnimation animation = new MovementAnimation(this)
{
From = new Rectangle(X, Y, Width, Height),
To = new Rectangle(X, targetY, Width, Height),
Duration = 12,
EasingType = EasingType.Sine,
EasingDirection = EasingDirection.Out
};
animation.Completed = RefreshWindowTree;
animation.Start();
}
private void StartCloseAnimation()
{
if (closeAnimationRunning)
{
return;
}
closeAnimationRunning = true;
int offsetY = Math.Min(18, Math.Max(8, Height / 12));
int startX = X;
int startY = Y;
int startWidth = Width;
int startHeight = Height;
MovementAnimation animation = new MovementAnimation(this)
{
From = new Rectangle(startX, startY, startWidth, startHeight),
To = new Rectangle(startX, startY + offsetY, startWidth, startHeight),
Duration = 10,
EasingType = EasingType.Sine,
EasingDirection = EasingDirection.In
};
animation.Completed = () =>
{
Closing?.Invoke();
wm.RemoveWindow(this);
closeAnimationRunning = false;
};
animation.Start();
}
private void DecorationClicked(int x, int y)
{
if (closeAnimationRunning)
{
return;
}
if (x >= Width - titlebarHeight && _canClose)
{
// Close.
StartCloseAnimation();
}
else if (x >= Width - (titlebarHeight * (_canClose ? 2 : 1)) && _canResize)
{
// Maximise / restore.
if (maximised)
{
maximised = false;
MoveAndResize(originalX, originalY, originalWidth, originalHeight, sendWMEvent: false);
decorationWindow.Resize(originalWidth, titlebarHeight, sendWMEvent: false);
UserResized?.Invoke();
ProcessManager.GetProcess().RerenderAll();
}
else
{
maximised = true;
var taskbar = ProcessManager.GetProcess();
int taskbarHeight = taskbar.GetTaskbarHeight();
var dock = ProcessManager.GetProcess();
int dockHeight = dock.GetDockHeight();
originalX = X;
originalY = Y;
originalWidth = Width;
originalHeight = Height;
MoveAndResize(
0,
taskbarHeight + titlebarHeight,
(int)wm.ScreenWidth,
(int)wm.ScreenHeight - titlebarHeight - taskbarHeight - dockHeight,
sendWMEvent: false
);
decorationWindow.Resize((int)wm.ScreenWidth, titlebarHeight, sendWMEvent: false);
UserResized?.Invoke();
ProcessManager.GetProcess().RerenderAll();
}
RenderDecoration();
}
}
private void DecorationDown(int x, int y)
{
int buttonSpace = 0;
if (_canClose)
{
buttonSpace += titlebarHeight;
}
if (_canResize)
{
buttonSpace += titlebarHeight;
}
if (x >= Width - buttonSpace || maximised || !_canMove) return;
if (closeAnimationRunning) return;
uint startMouseX = MouseManager.X;
uint startMouseY = MouseManager.Y;
int startWindowX = X;
int startWindowY = Y;
while (MouseManager.MouseState == MouseState.Left)
{
X = (int)(startWindowX + (MouseManager.X - startMouseX));
Y = (int)(startWindowY + (MouseManager.Y - startMouseY));
ProcessManager.Yield();
}
}
private void RenderDecoration()
{
decorationWindow.Clear(UITheme.WindowTitleBackground);
decorationWindow.DrawHorizontalLine(Width, 0, 0, UITheme.WindowTitleTopHighlight);
decorationWindow.DrawHorizontalLine(Width, 0, titlebarHeight - 1, UITheme.WindowTitleBottomBorder);
int buttonSpace = 0;
if (_canClose)
{
buttonSpace += titlebarHeight;
}
if (_canResize)
{
buttonSpace += titlebarHeight;
}
if (buttonSpace > 0)
{
decorationWindow.DrawFilledRectangle(Width - buttonSpace, 1, buttonSpace, titlebarHeight - 2, UITheme.WindowButtonBackground);
}
if (_smallIcon != null)
{
decorationWindow.DrawImageAlpha(_smallIcon, 3, 2);
}
int maxTitleChars = Math.Max(4, (Width - buttonSpace - 42) / FontData.Width);
string displayTitle = Title;
if (displayTitle.Length > maxTitleChars)
{
displayTitle = displayTitle.Substring(0, Math.Max(1, maxTitleChars - 1)) + "~";
}
decorationWindow.DrawString(displayTitle, UITheme.WindowTitleText, 28, 4);
if (_canClose)
{
decorationWindow.DrawImageAlpha(closeBitmap, Width - titlebarHeight, 0);
}
if (_canResize)
{
decorationWindow.DrawImageAlpha(maximised ? restoreBitmap : maximiseBitmap, Width - (titlebarHeight * (_canClose ? 2 : 1)), 0);
}
wm.Update(decorationWindow);
}
}
}