using Annulus;
using Azimuth;
using System;
namespace Glass.Testbed
{
///
/// Hoisting and modality on screen: what escapes an owner's clip, what order overlays land in, and who
/// hears a click first.
///
public class OverlaysTestCase : TestCase
{
///
/// A hoisted panel that re-derives its position from its owner every tick.
///
private class Overlay : Panel
{
///
/// Where to sit relative to the owner's ClientRect.
///
public Vector OffsetFromOwner { get; set; }
public Overlay()
{
// An overlay wants all three: screen space, drawn last, clipped by nothing.
Hoisted = true;
AlwaysOnTop = true;
ClipToAncestors = false;
}
public override void Update(TimeSpan elapsed, UpdateState state)
{
base.Update(elapsed, state);
Bounds = Rect.FromPositionAndSize(state.ParentClientRect.TopLeft + OffsetFromOwner, Bounds.Size);
}
}
///
/// An Overlay that also takes first refusal on every click, and counts what it heard.
///
private class ModalOverlay : Overlay
{
public int HeardCount { get; private set; }
///
/// Whether to claim what it hears, which is the only thing that keeps input from the controls below.
///
public bool Swallow { get; set; }
public Action OnHeard;
public ModalOverlay()
{
Modal = true;
}
public override bool MouseDown(in MouseState state, MouseButtonFlags button)
{
base.MouseDown(state, button);
++HeardCount;
OnHeard?.Invoke();
return Swallow;
}
public override bool MouseUp(in MouseState state, MouseButtonFlags button)
{
base.MouseUp(state, button);
return Swallow;
}
public override bool MouseMoveStart(in MouseState state)
{
base.MouseMoveStart(state);
return Swallow;
}
public override bool MouseMove(in MouseState state)
{
base.MouseMove(state);
return Swallow;
}
public override bool MouseMoveEnd(in MouseState state)
{
base.MouseMoveEnd(state);
return Swallow;
}
public override bool Click(in MouseState state, MouseButtonFlags button)
{
base.Click(state, button);
return Swallow;
}
}
public OverlaysTestCase(string name) : base(name) { }
protected override void Populate()
{
var ownerSize = new Vector(150, 80);
var flapSize = new Vector(110, 60);
AddCaption("Left: a normal child, clipped to its owner");
AddCaption("Right: a hoisted one, escaping it and painting after the whole tree");
AddCaption("Also right: on top but not hoisted, so it rides with its owner in the owner's own space");
Rect stage = ContentAggregator.NewRow(190);
Rect clippedSlot = stage.NewCol(ownerSize.X, margin: 140);
var clippedOwner = NewChild("clipped owner");
clippedOwner.Material = UiPacksheetMaterials.GreenFlatPanel;
clippedOwner.Bounds = clippedSlot.NewRow(ownerSize.Y);
var clippedFlap = clippedOwner.ChildControls.NewChild("clipped flap");
clippedFlap.Material = UiPacksheetMaterials.RedFlatPanel;
clippedFlap.Bounds = Rect.FromPositionAndSize(new Vector(40, 40), flapSize);
Rect hoistSlot = stage.NewCol(ownerSize.X, margin: 140);
var hoistOwner = NewChild("hoisting owner");
hoistOwner.Material = UiPacksheetMaterials.GreenFlatPanel;
hoistOwner.Bounds = hoistSlot.NewRow(ownerSize.Y);
var hoistedFlap = new Overlay
{
DebugName = "hoisted flap",
Material = UiPacksheetMaterials.RedFlatPanel,
Bounds = Rect.FromPositionAndSize(Vector.Zero, flapSize),
OffsetFromOwner = new Vector(40, 40),
};
hoistOwner.ChildControls.Add(hoistedFlap);
// Sticks out past the flap, so it shows the nested hoist escaping a hoist's clip rather than merely
// drawing after a sibling, which any ordinary child would do.
var nestedFlap = new Overlay
{
DebugName = "nested hoist",
Material = UiPacksheetMaterials.YellowFlatPanel,
Bounds = Rect.FromPositionAndSize(Vector.Zero, new Vector(80, 40)),
OffsetFromOwner = new Vector(60, 34),
};
hoistedFlap.ChildControls.Add(nestedFlap);
// AlwaysOnTop without Hoisted: parent space, so it rides along with its owner for free and needs no
// Update override, while still painting after the whole tree.
var ridingFlap = hoistOwner.ChildControls.NewChild("riding flap");
ridingFlap.Material = UiPacksheetMaterials.BlueFlatPanel;
ridingFlap.Bounds = Rect.FromPositionAndSize(new Vector(-30, 84), new Vector(90, 40));
ridingFlap.AlwaysOnTop = true;
ridingFlap.ClipToAncestors = false;
// Added last, so it paints over both owners — and still ends up under the hoists.
var later = NewChild("later sibling");
later.Material = UiPacksheetMaterials.BlueFlatPanel;
later.Bounds = Rect.FromPositionAndSize(hoistOwner.Bounds.TopLeft + new Vector(60, 60), ownerSize);
AddCaption("A modal hears every click wherever it lands");
AddCaption("It will keep input off anything else");
Rect modalRow = ContentAggregator.NewRow(110);
var buttonSize = new Vector(200, 56);
var modalSize = new Vector(260, 130);
Rect buttonSlot = modalRow.NewCol(buttonSize.X, margin: 60);
int plainClicks = 0;
TextMaterial plainLabel = Widgets.ButtonLabel("clicked 0");
Button plainButton = new Panel { Material = plainLabel }.FillParent(new Button
{
DebugName = "plain button",
ButtonMaterial = UiPacksheetMaterials.Button,
Bounds = buttonSlot.NewRow(buttonSize.Y),
});
Add(plainButton);
plainButton.OnClick += delegate (in MouseState _, MouseButtonFlags __)
{
++plainClicks;
plainLabel.Text = $"clicked {plainClicks}";
};
var modal = new ModalOverlay
{
DebugName = "modal overlay",
Material = UiPacksheetMaterials.YellowPanel,
Bounds = Rect.FromPositionAndSize(Vector.Zero, modalSize),
OffsetFromOwner = modalRow.NewCol(modalSize.X).TopLeft,
};
Add(modal);
Rect insideModal = Rect.FromPositionAndSize(Vector.Zero, modal.ClientRect.Size);
TextMaterial heardLabel = Widgets.ButtonLabel("heard 0");
Panel heardReadout = heardLabel.BuildControl(Constraints.Unbounded);
heardReadout.Bounds = insideModal.NewRow(heardReadout.Bounds.Height, margin: Gutter);
modal.ChildControls.Add(heardReadout);
TextMaterial swallowLabel = Widgets.ButtonLabel("swallow: off");
Button swallowButton = new Panel { Material = swallowLabel }.FillParent(new Button
{
DebugName = "swallow toggle",
ButtonMaterial = UiPacksheetMaterials.Button,
Bounds = insideModal.NewRow(44),
});
modal.ChildControls.Add(swallowButton);
modal.OnHeard = delegate
{
heardLabel.Text = $"heard {modal.HeardCount}";
};
swallowButton.OnClick += delegate (in MouseState _, MouseButtonFlags __)
{
modal.Swallow = !modal.Swallow;
swallowLabel.Text = modal.Swallow ? "swallow: on" : "swallow: off";
};
}
}
}