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"; }; } } }