mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
Color mixer popup
This commit is contained in:
committed by
Matt Stevens
parent
ab1ae86f88
commit
79eebb54f9
72
game/addons/base/code/UI/Controls/Color/ColorAlphaControl.cs
Normal file
72
game/addons/base/code/UI/Controls/Color/ColorAlphaControl.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace Sandbox.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A control for editing Color properties. Displays a text entry that can be edited, and a color swatch which pops up a mixer.
|
||||
/// </summary>
|
||||
public partial class ColorAlphaControl : BaseControl
|
||||
{
|
||||
readonly Panel _handle;
|
||||
|
||||
public ColorAlphaControl()
|
||||
{
|
||||
_handle = AddChild<Panel>( "handle" );
|
||||
}
|
||||
|
||||
public override void Rebuild()
|
||||
{
|
||||
if ( Property == null ) return;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
|
||||
void UpdateFromColor()
|
||||
{
|
||||
var color = Property.GetValue<Color>();
|
||||
_handle.Style.Left = Length.Percent( color.a * 100f );
|
||||
}
|
||||
|
||||
protected override void OnMouseDown( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseDown( e );
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
protected override void OnMouseMove( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseMove( e );
|
||||
|
||||
if ( !PseudoClass.HasFlag( PseudoClass.Active ) )
|
||||
return;
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
private void UpdateFromPosition( Vector2 localPosition )
|
||||
{
|
||||
// Get the bounds of the control
|
||||
var bounds = Box.Rect;
|
||||
if ( bounds.Width <= 0 || bounds.Height <= 0 ) return;
|
||||
|
||||
// Clamp position within bounds
|
||||
float x = Math.Clamp( localPosition.x, 0, bounds.Width );
|
||||
|
||||
// Calculate saturation and value from position
|
||||
var alpha = (x / bounds.Width);
|
||||
|
||||
var color = Property.GetValue<Color>();
|
||||
|
||||
// Create new color with updated saturation and value
|
||||
var newColor = color with { a = alpha };
|
||||
|
||||
// Set the property to the new color
|
||||
Property.SetValue( newColor );
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
ColorAlphaControl
|
||||
{
|
||||
gap: 0.5rem;
|
||||
flex-grow: 1;
|
||||
pointer-events: all;
|
||||
background: linear-gradient( to right, black, white );
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 1px solid #333;
|
||||
|
||||
&:hover
|
||||
{
|
||||
border: 1px solid #08f;
|
||||
}
|
||||
|
||||
&:active
|
||||
{
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.handle
|
||||
{
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 100px;
|
||||
border: 2px solid #444;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 16px #000a;
|
||||
transform: translateX( -50% );
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public partial class ColorControl : BaseControl
|
||||
public ColorControl()
|
||||
{
|
||||
_colorSwatch = AddChild<Panel>( "colorswatch" );
|
||||
_colorSwatch.AddEventListener( "onmousedown", OpenPopup );
|
||||
|
||||
_textEntry = AddChild<TextEntry>( "textentry" );
|
||||
_textEntry.OnTextEdited = OnTextEntryChanged;
|
||||
@@ -35,4 +36,12 @@ public partial class ColorControl : BaseControl
|
||||
{
|
||||
Property.SetValue( value );
|
||||
}
|
||||
|
||||
void OpenPopup()
|
||||
{
|
||||
var popup = new Popup( _colorSwatch, Popup.PositionMode.BelowLeft, 0 );
|
||||
|
||||
var picker = popup.AddChild<ColorPickerControl>();
|
||||
picker.Property = Property;
|
||||
}
|
||||
}
|
||||
83
game/addons/base/code/UI/Controls/Color/ColorHueControl.cs
Normal file
83
game/addons/base/code/UI/Controls/Color/ColorHueControl.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace Sandbox.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A control for editing Color properties. Displays a text entry that can be edited, and a color swatch which pops up a mixer.
|
||||
/// </summary>
|
||||
public partial class ColorHueControl : BaseControl
|
||||
{
|
||||
readonly Panel _handle;
|
||||
|
||||
float _hue = 0;
|
||||
|
||||
public ColorHueControl()
|
||||
{
|
||||
_handle = AddChild<Panel>( "handle" );
|
||||
}
|
||||
|
||||
public override void Rebuild()
|
||||
{
|
||||
if ( Property == null ) return;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
|
||||
void UpdateFromColor()
|
||||
{
|
||||
var color = Property.GetValue<Color>();
|
||||
var hsv = color.ToHsv();
|
||||
|
||||
if ( hsv.Saturation > 0.05f && hsv.Value > 0.05f )
|
||||
{
|
||||
_hue = hsv.Hue;
|
||||
}
|
||||
|
||||
_handle.Style.Left = Length.Percent( (_hue / 360.0f) * 100f );
|
||||
}
|
||||
|
||||
protected override void OnMouseDown( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseDown( e );
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
protected override void OnMouseMove( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseMove( e );
|
||||
|
||||
if ( !PseudoClass.HasFlag( PseudoClass.Active ) )
|
||||
return;
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
private void UpdateFromPosition( Vector2 localPosition )
|
||||
{
|
||||
// Get the bounds of the control
|
||||
var bounds = Box.Rect;
|
||||
if ( bounds.Width <= 0 || bounds.Height <= 0 ) return;
|
||||
|
||||
// Clamp position within bounds
|
||||
float x = Math.Clamp( localPosition.x, 0, bounds.Width );
|
||||
|
||||
// Calculate saturation and value from position
|
||||
_hue = (x / bounds.Width) * 360.0f;
|
||||
_hue = _hue.Clamp( 0, 360.0f - 0.001f );
|
||||
|
||||
var color = Property.GetValue<Color>().ToHsv();
|
||||
|
||||
// Create new color with updated saturation and value
|
||||
var newColor = color with { Hue = _hue };
|
||||
|
||||
// Set the property to the new color
|
||||
Property.SetValue( newColor.ToColor() );
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
ColorHueControl
|
||||
{
|
||||
gap: 0.5rem;
|
||||
flex-grow: 1;
|
||||
pointer-events: all;
|
||||
background: linear-gradient( to right, red, yellow, lime, cyan, blue, magenta, red );
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #333;
|
||||
|
||||
&:hover
|
||||
{
|
||||
border: 1px solid #08f;
|
||||
}
|
||||
|
||||
&:active
|
||||
{
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.handle
|
||||
{
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 100px;
|
||||
border: 2px solid #444;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 16px #000a;
|
||||
transform: translateX( -50% );
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Sandbox.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A control for picking a color using sliders and whatever
|
||||
/// </summary>
|
||||
public partial class ColorPickerControl : BaseControl
|
||||
{
|
||||
readonly ColorSaturationValueControl _svControl;
|
||||
readonly ColorHueControl _hueControl;
|
||||
readonly ColorAlphaControl _alphaControl;
|
||||
|
||||
public ColorPickerControl()
|
||||
{
|
||||
_svControl = AddChild<ColorSaturationValueControl>( "sv" );
|
||||
_hueControl = AddChild<ColorHueControl>( "hue" );
|
||||
_alphaControl = AddChild<ColorAlphaControl>( "alpha" );
|
||||
}
|
||||
|
||||
public override void Rebuild()
|
||||
{
|
||||
_svControl.Property = Property;
|
||||
_hueControl.Property = Property;
|
||||
_alphaControl.Property = Property;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
ColorPickerControl
|
||||
{
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
namespace Sandbox.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A control for editing Color properties. Displays a text entry that can be edited, and a color swatch which pops up a mixer.
|
||||
/// </summary>
|
||||
public partial class ColorSaturationValueControl : BaseControl
|
||||
{
|
||||
readonly Panel _handle;
|
||||
|
||||
float _hue = 0;
|
||||
|
||||
public ColorSaturationValueControl()
|
||||
{
|
||||
_handle = AddChild<Panel>( "handle" );
|
||||
|
||||
AddChild<Panel>( "gradient" );
|
||||
}
|
||||
|
||||
public override void Rebuild()
|
||||
{
|
||||
if ( Property == null ) return;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
|
||||
void UpdateFromColor()
|
||||
{
|
||||
var color = Property.GetValue<Color>();
|
||||
var hsv = color.ToHsv();
|
||||
|
||||
if ( hsv.Saturation > 0.05f && hsv.Value > 0.05f )
|
||||
{
|
||||
_hue = color.ToHsv().Hue;
|
||||
}
|
||||
|
||||
_handle.Style.Left = Length.Percent( hsv.Saturation * 100f );
|
||||
_handle.Style.Top = Length.Percent( (1 - hsv.Value) * 100f );
|
||||
_handle.Style.BackgroundColor = color;
|
||||
|
||||
Style.BackgroundColor = new ColorHsv( _hue, 1f, 1f );
|
||||
}
|
||||
|
||||
protected override void OnMouseDown( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseDown( e );
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
protected override void OnMouseMove( MousePanelEvent e )
|
||||
{
|
||||
base.OnMouseMove( e );
|
||||
|
||||
if ( !PseudoClass.HasFlag( PseudoClass.Active ) )
|
||||
return;
|
||||
|
||||
UpdateFromPosition( e.LocalPosition );
|
||||
}
|
||||
|
||||
private void UpdateFromPosition( Vector2 localPosition )
|
||||
{
|
||||
// Get the bounds of the control
|
||||
var bounds = Box.Rect;
|
||||
if ( bounds.Width <= 0 || bounds.Height <= 0 ) return;
|
||||
|
||||
// Clamp position within bounds
|
||||
float x = Math.Clamp( localPosition.x, 0, bounds.Width );
|
||||
float y = Math.Clamp( localPosition.y, 0, bounds.Height );
|
||||
|
||||
// Calculate saturation and value from position
|
||||
float saturation = x / bounds.Width;
|
||||
float value = 1f - (y / bounds.Height);
|
||||
|
||||
// Create new color with updated saturation and value
|
||||
var newColor = new ColorHsv( _hue, saturation, value ).ToColor();
|
||||
|
||||
// Set the property to the new color
|
||||
Property.SetValue( newColor );
|
||||
|
||||
UpdateFromColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
ColorSaturationValueControl
|
||||
{
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
background-color: red;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #333;
|
||||
|
||||
&:hover
|
||||
{
|
||||
border: 1px solid #08f;
|
||||
}
|
||||
|
||||
&:active
|
||||
{
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.handle
|
||||
{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100px;
|
||||
border: 2px solid #444;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 16px #000a;
|
||||
transform: translateX( -50% ) translateY( -50% );
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.gradient
|
||||
{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );
|
||||
|
||||
&:after
|
||||
{
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,354 +1,351 @@
|
||||
using Sandbox.UI.Construct;
|
||||
namespace Sandbox.UI;
|
||||
|
||||
namespace Sandbox.UI
|
||||
public partial class Popup : BasePopup
|
||||
{
|
||||
public partial class Popup : BasePopup
|
||||
/// <summary>
|
||||
/// Which panel triggered this popup. Set by <see cref="SetPositioning"/> or the constructor.
|
||||
/// </summary>
|
||||
public Panel PopupSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected option in the popup. Used internally for keyboard navigation.
|
||||
/// </summary>
|
||||
public Panel SelectedChild { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Positioning mode for this popup.
|
||||
/// </summary>
|
||||
public PositionMode Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset away from <see cref="PopupSource"/> based on <see cref="Position"/>.
|
||||
/// </summary>
|
||||
public float PopupSourceOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, will close this popup when the <see cref="PopupSource"/> is hidden.
|
||||
/// </summary>
|
||||
public bool CloseWhenParentIsHidden { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Dictates where a <see cref="Popup"/> is positioned.
|
||||
/// </summary>
|
||||
public enum PositionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Which panel triggered this popup. Set by <see cref="SetPositioning"/> or the constructor.
|
||||
/// To the left of the source panel, centered.
|
||||
/// </summary>
|
||||
public Panel PopupSource { get; set; }
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected option in the popup. Used internally for keyboard navigation.
|
||||
/// To the left of the source panel, aligned to the bottom.
|
||||
/// </summary>
|
||||
public Panel SelectedChild { get; set; }
|
||||
LeftBottom,
|
||||
|
||||
/// <summary>
|
||||
/// Positioning mode for this popup.
|
||||
/// Above the source panel, aligned to the left.
|
||||
/// </summary>
|
||||
public PositionMode Position { get; set; }
|
||||
AboveLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Offset away from <see cref="PopupSource"/> based on <see cref="Position"/>.
|
||||
/// Below the source panel, aliging on the left. Do not stretch to size of <see cref="Popup.PopupSource"/>.
|
||||
/// </summary>
|
||||
public float PopupSourceOffset { get; set; }
|
||||
BelowLeft,
|
||||
|
||||
/// <summary>
|
||||
/// If true, will close this popup when the <see cref="PopupSource"/> is hidden.
|
||||
/// Below the source panel, centered horizontally.
|
||||
/// </summary>
|
||||
public bool CloseWhenParentIsHidden { get; set; } = false;
|
||||
BelowCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Dictates where a <see cref="Popup"/> is positioned.
|
||||
/// Below the source panel, stretch to the width of the <see cref="Popup.PopupSource"/>.
|
||||
/// </summary>
|
||||
public enum PositionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// To the left of the source panel, centered.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// To the left of the source panel, aligned to the bottom.
|
||||
/// </summary>
|
||||
LeftBottom,
|
||||
|
||||
/// <summary>
|
||||
/// Above the source panel, aligned to the left.
|
||||
/// </summary>
|
||||
AboveLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Below the source panel, aliging on the left. Do not stretch to size of <see cref="Popup.PopupSource"/>.
|
||||
/// </summary>
|
||||
BelowLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Below the source panel, centered horizontally.
|
||||
/// </summary>
|
||||
BelowCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Below the source panel, stretch to the width of the <see cref="Popup.PopupSource"/>.
|
||||
/// </summary>
|
||||
BelowStretch,
|
||||
|
||||
/// <summary>
|
||||
/// Above, centered
|
||||
/// </summary>
|
||||
AboveCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Position where the mouse cursor is currently
|
||||
/// </summary>
|
||||
UnderMouse
|
||||
}
|
||||
|
||||
public Popup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetPositioning"/>
|
||||
public Popup( Panel sourcePanel, PositionMode position, float offset )
|
||||
{
|
||||
SetPositioning( sourcePanel, position, offset );
|
||||
}
|
||||
BelowStretch,
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="PopupSource"/>, <see cref="Position"/> and <see cref="PopupSourceOffset"/>.
|
||||
/// Applies relevant CSS classes.
|
||||
/// Above, centered
|
||||
/// </summary>
|
||||
/// <param name="sourcePanel">Which panel triggered this popup.</param>
|
||||
/// <param name="position">Desired positioning mode.</param>
|
||||
/// <param name="offset">Offset away from the <paramref name="sourcePanel"/>.</param>
|
||||
public void SetPositioning( Panel sourcePanel, PositionMode position, float offset )
|
||||
{
|
||||
Parent = sourcePanel.FindPopupPanel();
|
||||
PopupSource = sourcePanel;
|
||||
Position = position;
|
||||
PopupSourceOffset = offset;
|
||||
|
||||
AddClass( "popup-panel" );
|
||||
PositionMe( true );
|
||||
|
||||
switch ( Position )
|
||||
{
|
||||
case PositionMode.Left:
|
||||
AddClass( "left" );
|
||||
break;
|
||||
|
||||
case PositionMode.LeftBottom:
|
||||
AddClass( "left-bottom" );
|
||||
break;
|
||||
|
||||
case PositionMode.AboveLeft:
|
||||
AddClass( "above-left" );
|
||||
break;
|
||||
|
||||
case PositionMode.AboveCenter:
|
||||
AddClass( "above-center" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowLeft:
|
||||
AddClass( "below-left" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowCenter:
|
||||
AddClass( "below-center" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowStretch:
|
||||
AddClass( "below-stretch" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
AboveCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Header panel that holds <see cref="TitleLabel"/> and <see cref="IconPanel"/>.
|
||||
/// Position where the mouse cursor is currently
|
||||
/// </summary>
|
||||
protected Panel Header;
|
||||
UnderMouse
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Label that dispalys <see cref="Title"/>.
|
||||
/// </summary>
|
||||
protected Label TitleLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Panel that dispalys <see cref="Icon"/>.
|
||||
/// </summary>
|
||||
protected IconPanel IconPanel;
|
||||
|
||||
void CreateHeader()
|
||||
{
|
||||
if ( Header.IsValid() ) return;
|
||||
|
||||
Header = Add.Panel( "header" );
|
||||
|
||||
IconPanel = Header.Add.Icon( null );
|
||||
TitleLabel = Header.Add.Label( null, "title" );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, will add an unselectable header with given text and <see cref="Icon"/>.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => TitleLabel?.Text;
|
||||
set
|
||||
{
|
||||
CreateHeader();
|
||||
TitleLabel.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, will add an unselectable header with given icon and <see cref="Title"/>.
|
||||
/// </summary>
|
||||
public string Icon
|
||||
{
|
||||
get => IconPanel?.Text;
|
||||
set
|
||||
{
|
||||
CreateHeader();
|
||||
IconPanel.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all panels, marks this one as a success and closes it.
|
||||
/// </summary>
|
||||
public void Success()
|
||||
{
|
||||
AddClass( "success" );
|
||||
Popup.CloseAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all panels, marks this one as a failure and closes it.
|
||||
/// </summary>
|
||||
public void Failure()
|
||||
{
|
||||
AddClass( "failure" );
|
||||
Popup.CloseAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an option to this popup with given text and click action.
|
||||
/// </summary>
|
||||
public Panel AddOption( string text, Action action = null )
|
||||
{
|
||||
return AddChild( new Button( text, () =>
|
||||
{
|
||||
CloseAll();
|
||||
action?.Invoke();
|
||||
} ) );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an option to this popup with given text, icon and click action.
|
||||
/// </summary>
|
||||
public Panel AddOption( string text, string icon, Action action = null )
|
||||
{
|
||||
return AddChild( new Button( text, icon, () => { CloseAll(); action?.Invoke(); } ) );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move selection in given direction.
|
||||
/// </summary>
|
||||
/// <param name="dir">Positive numbers move selection downwards, negative - upwards.</param>
|
||||
public void MoveSelection( int dir )
|
||||
{
|
||||
var currentIndex = GetChildIndex( SelectedChild );
|
||||
|
||||
if ( currentIndex >= 0 ) currentIndex += dir;
|
||||
else if ( currentIndex < 0 ) currentIndex = dir == 1 ? 0 : -1;
|
||||
|
||||
SelectedChild?.SetClass( "active", false );
|
||||
SelectedChild = GetChild( currentIndex, true );
|
||||
SelectedChild?.SetClass( "active", true );
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
if ( CloseWhenParentIsHidden && !PopupSource.IsValid() )
|
||||
{
|
||||
Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
PositionMe( false );
|
||||
}
|
||||
|
||||
public override void OnLayout( ref Rect layoutRect )
|
||||
{
|
||||
var padding = 10;
|
||||
var h = Screen.Height - padding;
|
||||
var w = Screen.Width - padding;
|
||||
|
||||
if ( layoutRect.Bottom > h )
|
||||
{
|
||||
layoutRect.Top -= layoutRect.Bottom - h;
|
||||
layoutRect.Bottom -= layoutRect.Bottom - h;
|
||||
}
|
||||
|
||||
if ( layoutRect.Right > w )
|
||||
{
|
||||
layoutRect.Left -= layoutRect.Right - w;
|
||||
layoutRect.Right -= layoutRect.Right - w;
|
||||
}
|
||||
}
|
||||
|
||||
void PositionMe( bool isInitial )
|
||||
{
|
||||
var rect = PopupSource.Box.Rect * PopupSource.ScaleFromScreen;
|
||||
|
||||
var w = Screen.Width * PopupSource.ScaleFromScreen;
|
||||
var h = Screen.Height * PopupSource.ScaleFromScreen;
|
||||
|
||||
|
||||
Style.MaxHeight = Screen.Height - 50;
|
||||
|
||||
switch ( Position )
|
||||
{
|
||||
case PositionMode.Left:
|
||||
{
|
||||
Style.Left = null;
|
||||
Style.Right = ((w - rect.Left) + PopupSourceOffset);
|
||||
Style.Top = rect.Top + rect.Height * 0.5f;
|
||||
break;
|
||||
}
|
||||
case PositionMode.LeftBottom:
|
||||
{
|
||||
Style.Left = null;
|
||||
Style.Right = ((w - rect.Left) + PopupSourceOffset);
|
||||
Style.Top = null;
|
||||
Style.Bottom = (h - rect.Bottom);
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.AboveLeft:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Bottom = (Parent.Box.Rect * Parent.ScaleFromScreen).Height - rect.Top + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.AboveCenter:
|
||||
{
|
||||
Style.Left = rect.Left + rect.Width * 0.5f;
|
||||
Style.Bottom = (Parent.Box.Rect * Parent.ScaleFromScreen).Height - rect.Top + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowLeft:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowCenter:
|
||||
{
|
||||
Style.Left = rect.Center.x; // centering is done via styles
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowStretch:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Width = rect.Width;
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.UnderMouse:
|
||||
{
|
||||
if ( isInitial )
|
||||
{
|
||||
Style.Left = Mouse.Position.x * PopupSource.ScaleFromScreen;
|
||||
Style.Top = (Mouse.Position.y + PopupSourceOffset) * PopupSource.ScaleFromScreen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Style.Dirty();
|
||||
}
|
||||
public Popup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetPositioning"/>
|
||||
public Popup( Panel sourcePanel, PositionMode position, float offset )
|
||||
{
|
||||
SetPositioning( sourcePanel, position, offset );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="PopupSource"/>, <see cref="Position"/> and <see cref="PopupSourceOffset"/>.
|
||||
/// Applies relevant CSS classes.
|
||||
/// </summary>
|
||||
/// <param name="sourcePanel">Which panel triggered this popup.</param>
|
||||
/// <param name="position">Desired positioning mode.</param>
|
||||
/// <param name="offset">Offset away from the <paramref name="sourcePanel"/>.</param>
|
||||
public void SetPositioning( Panel sourcePanel, PositionMode position, float offset )
|
||||
{
|
||||
Parent = sourcePanel.FindPopupPanel();
|
||||
PopupSource = sourcePanel;
|
||||
Position = position;
|
||||
PopupSourceOffset = offset;
|
||||
|
||||
AddClass( "popup-panel" );
|
||||
PositionMe( true );
|
||||
|
||||
switch ( Position )
|
||||
{
|
||||
case PositionMode.Left:
|
||||
AddClass( "left" );
|
||||
break;
|
||||
|
||||
case PositionMode.LeftBottom:
|
||||
AddClass( "left-bottom" );
|
||||
break;
|
||||
|
||||
case PositionMode.AboveLeft:
|
||||
AddClass( "above-left" );
|
||||
break;
|
||||
|
||||
case PositionMode.AboveCenter:
|
||||
AddClass( "above-center" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowLeft:
|
||||
AddClass( "below-left" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowCenter:
|
||||
AddClass( "below-center" );
|
||||
break;
|
||||
|
||||
case PositionMode.BelowStretch:
|
||||
AddClass( "below-stretch" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Header panel that holds <see cref="TitleLabel"/> and <see cref="IconPanel"/>.
|
||||
/// </summary>
|
||||
protected Panel Header;
|
||||
|
||||
/// <summary>
|
||||
/// Label that dispalys <see cref="Title"/>.
|
||||
/// </summary>
|
||||
protected Label TitleLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Panel that dispalys <see cref="Icon"/>.
|
||||
/// </summary>
|
||||
protected IconPanel IconPanel;
|
||||
|
||||
void CreateHeader()
|
||||
{
|
||||
if ( Header.IsValid() ) return;
|
||||
|
||||
Header = Add.Panel( "header" );
|
||||
|
||||
IconPanel = Header.Add.Icon( null );
|
||||
TitleLabel = Header.Add.Label( null, "title" );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, will add an unselectable header with given text and <see cref="Icon"/>.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => TitleLabel?.Text;
|
||||
set
|
||||
{
|
||||
CreateHeader();
|
||||
TitleLabel.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, will add an unselectable header with given icon and <see cref="Title"/>.
|
||||
/// </summary>
|
||||
public string Icon
|
||||
{
|
||||
get => IconPanel?.Text;
|
||||
set
|
||||
{
|
||||
CreateHeader();
|
||||
IconPanel.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all panels, marks this one as a success and closes it.
|
||||
/// </summary>
|
||||
public void Success()
|
||||
{
|
||||
AddClass( "success" );
|
||||
Popup.CloseAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all panels, marks this one as a failure and closes it.
|
||||
/// </summary>
|
||||
public void Failure()
|
||||
{
|
||||
AddClass( "failure" );
|
||||
Popup.CloseAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an option to this popup with given text and click action.
|
||||
/// </summary>
|
||||
public Panel AddOption( string text, Action action = null )
|
||||
{
|
||||
return AddChild( new Button( text, () =>
|
||||
{
|
||||
CloseAll();
|
||||
action?.Invoke();
|
||||
} ) );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an option to this popup with given text, icon and click action.
|
||||
/// </summary>
|
||||
public Panel AddOption( string text, string icon, Action action = null )
|
||||
{
|
||||
return AddChild( new Button( text, icon, () => { CloseAll(); action?.Invoke(); } ) );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move selection in given direction.
|
||||
/// </summary>
|
||||
/// <param name="dir">Positive numbers move selection downwards, negative - upwards.</param>
|
||||
public void MoveSelection( int dir )
|
||||
{
|
||||
var currentIndex = GetChildIndex( SelectedChild );
|
||||
|
||||
if ( currentIndex >= 0 ) currentIndex += dir;
|
||||
else if ( currentIndex < 0 ) currentIndex = dir == 1 ? 0 : -1;
|
||||
|
||||
SelectedChild?.SetClass( "active", false );
|
||||
SelectedChild = GetChild( currentIndex, true );
|
||||
SelectedChild?.SetClass( "active", true );
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
if ( CloseWhenParentIsHidden && !PopupSource.IsValid() )
|
||||
{
|
||||
Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
PositionMe( false );
|
||||
}
|
||||
|
||||
public override void OnLayout( ref Rect layoutRect )
|
||||
{
|
||||
var padding = 10;
|
||||
var h = Screen.Height - padding;
|
||||
var w = Screen.Width - padding;
|
||||
|
||||
if ( layoutRect.Bottom > h )
|
||||
{
|
||||
layoutRect.Top -= layoutRect.Bottom - h;
|
||||
layoutRect.Bottom -= layoutRect.Bottom - h;
|
||||
}
|
||||
|
||||
if ( layoutRect.Right > w )
|
||||
{
|
||||
layoutRect.Left -= layoutRect.Right - w;
|
||||
layoutRect.Right -= layoutRect.Right - w;
|
||||
}
|
||||
}
|
||||
|
||||
void PositionMe( bool isInitial )
|
||||
{
|
||||
var rect = PopupSource.Box.Rect * PopupSource.ScaleFromScreen;
|
||||
|
||||
var w = Screen.Width * PopupSource.ScaleFromScreen;
|
||||
var h = Screen.Height * PopupSource.ScaleFromScreen;
|
||||
|
||||
|
||||
Style.MaxHeight = Screen.Height - 50;
|
||||
|
||||
switch ( Position )
|
||||
{
|
||||
case PositionMode.Left:
|
||||
{
|
||||
Style.Left = null;
|
||||
Style.Right = ((w - rect.Left) + PopupSourceOffset);
|
||||
Style.Top = rect.Top + rect.Height * 0.5f;
|
||||
break;
|
||||
}
|
||||
case PositionMode.LeftBottom:
|
||||
{
|
||||
Style.Left = null;
|
||||
Style.Right = ((w - rect.Left) + PopupSourceOffset);
|
||||
Style.Top = null;
|
||||
Style.Bottom = (h - rect.Bottom);
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.AboveLeft:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Bottom = (Parent.Box.Rect * Parent.ScaleFromScreen).Height - rect.Top + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.AboveCenter:
|
||||
{
|
||||
Style.Left = rect.Left + rect.Width * 0.5f;
|
||||
Style.Bottom = (Parent.Box.Rect * Parent.ScaleFromScreen).Height - rect.Top + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowLeft:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowCenter:
|
||||
{
|
||||
Style.Left = rect.Center.x; // centering is done via styles
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.BelowStretch:
|
||||
{
|
||||
Style.Left = rect.Left;
|
||||
Style.Width = rect.Width;
|
||||
Style.Top = rect.Bottom + PopupSourceOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
case PositionMode.UnderMouse:
|
||||
{
|
||||
if ( isInitial )
|
||||
{
|
||||
Style.Left = Mouse.Position.x * PopupSource.ScaleFromScreen;
|
||||
Style.Top = (Mouse.Position.y + PopupSourceOffset) * PopupSource.ScaleFromScreen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Style.Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user