Color mixer popup

This commit is contained in:
Garry Newman
2025-11-26 14:10:47 +00:00
committed by Matt Stevens
parent ab1ae86f88
commit 79eebb54f9
11 changed files with 737 additions and 323 deletions

View 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,7 @@
ColorPickerControl
{
flex-direction: column;
flex-shrink: 0;
gap: 0.5rem;
margin: 1rem;
}

View File

@@ -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();
}
}

View File

@@ -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 ) );
}
}
}

View File

@@ -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();
}
}