2019-08-13 23:54:29 +02:00
using System ;
using Microsoft.Xna.Framework ;
using MLEM.Ui.Style ;
namespace MLEM.Ui.Elements {
2020-05-22 17:02:24 +02:00
/// <summary>
/// A tooltip element for use inside of a <see cref="UiSystem"/>.
/// A tooltip is a <see cref="Panel"/> with a custom cursor that always follows the position of the mouse.
/// Tooltips can easily be configured to be hooked onto an element, causing them to appear when it is moused, and disappear when it is not moused anymore.
/// </summary>
2019-12-31 14:08:13 +01:00
public class Tooltip : Panel {
2019-08-13 23:54:29 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// The offset that this tooltip's top left corner should have from the mouse position
/// </summary>
2019-12-31 14:08:13 +01:00
public StyleProp < Vector2 > MouseOffset ;
2020-05-22 17:02:24 +02:00
/// <summary>
2020-12-05 16:42:21 +01:00
/// The amount of time that the mouse has to be over an element before it appears
/// </summary>
public StyleProp < TimeSpan > Delay ;
/// <summary>
2020-05-22 17:02:24 +02:00
/// The paragraph of text that this tooltip displays
/// </summary>
2019-12-28 14:23:40 +01:00
public Paragraph Paragraph ;
2019-08-13 23:54:29 +02:00
2020-12-05 16:42:21 +01:00
private TimeSpan delayCountdown ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Creates a new tooltip with the given settings
/// </summary>
/// <param name="width">The width of the tooltip</param>
/// <param name="text">The text to display on the tooltip</param>
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
2019-12-28 14:23:40 +01:00
public Tooltip ( float width , string text = null , Element elementToHover = null ) :
2019-12-31 14:08:13 +01:00
base ( Anchor . TopLeft , Vector2 . One , Vector2 . Zero ) {
2021-01-11 00:09:29 +01:00
if ( text ! = null )
2019-12-28 14:23:40 +01:00
this . Paragraph = this . AddChild ( new Paragraph ( Anchor . TopLeft , width , text ) ) ;
2021-01-11 00:09:29 +01:00
this . Init ( elementToHover ) ;
}
2019-08-24 20:45:40 +02:00
2021-01-11 00:09:29 +01:00
/// <summary>
/// Creates a new tooltip with the given settings
/// </summary>
/// <param name="width">The width of the tooltip</param>
/// <param name="textCallback">The text to display on the tooltip</param>
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
public Tooltip ( float width , Paragraph . TextCallback textCallback , Element elementToHover = null ) :
base ( Anchor . TopLeft , Vector2 . One , Vector2 . Zero ) {
this . Paragraph = this . AddChild ( new Paragraph ( Anchor . TopLeft , width , textCallback ) ) ;
this . Init ( elementToHover ) ;
2019-08-13 23:54:29 +02:00
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc />
2019-08-13 23:54:29 +02:00
public override void Update ( GameTime time ) {
base . Update ( time ) ;
2019-12-28 14:23:40 +01:00
this . SnapPositionToMouse ( ) ;
2020-12-05 16:42:21 +01:00
if ( this . IsHidden & & this . delayCountdown > TimeSpan . Zero ) {
this . delayCountdown - = time . ElapsedGameTime ;
if ( this . delayCountdown < = TimeSpan . Zero )
this . IsHidden = false ;
}
2019-12-28 14:23:40 +01:00
}
2019-08-15 14:59:15 +02:00
2020-05-22 17:02:24 +02:00
/// <inheritdoc />
2019-12-28 14:23:40 +01:00
public override void ForceUpdateArea ( ) {
if ( this . Parent ! = null )
throw new NotSupportedException ( $"A tooltip shouldn't be the child of another element ({this.Parent})" ) ;
base . ForceUpdateArea ( ) ;
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc />
2019-12-28 14:23:40 +01:00
protected override void InitStyle ( UiStyle style ) {
base . InitStyle ( style ) ;
2019-12-31 14:08:13 +01:00
this . Texture . SetFromStyle ( style . TooltipBackground ) ;
this . MouseOffset . SetFromStyle ( style . TooltipOffset ) ;
2020-12-05 16:42:21 +01:00
this . Delay . SetFromStyle ( style . TooltipDelay ) ;
2020-10-31 17:55:46 +01:00
// we can't set from style here since it's a different element
this . Paragraph ? . TextColor . Set ( style . TooltipTextColor ) ;
2019-12-28 14:23:40 +01:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Causes this tooltip's position to be snapped to the mouse position.
/// </summary>
2019-12-28 14:23:40 +01:00
public void SnapPositionToMouse ( ) {
2021-03-29 02:15:17 +02:00
var viewport = this . System . Viewport ;
2020-11-17 21:05:48 +01:00
var offset = ( this . Input . MousePosition . ToVector2 ( ) + this . MouseOffset . Value ) / this . Scale ;
2021-03-29 02:15:17 +02:00
if ( offset . X < viewport . X )
offset . X = viewport . X ;
if ( offset . Y < viewport . Y )
offset . Y = viewport . Y ;
if ( offset . X * this . Scale + this . Area . Width > = viewport . Right )
offset . X = ( viewport . Right - this . Area . Width ) / this . Scale ;
if ( offset . Y * this . Scale + this . Area . Height > = viewport . Bottom )
offset . Y = ( viewport . Bottom - this . Area . Height ) / this . Scale ;
2019-08-15 14:59:15 +02:00
this . PositionOffset = offset ;
2019-08-13 23:54:29 +02:00
}
2019-12-29 15:18:49 +01:00
2020-12-05 22:42:10 +01:00
/// <summary>
/// Adds this tooltip to the given <see cref="UiSystem"/> and either displays it directly or starts the <see cref="Delay"/> timer.
/// </summary>
/// <param name="system">The system to add this tooltip to</param>
/// <param name="name">The name that this tooltip should use</param>
public void Display ( UiSystem system , string name ) {
system . Add ( name , this ) ;
if ( this . Delay < = TimeSpan . Zero ) {
this . IsHidden = false ;
this . SnapPositionToMouse ( ) ;
} else {
this . IsHidden = true ;
this . delayCountdown = this . Delay ;
}
}
/// <summary>
/// Removes this tooltip from its <see cref="UiSystem"/> and resets the <see cref="Delay"/> timer, if there is one.
/// </summary>
public void Remove ( ) {
this . delayCountdown = TimeSpan . Zero ;
if ( this . System ! = null )
this . System . Remove ( this . Root . Name ) ;
}
2021-02-18 04:16:17 +01:00
/// <summary>
/// Adds this tooltip instance to the given <see cref="Element"/>, making it display when it is moused over
/// </summary>
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
public void AddToElement ( Element elementToHover ) {
elementToHover . OnMouseEnter + = element = > {
// only display the tooltip if there is anything in it
2021-07-18 22:18:46 +02:00
foreach ( var c in this . Children ) {
if ( ! c . IsHidden ) {
this . Display ( element . System , element . GetType ( ) . Name + "Tooltip" ) ;
break ;
}
}
2021-02-18 04:16:17 +01:00
} ;
elementToHover . OnMouseExit + = element = > this . Remove ( ) ;
}
2021-01-11 00:09:29 +01:00
private void Init ( Element elementToHover ) {
2021-01-20 06:41:52 +01:00
if ( this . Paragraph ! = null )
this . Paragraph . AutoAdjustWidth = true ;
2021-01-11 00:09:29 +01:00
this . SetWidthBasedOnChildren = true ;
this . SetHeightBasedOnChildren = true ;
this . ChildPadding = new Vector2 ( 2 ) ;
this . CanBeMoused = false ;
2021-02-18 04:16:17 +01:00
if ( elementToHover ! = null )
this . AddToElement ( elementToHover ) ;
2021-01-11 00:09:29 +01:00
}
2019-08-13 23:54:29 +02:00
}
}