A game framework written with osu! in mind.
at master 446 lines 15 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Linq; 7using osu.Framework.Graphics.Containers; 8using osu.Framework.Graphics.Sprites; 9using osuTK; 10using osuTK.Graphics; 11using osuTK.Input; 12using osu.Framework.Graphics.Shapes; 13using osu.Framework.Allocation; 14using osu.Framework.Extensions.IEnumerableExtensions; 15using osu.Framework.Graphics.Textures; 16using osu.Framework.Input.Events; 17 18namespace osu.Framework.Graphics.Visualisation 19{ 20 internal class VisualisedDrawable : Container, IContainVisualisedDrawables 21 { 22 private const int line_height = 12; 23 24 public Drawable Target { get; } 25 26 private bool isHighlighted; 27 28 public bool IsHighlighted 29 { 30 get => isHighlighted; 31 set 32 { 33 isHighlighted = value; 34 35 updateColours(); 36 if (value) 37 Expand(); 38 } 39 } 40 41 public Action<Drawable> RequestTarget; 42 public Action<VisualisedDrawable> HighlightTarget; 43 44 private Box background; 45 private SpriteText text; 46 private SpriteText text2; 47 private Drawable previewBox; 48 private Drawable activityInvalidate; 49 private Drawable activityAutosize; 50 private Drawable activityLayout; 51 private VisualisedDrawableFlow flow; 52 private Container connectionContainer; 53 54 private const float row_width = 10; 55 private const float row_height = 20; 56 57 [Resolved] 58 private DrawVisualiser visualiser { get; set; } 59 60 [Resolved] 61 private TreeContainer tree { get; set; } 62 63 public VisualisedDrawable(Drawable d) 64 { 65 Target = d; 66 } 67 68 [BackgroundDependencyLoader] 69 private void load() 70 { 71 RelativeSizeAxes = Axes.X; 72 AutoSizeAxes = Axes.Y; 73 74 var spriteTarget = Target as Sprite; 75 76 AddRange(new Drawable[] 77 { 78 flow = new VisualisedDrawableFlow 79 { 80 Direction = FillDirection.Vertical, 81 RelativeSizeAxes = Axes.X, 82 AutoSizeAxes = Axes.Y, 83 Position = new Vector2(row_width, row_height) 84 }, 85 new Container 86 { 87 AutoSizeAxes = Axes.Both, 88 Children = new[] 89 { 90 background = new Box 91 { 92 RelativeSizeAxes = Axes.Both, 93 Size = new Vector2(100, 1), // a bit of a hack, but works well enough. 94 Anchor = Anchor.Centre, 95 Origin = Anchor.Centre, 96 Colour = Color4.Transparent, 97 }, 98 activityInvalidate = new Box 99 { 100 Colour = Color4.Yellow, 101 Size = new Vector2(2, line_height), 102 Anchor = Anchor.CentreLeft, 103 Origin = Anchor.CentreLeft, 104 Position = new Vector2(6, 0), 105 Alpha = 0 106 }, 107 activityLayout = new Box 108 { 109 Colour = Color4.Orange, 110 Size = new Vector2(2, line_height), 111 Anchor = Anchor.CentreLeft, 112 Origin = Anchor.CentreLeft, 113 Position = new Vector2(3, 0), 114 Alpha = 0 115 }, 116 activityAutosize = new Box 117 { 118 Colour = Color4.Red, 119 Size = new Vector2(2, line_height), 120 Anchor = Anchor.CentreLeft, 121 Origin = Anchor.CentreLeft, 122 Position = new Vector2(0, 0), 123 Alpha = 0 124 }, 125 previewBox = spriteTarget?.Texture == null 126 ? previewBox = new Box 127 { 128 Colour = Color4.White, 129 Anchor = Anchor.CentreLeft, 130 Origin = Anchor.CentreLeft, 131 } 132 : new Sprite 133 { 134 // It's fine to only bypass the ref count, because this sprite will dispose along with the original sprite 135 Texture = new Texture(spriteTarget.Texture.TextureGL), 136 Scale = new Vector2(spriteTarget.Texture.DisplayWidth / spriteTarget.Texture.DisplayHeight, 1), 137 Anchor = Anchor.CentreLeft, 138 Origin = Anchor.CentreLeft, 139 }, 140 new FillFlowContainer 141 { 142 AutoSizeAxes = Axes.Both, 143 Direction = FillDirection.Horizontal, 144 Spacing = new Vector2(5), 145 Position = new Vector2(24, 0), 146 Children = new Drawable[] 147 { 148 text = new SpriteText { Font = FrameworkFont.Regular }, 149 text2 = new SpriteText { Font = FrameworkFont.Regular }, 150 } 151 }, 152 } 153 }, 154 }); 155 156 const float connection_width = 1; 157 158 AddInternal(connectionContainer = new Container 159 { 160 Colour = FrameworkColour.Green, 161 RelativeSizeAxes = Axes.Y, 162 Width = connection_width, 163 Children = new Drawable[] 164 { 165 new Box 166 { 167 RelativeSizeAxes = Axes.Both, 168 EdgeSmoothness = new Vector2(0.5f), 169 }, 170 new Box 171 { 172 Anchor = Anchor.TopRight, 173 Origin = Anchor.CentreLeft, 174 Y = row_height / 2, 175 Width = row_width / 2, 176 EdgeSmoothness = new Vector2(0.5f), 177 } 178 } 179 }); 180 181 previewBox.Position = new Vector2(9, 0); 182 previewBox.Size = new Vector2(line_height, line_height); 183 184 var compositeTarget = Target as CompositeDrawable; 185 compositeTarget?.AliveInternalChildren.ForEach(addChild); 186 187 updateSpecifics(); 188 } 189 190 protected override void LoadComplete() 191 { 192 base.LoadComplete(); 193 194 attachEvents(); 195 updateColours(); 196 } 197 198 public bool TopLevel 199 { 200 set => connectionContainer.Alpha = value ? 0 : 1; 201 } 202 203 private void attachEvents() 204 { 205 Target.Invalidated += onInvalidated; 206 Target.OnDispose += onDispose; 207 208 if (Target is CompositeDrawable da) 209 { 210 da.OnAutoSize += onAutoSize; 211 da.ChildBecameAlive += addChild; 212 da.ChildDied += removeChild; 213 da.ChildDepthChanged += depthChanged; 214 } 215 216 if (Target is FlowContainer<Drawable> df) df.OnLayout += onLayout; 217 } 218 219 private void detachEvents() 220 { 221 Target.Invalidated -= onInvalidated; 222 Target.OnDispose -= onDispose; 223 224 if (Target is CompositeDrawable da) 225 { 226 da.OnAutoSize -= onAutoSize; 227 da.ChildBecameAlive -= addChild; 228 da.ChildDied -= removeChild; 229 da.ChildDepthChanged -= depthChanged; 230 } 231 232 if (Target is FlowContainer<Drawable> df) df.OnLayout -= onLayout; 233 } 234 235 private void addChild(Drawable drawable) 236 { 237 // Make sure to never add the DrawVisualiser (recursive scenario) 238 if (drawable == visualiser) return; 239 240 // Don't add individual characters of SpriteText 241 if (Target is SpriteText) return; 242 243 visualiser.GetVisualiserFor(drawable).SetContainer(this); 244 } 245 246 private void removeChild(Drawable drawable) 247 { 248 var vis = visualiser.GetVisualiserFor(drawable); 249 if (vis.currentContainer == this) 250 vis.SetContainer(null); 251 } 252 253 private void depthChanged(Drawable drawable) 254 { 255 var vis = visualiser.GetVisualiserFor(drawable); 256 257 vis.currentContainer?.RemoveVisualiser(vis); 258 vis.currentContainer?.AddVisualiser(vis); 259 } 260 261 void IContainVisualisedDrawables.AddVisualiser(VisualisedDrawable visualiser) 262 { 263 visualiser.RequestTarget = d => RequestTarget?.Invoke(d); 264 visualiser.HighlightTarget = d => HighlightTarget?.Invoke(d); 265 266 visualiser.Depth = visualiser.Target.Depth; 267 268 flow.Add(visualiser); 269 } 270 271 void IContainVisualisedDrawables.RemoveVisualiser(VisualisedDrawable visualiser) => flow.Remove(visualiser); 272 273 public VisualisedDrawable FindVisualisedDrawable(Drawable drawable) 274 { 275 if (drawable == Target) 276 return this; 277 278 foreach (var child in flow) 279 { 280 var vis = child.FindVisualisedDrawable(drawable); 281 if (vis != null) 282 return vis; 283 } 284 285 return null; 286 } 287 288 protected override void Dispose(bool isDisposing) 289 { 290 detachEvents(); 291 base.Dispose(isDisposing); 292 } 293 294 protected override bool OnHover(HoverEvent e) 295 { 296 updateColours(); 297 return base.OnHover(e); 298 } 299 300 protected override void OnHoverLost(HoverLostEvent e) 301 { 302 updateColours(); 303 base.OnHoverLost(e); 304 } 305 306 private void updateColours() 307 { 308 if (isHighlighted) 309 { 310 background.Colour = FrameworkColour.YellowGreen; 311 text.Colour = FrameworkColour.Blue; 312 text2.Colour = FrameworkColour.Blue; 313 } 314 else if (IsHovered) 315 { 316 background.Colour = FrameworkColour.BlueGreen; 317 text.Colour = Color4.White; 318 text2.Colour = FrameworkColour.YellowGreen; 319 } 320 else 321 { 322 background.Colour = Color4.Transparent; 323 text.Colour = Color4.White; 324 text2.Colour = FrameworkColour.YellowGreen; 325 } 326 } 327 328 protected override bool OnMouseDown(MouseDownEvent e) 329 { 330 if (e.Button == MouseButton.Right) 331 { 332 HighlightTarget?.Invoke(this); 333 return true; 334 } 335 336 return false; 337 } 338 339 protected override bool OnClick(ClickEvent e) 340 { 341 if (isExpanded) 342 Collapse(); 343 else 344 Expand(); 345 return true; 346 } 347 348 protected override bool OnDoubleClick(DoubleClickEvent e) 349 { 350 RequestTarget?.Invoke(Target); 351 return true; 352 } 353 354 private bool isExpanded = true; 355 356 public void Expand() 357 { 358 flow.FadeIn(); 359 updateSpecifics(); 360 361 isExpanded = true; 362 } 363 364 public void ExpandAll() 365 { 366 Expand(); 367 flow.ForEach(f => f.Expand()); 368 } 369 370 public void Collapse() 371 { 372 flow.FadeOut(); 373 updateSpecifics(); 374 375 isExpanded = false; 376 } 377 378 private void onAutoSize() => activityAutosize.FadeOutFromOne(1); 379 380 private void onLayout() => activityLayout.FadeOutFromOne(1); 381 382 private void onInvalidated(Drawable d) => activityInvalidate.FadeOutFromOne(1); 383 384 private void onDispose() 385 { 386 // May come from the disposal thread, in which case they won't ever be reused and the container doesn't need to be reset 387 Schedule(() => SetContainer(null)); 388 } 389 390 private void updateSpecifics() 391 { 392 Vector2 posInTree = ToSpaceOfOtherDrawable(Vector2.Zero, tree); 393 394 if (posInTree.Y < -previewBox.DrawHeight || posInTree.Y > tree.Height) 395 { 396 text.Text = string.Empty; 397 return; 398 } 399 400 previewBox.Alpha = Math.Max(0.2f, Target.Alpha); 401 previewBox.Colour = Target.Colour; 402 403 int childCount = (Target as CompositeDrawable)?.InternalChildren.Count ?? 0; 404 405 text.Text = Target.ToString(); 406 text2.Text = $"({Target.DrawPosition.X:#,0},{Target.DrawPosition.Y:#,0}) {Target.DrawSize.X:#,0}x{Target.DrawSize.Y:#,0}" 407 + (!isExpanded && childCount > 0 ? $@" ({childCount} children)" : string.Empty); 408 409 Alpha = Target.IsPresent ? 1 : 0.3f; 410 } 411 412 protected override void Update() 413 { 414 updateSpecifics(); 415 base.Update(); 416 } 417 418 private IContainVisualisedDrawables currentContainer; 419 420 /// <summary> 421 /// Moves this <see cref="VisualisedDrawable"/> to be contained by another target. 422 /// </summary> 423 /// <remarks> 424 /// The <see cref="VisualisedDrawable"/> is first removed from its current container via <see cref="IContainVisualisedDrawables.RemoveVisualiser"/>, 425 /// prior to being added to the new container via <see cref="IContainVisualisedDrawables.AddVisualiser"/>. 426 /// </remarks> 427 /// <param name="container">The target which should contain this <see cref="VisualisedDrawable"/>.</param> 428 public void SetContainer(IContainVisualisedDrawables container) 429 { 430 currentContainer?.RemoveVisualiser(this); 431 432 // The visualised may have previously been within a container (e.g. flow), which repositioned it 433 // We should make sure that the position is reset before it's added to another container 434 Y = 0; 435 436 container?.AddVisualiser(this); 437 438 currentContainer = container; 439 } 440 441 private class VisualisedDrawableFlow : FillFlowContainer<VisualisedDrawable> 442 { 443 public override IEnumerable<Drawable> FlowingChildren => AliveInternalChildren.Where(d => d.IsPresent).OrderBy(d => -d.Depth).ThenBy(d => ((VisualisedDrawable)d).Target.ChildID); 444 } 445 } 446}