A game framework written with osu! in mind.
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.Threading;
5using osu.Framework.Graphics.Containers;
6using osu.Framework.Graphics.Sprites;
7using osu.Framework.Logging;
8using osuTK;
9using osuTK.Graphics;
10using osu.Framework.Allocation;
11using osu.Framework.Configuration;
12using osu.Framework.Development;
13using osu.Framework.Timing;
14using osuTK.Input;
15using osu.Framework.Graphics.Shapes;
16using osu.Framework.Input.Events;
17
18namespace osu.Framework.Graphics.Visualisation
19{
20 internal class LogOverlay : OverlayContainer
21 {
22 private readonly FillFlowContainer flow;
23
24 protected override bool BlockPositionalInput => false;
25
26 private StopwatchClock clock;
27
28 private readonly Box box;
29
30 private const float background_alpha = 0.6f;
31
32 public LogOverlay()
33 {
34 //todo: use Input as font
35
36 Width = 700;
37 AutoSizeAxes = Axes.Y;
38
39 Anchor = Anchor.BottomLeft;
40 Origin = Anchor.BottomLeft;
41
42 Margin = new MarginPadding(1);
43
44 Masking = true;
45
46 Children = new Drawable[]
47 {
48 box = new Box
49 {
50 RelativeSizeAxes = Axes.Both,
51 Colour = Color4.Black,
52 Alpha = background_alpha,
53 },
54 flow = new FillFlowContainer
55 {
56 RelativeSizeAxes = Axes.X,
57 AutoSizeAxes = Axes.Y,
58 }
59 };
60 }
61
62 protected override void LoadComplete()
63 {
64 // custom clock is used to adjust log display speed (to freeze log display with a key).
65 Clock = new FramedClock(clock = new StopwatchClock(true));
66
67 base.LoadComplete();
68
69 addEntry(new LogEntry
70 {
71 Level = LogLevel.Important,
72 Message = "The debug log overlay is currently being displayed. You can toggle with Ctrl+F10 at any point.",
73 Target = LoggingTarget.Information,
74 });
75 }
76
77 private int logPosition;
78
79 private void addEntry(LogEntry entry)
80 {
81 if (!DebugUtils.IsDebugBuild && entry.Level <= LogLevel.Verbose)
82 return;
83
84 int pos = Interlocked.Increment(ref logPosition);
85
86 Schedule(() =>
87 {
88 const int display_length = 4000;
89
90 LoadComponentAsync(new DrawableLogEntry(entry), drawEntry =>
91 {
92 flow.Insert(pos, drawEntry);
93
94 drawEntry.FadeInFromZero(800, Easing.OutQuint).Delay(display_length).FadeOut(800, Easing.InQuint);
95 drawEntry.Expire();
96 });
97 });
98 }
99
100 protected override bool OnKeyDown(KeyDownEvent e)
101 {
102 if (!e.Repeat)
103 setHoldState(e.Key == Key.ControlLeft || e.Key == Key.ControlRight);
104
105 return base.OnKeyDown(e);
106 }
107
108 protected override void OnKeyUp(KeyUpEvent e)
109 {
110 if (!e.ControlPressed)
111 setHoldState(false);
112 base.OnKeyUp(e);
113 }
114
115 private void setHoldState(bool controlPressed)
116 {
117 box.Alpha = controlPressed ? 1 : background_alpha;
118 if (clock != null) clock.Rate = controlPressed ? 0 : 1;
119 }
120
121 [BackgroundDependencyLoader]
122 private void load(FrameworkConfigManager config)
123 {
124 }
125
126 protected override void PopIn()
127 {
128 Logger.NewEntry += addEntry;
129 this.FadeIn(100);
130 }
131
132 protected override void PopOut()
133 {
134 Logger.NewEntry -= addEntry;
135 setHoldState(false);
136 this.FadeOut(100);
137 }
138
139 protected override void Dispose(bool isDisposing)
140 {
141 base.Dispose(isDisposing);
142 Logger.NewEntry -= addEntry;
143 }
144 }
145
146 internal class DrawableLogEntry : Container
147 {
148 private const float target_box_width = 65;
149
150 private const float font_size = 14;
151
152 public DrawableLogEntry(LogEntry entry)
153 {
154 RelativeSizeAxes = Axes.X;
155 AutoSizeAxes = Axes.Y;
156
157 Color4 col = getColourForEntry(entry);
158
159 Children = new Drawable[]
160 {
161 new Container
162 {
163 //log target coloured box
164 Margin = new MarginPadding(3),
165 Size = new Vector2(target_box_width, font_size),
166 Anchor = Anchor.CentreLeft,
167 Origin = Anchor.CentreLeft,
168 CornerRadius = 5,
169 Masking = true,
170 Children = new Drawable[]
171 {
172 new Box
173 {
174 RelativeSizeAxes = Axes.Both,
175 Colour = col,
176 },
177 new SpriteText
178 {
179 Anchor = Anchor.Centre,
180 Origin = Anchor.Centre,
181 Shadow = true,
182 ShadowColour = Color4.Black,
183 Margin = new MarginPadding { Left = 5, Right = 5 },
184 Font = FrameworkFont.Regular.With(size: font_size),
185 Text = entry.Target?.ToString() ?? entry.LoggerName,
186 }
187 }
188 },
189 new Container
190 {
191 AutoSizeAxes = Axes.Y,
192 RelativeSizeAxes = Axes.X,
193 Anchor = Anchor.CentreLeft,
194 Origin = Anchor.CentreLeft,
195 Padding = new MarginPadding { Left = target_box_width + 10 },
196 Child = new SpriteText
197 {
198 RelativeSizeAxes = Axes.X,
199 Font = FrameworkFont.Regular.With(size: font_size),
200 Text = entry.Message
201 }
202 }
203 };
204 }
205
206 private Color4 getColourForEntry(LogEntry entry)
207 {
208 switch (entry.Target)
209 {
210 case LoggingTarget.Runtime:
211 return Color4.YellowGreen;
212
213 case LoggingTarget.Network:
214 return Color4.BlueViolet;
215
216 case LoggingTarget.Performance:
217 return Color4.HotPink;
218
219 case LoggingTarget.Information:
220 return Color4.CadetBlue;
221
222 default:
223 return Color4.Cyan;
224 }
225 }
226 }
227}