2019-08-13 23:54:29 +02:00
using System ;
using Microsoft.Xna.Framework ;
2022-03-14 14:20:12 +01:00
using MLEM.Input ;
2019-08-13 23:54:29 +02:00
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 ;
2022-03-14 14:20:12 +01:00
/// <summary>
/// The position that this tooltip should be following (or snapped to) instead of the <see cref="InputHandler.ViewportMousePosition"/>.
/// If this value is unset, <see cref="InputHandler.ViewportMousePosition"/> will be used as the snap position.
/// Note that <see cref="MouseOffset"/> is still applied with this value set.
/// </summary>
public virtual Vector2 ? SnapPosition { get ; set ; }
2019-08-13 23:54:29 +02:00
2020-12-05 16:42:21 +01:00
private TimeSpan delayCountdown ;
2021-09-09 16:53:12 +02:00
private bool autoHidden ;
2020-12-05 16:42:21 +01:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Creates a new tooltip with the given settings
/// </summary>
/// <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>
2021-10-29 23:33:15 +02:00
public Tooltip ( 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 )
2021-10-29 23:33:15 +02:00
this . Paragraph = this . AddChild ( new Paragraph ( Anchor . TopLeft , 0 , 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="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>
2021-10-29 23:33:15 +02:00
public Tooltip ( Paragraph . TextCallback textCallback , Element elementToHover = null ) :
2021-01-11 00:09:29 +01:00
base ( Anchor . TopLeft , Vector2 . One , Vector2 . Zero ) {
2021-10-29 23:33:15 +02:00
this . Paragraph = this . AddChild ( new Paragraph ( Anchor . TopLeft , 0 , textCallback ) ) ;
2021-01-11 00:09:29 +01:00
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
2021-09-09 16:53:12 +02:00
if ( this . delayCountdown > TimeSpan . Zero ) {
2020-12-05 16:42:21 +01:00
this . delayCountdown - = time . ElapsedGameTime ;
2021-09-09 16:53:12 +02:00
if ( this . delayCountdown < = TimeSpan . Zero ) {
2020-12-05 16:42:21 +01:00
this . IsHidden = false ;
2021-09-09 16:53:12 +02:00
this . UpdateAutoHidden ( ) ;
}
} else {
this . UpdateAutoHidden ( ) ;
2020-12-05 16:42:21 +01:00
}
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 ( ) ;
2021-07-22 04:27:57 +02:00
this . SnapPositionToMouse ( ) ;
2019-12-28 14:23:40 +01:00
}
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 ) ;
2021-12-21 11:54:32 +01:00
this . Texture = this . Texture . OrStyle ( style . TooltipBackground ) ;
this . MouseOffset = this . MouseOffset . OrStyle ( style . TooltipOffset ) ;
this . Delay = this . Delay . OrStyle ( style . TooltipDelay ) ;
this . ChildPadding = this . ChildPadding . OrStyle ( style . TooltipChildPadding ) ;
2021-10-29 23:33:15 +02:00
if ( this . Paragraph ! = null ) {
2021-12-21 11:54:32 +01:00
this . Paragraph . TextColor = this . Paragraph . TextColor . OrStyle ( style . TooltipTextColor , 1 ) ;
2021-10-29 23:33:15 +02:00
this . Paragraph . Size = new Vector2 ( style . TooltipTextWidth , 0 ) ;
}
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 ;
2022-03-14 14:20:12 +01:00
var snapPosition = this . SnapPosition ? ? this . Input . ViewportMousePosition . ToVector2 ( ) ;
var offset = ( snapPosition + 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 ;
}
2021-09-09 16:53:12 +02:00
this . autoHidden = false ;
2020-12-05 22:42:10 +01:00
}
/// <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 ) {
2021-09-09 16:53:12 +02:00
elementToHover . OnMouseEnter + = element = > this . Display ( element . System , element . GetType ( ) . Name + "Tooltip" ) ;
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 . CanBeMoused = false ;
2021-02-18 04:16:17 +01:00
if ( elementToHover ! = null )
this . AddToElement ( elementToHover ) ;
2021-01-11 00:09:29 +01:00
}
2021-09-09 16:53:12 +02:00
private void UpdateAutoHidden ( ) {
var shouldBeHidden = true ;
foreach ( var child in this . Children ) {
if ( ! child . IsHidden ) {
shouldBeHidden = false ;
break ;
}
}
if ( this . autoHidden ! = shouldBeHidden ) {
// only auto-hide if IsHidden wasn't changed manually
if ( this . IsHidden = = this . autoHidden )
this . IsHidden = shouldBeHidden ;
this . autoHidden = shouldBeHidden ;
}
}
2019-08-13 23:54:29 +02:00
}
}