Better splitter behavior for HTMLayout
The implementation of the splitter behavior which comes with HTMLayout, and its .NET wrapper in Nabu, suffers from several issues: it doesn't take into account RTL layouts, and the actual resizing is done while dragging the splitter itself - which is quite costly. As a result of the latter, 2 on_size notifications were pushed for every sizing operation instead of one.
I perfected it so it uses a "ghost" element to mark the splitter's position while dragging, and the actual layout changes occur only once its released from capture. Also, it now works correctly in RTL layouts, and many graphical glitches are prevented.
The code below is meant to be used with the .NET Nabu wrapper; porting it to be native HTMLayout C++ behavior is quite easy. Some more generalization work may be required before you can implement it in your solution.
[code lang="csharp"]
using System;
using System.Collections.Generic;
using System.Text;
using Nabu.Forms.Html;
namespace MyApp.UI
{
[HtmlBehaviorName("splitter")]
public sealed class HtmlSplitterBehavior : HtmlBehaviorBase
{
#region HtmlSplitterBehavior
#region Fields
private bool _isCaptured;
private bool _isHorizontal;
private bool _isRTL;
private HtmlTag _parent;
private HtmlTag _prevSibling;
private HtmlTag _nextSibling;
private string _className;
private int _startPrevSize;
private int _startPosition;
#endregion
#region Methods
public HtmlSplitterBehavior()
: base(HtmlEventGroup.InitializationAndMouse)
{
}
#endregion
#endregion
#region HtmlBehaviorBase
protected override void OnMouse(HtmlTag sender, HtmlMouseEventArgs e)
{
if ((e.MouseState & HtmlEventMouseState.Left) != 0)
{
switch (e.Command)
{
case HtmlMouseEventCommand.Down:
{
using (HtmlTag splitter = e.Target)
{
HtmlTag parent = splitter.Parent;
HtmlTag leftSibling = splitter.PrevSibling;
HtmlTag rightSibling = splitter.NextSibling;
if ((parent != null) && (leftSibling != null) && (rightSibling != null))
{
this._parent = parent;
_isRTL = splitter.GetState(HtmlTagState.IsRTL);
this._prevSibling = leftSibling;
this._nextSibling = rightSibling;
this._isHorizontal = this._parent.GetStyle("flow") == "horizontal";
if (this._isHorizontal)
{
splitter.SetStyle(_isRTL ? "left" : "right",
e.PositionInDocument.X.ToString() + "px");
this._startPosition = e.PositionInDocument.X;
this._startPrevSize = this._prevSibling.GetLocation(HtmlTagArea.RootRelative).Width;
}
else
{
splitter.SetStyle("top", e.PositionInDocument.Y.ToString() + "px");
this._startPosition = e.PositionInDocument.Y;
this._startPrevSize = this._prevSibling.GetLocation(HtmlTagArea.RootRelative).Height;
}
splitter.SetStyle("height", this._parent.GetLocation(HtmlTagArea.BorderBox).Height + "px");
this._className = splitter.Attributes["class"];
splitter.Attributes["class"] = "splitter_ghost";
splitter.SetCapture();
this._isCaptured = true;
e.IsHandled = true;
}
}
}
break;
case HtmlMouseEventCommand.Up:
if (this._isCaptured)
{
using (HtmlTag splitter = e.Target)
{
splitter.Attributes["class"] = _className;
if (this._isHorizontal)
{
splitter.SetStyle("height", null);
int prevSize = this._startPrevSize;
if (_isRTL)
prevSize += (e.PositionInDocument.X - this._startPosition) * -1;
else
prevSize += (e.PositionInDocument.X - this._startPosition);
this._prevSibling.SetStyle(
"width",
prevSize + "px");
this._nextSibling.SetStyle(
"width",
"100%%");
}
else
{
int prevSize = this._startPrevSize + (e.PositionInDocument.Y - this._startPosition);
this._prevSibling.SetStyle(
"height",
prevSize + "px");
this._nextSibling.SetStyle(
"height",
"100%%");
}
}
this._parent = null;
this._prevSibling = null;
this._nextSibling = null;
HtmlTag.ReleaseCapture();
this._isCaptured = false;
e.IsHandled = true;
}
break;
case HtmlMouseEventCommand.Move:
if (this._isCaptured)
{
using (HtmlTag splitter = e.Target)
{
splitter.SetStyle(_isRTL ? "left" : "right", e.PositionInDocument.X.ToString() + "px");
}
e.IsHandled = true;
}
break;
}
}
base.OnMouse(sender, e);
}
#endregion
}
}
[/code]
The splitter element is defined in my CSS as follows:
[code lang="CSS"]
#mainsection > hr {padding:0;border:none;margin:0;width:0; height:*;hit-margin:3px; behavior:splitter;cursor:w-resize;}
#mainsection > hr.splitter_ghost { border: 1px solid #999; position:absolute;}
[/code]