Skip to content

Latest commit

 

History

History
168 lines (135 loc) · 5.3 KB

CustomUIElement.doc.md

File metadata and controls

168 lines (135 loc) · 5.3 KB

How to implement your own UIElement

  • First off, look what UIElement actually is and what it gives to the inheritors. UIElement
  • If you're going to implement element that contains other elements try implementing Wrapper
  • If you're going to implement element that should handle pressed keys it should implement IFocusable
  • You will get better understanding how to do it if you will watch simple implementations in the source code: for example, Rectangle or Canvas
  • You can watch what role UIElement has in Core.
  • I recommend you to look at UIElement.cs source code and its protected or public members that give you some opportunities.
  • With T : UIElement implementation you should also implement IUIElementBuilder to use it.

I think if you understand everything above you will manage.

Here is my simple demo implemetation with some explanations (Actually, it does weird things but it's just an example):

using Sunnyyssh.ConsoleUI;

namespace Test;

// I recommend you to implement IFocusable's members implicitly to hide them.
// Otherwise, it may cause some safety problems.
public sealed class MyUIElement : UIElement, IFocusable 
{
    public Color Color { get; }
    private ForceTakeFocusHandler? _forceTakeFocusHandler;
    
    private ForceLoseFocusHandler? _forceLoseFocusHandler;

    protected override DrawState CreateDrawState()
    {
        // DrawStateBuilder will help us to create DrawState.
        var drawStateBuilder = new DrawStateBuilder(Width, Height); // UIElement's width and height gotten.

        drawStateBuilder.Fill(Color);
        if (IsFocused)
        {
            drawStateBuilder.Place(0, 0, "it's focused", Color.DarkMagenta, Color.White);
        }

        // return created state.
        return drawStateBuilder.ToDrawState();
    }

    public bool IsWaitingFocus { get; } = true; // It will always wait for focus.

    public bool IsFocused { get; private set; }

    void IFocusable.TakeFocus()
    {
        // IsFocused must be set to true.
        IsFocused = true;
        if (IsStateInitialized)
        {
            // If DrawState of this element is initialized it should renew it because focused state differs.
            Redraw(CreateDrawState());
        }

        Task.Delay(2000)
            .ContinueWith(_ =>
            {
                if (IsFocused)
                {
                    // If after 2 seconds it's still focused then it must lose it.
                    // It makes no sense but it's just for example.
                    _forceLoseFocusHandler?.Invoke(this);
                }
            });
    }

    void IFocusable.LoseFocus()
    {
        // IsFocused must be set to false.
        IsFocused = false;
        if (IsStateInitialized)
        {
            // If DrawState of this element is initialized it should renew it because focused state differs.
            Redraw(CreateDrawState());
        }
        
        Task.Delay(3000)
            .ContinueWith(_ =>
            {
                if (IsFocused)
                {
                    // If after 3 seconds it's still not focused then it must take it.
                    // It makes no sense but it's just for example.
                    _forceTakeFocusHandler?.Invoke(this);
                }
            });
    }

    bool IFocusable.HandlePressedKey(ConsoleKeyInfo keyInfo)
    {
        // It will lose focus every time the key is pressed.
        return false;
    }

    event ForceTakeFocusHandler? IFocusable.ForceTakeFocus
    {
        add => _forceTakeFocusHandler += value;
        remove => _forceTakeFocusHandler -= value;
    }

    event ForceLoseFocusHandler? IFocusable.ForceLoseFocus
    {
        add => _forceLoseFocusHandler += value;
        remove => _forceLoseFocusHandler -= value;
    }

    internal MyUIElement(int width, int height, OverlappingPriority priority, Color color) : base(width, height, priority)
    {
        Color = color;
    }
}

// It will build element.
public sealed class MyUIElementBuilder : IUIElementBuilder<MyUIElement>
{
    public Size Size { get; }

    public OverlappingPriority OverlappingPriority { get; init; } = OverlappingPriority.Medium;
    public Color Color { get; init; }

    public MyUIElement Build(UIElementBuildArgs args)
    {
        // Here could be resolving some properties.
        return new MyUIElement(args.Width, args.Height, OverlappingPriority, Color);
    }

    UIElement IUIElementBuilder.Build(UIElementBuildArgs args) => Build(args);

    public MyUIElementBuilder(Size size)
    {
        Size = size;
    }
}

It can be used in this way:

using Sunnyyssh.ConsoleUI;
using Test;

var appBuilder = new ApplicationBuilder(
    new ApplicationSettings() { DefaultForeground = Color.Gray }); // app builder init.

var elementBuilder1 = new MyUIElementBuilder(new Size(0.5, 0.5))
{
    Color = Color.DarkBlue,
    OverlappingPriority = OverlappingPriority.Low
};
var elementBuilder2 = new MyUIElementBuilder(new Size(0.3, 0.3))
{
    Color = Color.DarkRed,
    OverlappingPriority = OverlappingPriority.High,
};

appBuilder.Add(elementBuilder1, 12, 3)
    .Add(elementBuilder2, 20, 4);

appBuilder.Build().Run();

This code runs like this:
demo