Code/XGUI/Window.cs
using Sandbox;
using Sandbox.UI;
using System.Linq;
namespace XGUI;
public partial class Window : Panel
{
public Panel TitleBar { get; set; } = new Panel();
public Label TitleLabel { get; set; } = new Label();
public Panel TitleIcon { get; set; } = new Panel();
public Panel TitleSpacer { get; set; } = new Panel();
public Vector2 Position;
public Vector2 Size;
public Vector2 MinSize = new Vector2();
public int ZIndex;
public bool HasControls = true;
public bool HasMinimise = false;
public bool HasMaximise = false;
public bool HasClose = true;
public bool IsResizable = true;
public bool IsDraggable = true;
public Button ControlsClose { get; set; } = new Button();
public Button ControlsMinimise { get; set; } = new Button();
public Button ControlsMaximise { get; set; } = new Button();
public Window()
{
AddClass( "Panel" );
AddClass( "Window" );
Style.PointerEvents = PointerEvents.All;
Style.Position = PositionMode.Absolute;
Style.FlexDirection = FlexDirection.Column;
}
protected override void OnAfterTreeRender( bool firstTime )
{
base.OnAfterTreeRender( firstTime );
if ( firstTime )
{
CreateTitleBar();
this.AddEventListener( "onmousedown", ResizeDown );
this.AddEventListener( "onmouseup", ResizeUp );
this.AddEventListener( "onmousemove", ResizeMove );
OverrideButtons();
}
SetChildIndex( TitleBar, 0 );
}
// theres no way by default to make buttons focusable so hack it in
public void OverrideButtons()
{
foreach ( Panel button in Descendants.OfType<Button>() )
{
var focusallowed = button.GetAttribute( "focus", "0" );
if ( focusallowed == "1" )
{
button.AcceptsFocus = true;
}
var autofocus = button.GetAttribute( "autofocus", "0" );
if ( autofocus == "1" )
{
button.Focus();
}
}
}
Panel LastFocus;
public void FocusUpdate()
{
if ( InputFocus.Current == null || InputFocus.Current is Window ) return;
if ( InputFocus.Current != LastFocus )
{
if ( LastFocus != null )
{
LastFocus.SetClass( "focus", false );
}
LastFocus = InputFocus.Current;
LastFocus.SetClass( "focus", true );
}
}
public void CreateTitleBar()
{
/*
<style>
Window {
pointer-events:all;
position: absolute;
flex-direction:column;
.TitleBar {
.TitleIcon {
}
.TitleSpacer {
flex-grow: 1;
background-color: rgba(0,0,0,1);
}
.Control {
}
}
}
</style>
<div class="TitleBar" @ref=TitleBar>
<div class="TitleIcon" @ref=TitleIcon></div>
<div>@Title</div>
<div class="TitleSpacer" onmousedown=@DragBarDown onmouseup=@DragBarUp onmousemove=@Drag></div>
<button class="Control" @ref=ControlsClose onclick=@Close>X</button>
</div>
*/
TitleBar.AddClass( "TitleBar" );
TitleIcon.AddClass( "TitleIcon" );
TitleLabel.AddClass( "TitleLabel" );
TitleSpacer.AddClass( "TitleSpacer" );
ControlsClose.AddClass( "Control" );
ControlsClose.AddClass( "CloseButton" );
ControlsMinimise.AddClass( "Control" );
ControlsMinimise.AddClass( "MinimiseButton" );
ControlsMaximise.AddClass( "Control" );
ControlsMaximise.AddClass( "MaximiseButton" );
AddChild( TitleBar );
TitleBar.AddChild( TitleIcon );
TitleBar.AddChild( TitleLabel );
TitleBar.AddChild( TitleSpacer );
TitleBar.Style.ZIndex = 100;
if ( HasMinimise ) TitleBar.AddChild( ControlsMinimise );
if ( HasMaximise ) TitleBar.AddChild( ControlsMaximise );
if ( HasClose ) TitleBar.AddChild( ControlsClose );
TitleSpacer.AddEventListener( "onmousedown", DragBarDown );
TitleSpacer.AddEventListener( "onmouseup", DragBarUp );
TitleSpacer.AddEventListener( "onmousedrag", Drag );
TitleSpacer.Style.FlexGrow = 1;
// The "0", "1" and "r" are for the marlett/webdings font
// Ideally i want these to be set from the theming CSS space
// but unfortunately s&box does not support the css content property
ControlsMinimise.AddEventListener( "onclick", Minimise );
ControlsMinimise.Text = "0";
ControlsMaximise.AddEventListener( "onclick", Maximise );
ControlsMaximise.Text = "1";
ControlsClose.AddEventListener( "onclick", Close );
ControlsClose.Text = "r";
}
bool Minimised = false;
Vector2 PreMinimisedSize;
Vector2 PreMinimisedPos;
public void Minimise()
{
if ( !Minimised )
{
PreMinimisedSize = Box.Rect.Size;
PreMinimisedPos = Position;
Position.x = 0;
var newheight = TitleBar.Box.Rect.Size.y + ((TitleBar.Box.Rect.Position.y - Box.Rect.Position.y) * 2);
Log.Info( newheight );
Position.y = Parent.Box.Rect.Size.y - newheight;
Style.Height = newheight;
Style.Width = 128;
Minimised = true;
}
else
{
Minimised = false;
Style.Width = PreMinimisedSize.x;
Style.Height = PreMinimisedSize.y;
Position = PreMinimisedPos;
}
Log.Info( "minimise" );
}
bool Maximised = false;
Vector2 PreMaximisedSize;
Vector2 PreMaximisedPos;
public void Maximise()
{
if ( !Maximised )
{
PreMaximisedSize = Box.Rect.Size;
PreMaximisedPos = Position;
Position = 0;
Style.Height = Parent.Box.Rect.Size.y;
Style.Width = Parent.Box.Rect.Size.x;
Maximised = true;
}
else
{
Maximised = false;
Style.Width = PreMaximisedSize.x;
Style.Height = PreMaximisedSize.y;
Position = PreMaximisedPos;
}
Log.Info( "maximise" );
}
public void Close()
{
Log.Info( "close" );
Delete();
}
public override void Tick()
{
base.Tick();
// Todo - use something nicer that doesn't rely on this being named Attack1
if ( Input.Released( "Attack1" ) )
{
Dragging = false;
ResizeUp();
}
Drag();
if ( Style.Left == null )
{
Style.Left = 0;
Style.Top = 0;
}
Style.Position = PositionMode.Absolute;
Style.Left = Position.x * ScaleFromScreen;
Style.Top = Position.y * ScaleFromScreen;
Style.ZIndex = (Parent.ChildrenCount - Parent.GetChildIndex( this )) * 10;
SetClass( "unfocused", !this.HasFocus );
FocusUpdate();
}
public void FocusWindow()
{
AcceptsFocus = true;
if ( !HasFocus )
Focus();
Parent.SetChildIndex( this, 0 );
}
Vector2 MousePos()
{
if ( FindRootPanel().IsWorldPanel && Game.ActiveScene.IsValid() && Game.ActiveScene.IsValid() )
{
Ray ray = Game.ActiveScene.Camera.ScreenPixelToRay( Mouse.Position );
FindRootPanel().RayToLocalPosition( ray, out var pos, out var distance );
return pos;
}
return FindRootPanel().MousePosition;
}
// -------------
// Dragging
// -------------
bool Dragging = false;
float xoff = 0;
float yoff = 0;
public void Drag()
{
if ( !Dragging ) return;
var mousePos = MousePos();
Position.x = ((mousePos.x) - xoff);
Position.y = ((mousePos.y) - yoff);
// Window edge to edge snapping
foreach ( Window window in Parent.Children.OfType<Window>() )
{
var snapDistance = 10;
var window1leftpos = Position.x;
var window1rightpos = Position.x + Box.Rect.Size.x;
var window1uppos = Position.y;
var window1downpos = Position.y + Box.Rect.Size.y;
var window2leftpos = window.Position.x;
var window2rightpos = window.Position.x + window.Box.Rect.Size.x;
var window2uppos = window.Position.y;
var window2downpos = window.Position.y + window.Box.Rect.Size.y;
if ( !(window1downpos < window2uppos || window1uppos > window2downpos) )
{
if ( window1leftpos.AlmostEqual( window2rightpos, snapDistance ) ) Position.x -= window1leftpos - window2rightpos;
if ( window1rightpos.AlmostEqual( window2leftpos, snapDistance ) ) Position.x += window2leftpos - window1rightpos;
}
if ( !(window1rightpos < window2leftpos || window1leftpos > window2rightpos) )
{
if ( window1uppos.AlmostEqual( window2downpos, snapDistance ) ) Position.y -= window1uppos - window2downpos;
if ( window1downpos.AlmostEqual( window2uppos, snapDistance ) ) Position.y += window2uppos - window1downpos;
}
}
}
public void DragBarDown()
{
if ( !IsDraggable ) return;
var mousePos = MousePos();
xoff = (float)((mousePos.x) - Box.Rect.Left);
yoff = (float)((mousePos.y) - Box.Rect.Top);
Dragging = true;
}
public void DragBarUp()
{
Dragging = false;
}
// -------------
// -------------
// Focusing
// -------------
protected override void OnMouseDown( MousePanelEvent e )
{
FocusWindow();
//Parent.SortChildren( x => x.HasFocus ? 1 : 0 );
base.OnMouseDown( e );
}
// -------------
// Resizing
// -------------
// I feel like everything about resizing sucks.
internal bool draggingR = false;
internal bool draggingL = false;
internal bool draggingT = false;
internal bool draggingB = false;
public void ResizeDown()
{
if ( !IsResizable ) return;
// TODO FIXME: Don't resize if were dragging a window by the title bar!!
var Distance = 5;
var mousePos = FindRootPanel().MousePosition;
if ( mousePos.y.AlmostEqual( this.Box.Rect.Bottom, Distance ) ) draggingB = true;
if ( mousePos.x.AlmostEqual( this.Box.Rect.Right, Distance ) ) draggingR = true;
if ( mousePos.y.AlmostEqual( this.Box.Rect.Top, Distance ) ) draggingT = true;
if ( mousePos.x.AlmostEqual( this.Box.Rect.Left, Distance ) ) draggingL = true;
//draggingT = true;
//draggingL = true;
xoff1 = (float)((FindRootPanel().MousePosition.x) - this.Box.Rect.Right);
yoff1 = (float)((FindRootPanel().MousePosition.y) - this.Box.Rect.Bottom);
xoff2 = (float)((FindRootPanel().MousePosition.x) - this.Box.Rect.Left);
yoff2 = (float)((FindRootPanel().MousePosition.y) - this.Box.Rect.Top);
}
public void ResizeUp()
{
draggingB = false;
draggingR = false;
draggingT = false;
draggingL = false;
}
internal float xoff1 = 0;
internal float yoff1 = 0;
internal float xoff2 = 0;
internal float yoff2 = 0;
public void ResizeMove()
{
var mousePos = MousePos();
if ( IsResizable )
{
var Distance = 5;
var almostbottom = mousePos.y.AlmostEqual( this.Box.Rect.Bottom, Distance );
var almostright = mousePos.x.AlmostEqual( this.Box.Rect.Right, Distance );
var almosttop = mousePos.y.AlmostEqual( this.Box.Rect.Top, Distance );
var almostleft = mousePos.x.AlmostEqual( this.Box.Rect.Left, Distance );
if ( (almostleft && almostbottom) || (draggingL && draggingB) ) Style.Cursor = "nesw-resize";
else if ( (almostright && almosttop) || (draggingR && draggingT) ) Style.Cursor = "nesw-resize";
else if ( (almostright && almostbottom) || (draggingR && draggingB) ) Style.Cursor = "nwse-resize";
else if ( (almostleft && almosttop) || (draggingL && draggingT) ) Style.Cursor = "nwse-resize";
else if ( almostbottom || draggingB ) Style.Cursor = "ns-resize";
else if ( almostright || draggingR ) Style.Cursor = "ew-resize";
else if ( almosttop || draggingT ) Style.Cursor = "ns-resize";
else if ( almostleft || draggingL ) Style.Cursor = "ew-resize";
else Style.Cursor = "unset";
}
/*if ( Mouse )
{
ResizeUp( e );
}*/
// This sucks.
if ( draggingB )
{
//Parent.Style.Width = (FindRootPanel().MousePosition.x - Parent.Box.Rect.Left) - xoff;
var newheight = (mousePos.y - Box.Rect.Top) - yoff1;
if ( newheight > MinSize.y )
{
Style.Height = newheight;
}
}
if ( draggingR )
{
var newwidth = (mousePos.x - Box.Rect.Left) - xoff1;
if ( newwidth > MinSize.x )
{
Style.Width = newwidth;
}
//Parent.Style.Height = (FindRootPanel().MousePosition.y - Parent.Box.Rect.Top) - yoff;
}
if ( draggingT )
{
var newheight = Box.Rect.Height - ((mousePos.y - yoff2) - Box.Rect.Top);
if ( newheight > MinSize.y )
{
Style.Height = newheight;
Position.y = mousePos.y - yoff2;
}
}
if ( draggingL )
{
var newwidth = Box.Rect.Width - ((mousePos.x - xoff2) - Box.Rect.Left);
if ( newwidth > MinSize.x )
{
Style.Width = newwidth;
Position.x = mousePos.x - xoff2;
}
}
}
// -------------
public override void SetProperty( string name, string value )
{
switch ( name )
{
case "title":
{
TitleLabel.Text = value;
return;
}
case "hasminimise":
{
HasMinimise = bool.Parse( value );
return;
}
case "hasmaximise":
{
HasMaximise = bool.Parse( value );
return;
}
case "hasclose":
{
HasClose = bool.Parse( value );
return;
}
case "isresizable":
{
IsResizable = bool.Parse( value );
return;
}
case "isdraggable":
{
IsDraggable = bool.Parse( value );
return;
}
case "width":
{
Style.Width = Length.Parse( value );
return;
}
case "height":
{
Style.Height = Length.Parse( value );
return;
}
case "x":
{
Position.x = Length.Parse( value ).Value.Value;
return;
}
case "y":
{
Position.y = Length.Parse( value ).Value.Value;
return;
}
case "minwidth":
{
MinSize.x = Length.Parse( value ).Value.Value;
return;
}
case "minheight":
{
MinSize.y = Length.Parse( value ).Value.Value;
return;
}
}
}
}