Skip to content

Instantly share code, notes, and snippets.

@yume-chan
Created February 18, 2016 19:29
Show Gist options
  • Save yume-chan/f89e2314ec199abcfd6e to your computer and use it in GitHub Desktop.
Save yume-chan/f89e2314ec199abcfd6e to your computer and use it in GitHub Desktop.
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace SoyMilk
{
public sealed class DanmakuControl : ContentPresenter
{
CanvasAnimatedControl Canvas;
public DanmakuControl()
{
IsHitTestVisible = false;
SizeChanged += DanmakuControl_SizeChanged;
RegisterPropertyChangedCallback(VisibilityProperty, DanmakuControl_VisibilityChanged);
Canvas = new CanvasAnimatedControl();
Canvas.Paused = true;
Canvas.IsFixedTimeStep = false;
Canvas.TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 25);
Canvas.CreateResources += Canvas_CreateResources;
Canvas.Update += Canvas_Update;
Canvas.Draw += Canvas_Draw;
Content = Canvas;
}
bool IsVisible = true;
private void DanmakuControl_VisibilityChanged(DependencyObject sender, DependencyProperty dp)
{
IsVisible = Visibility == Visibility.Visible;
}
class Danmaku
{
public readonly int LineIndex;
public readonly string Content;
public readonly bool FromSelf;
public float Left;
public float Top;
public CanvasRenderTarget Layout;
public float Width;
public float Right => Left + Width;
public Danmaku(int lineIndex, string content, bool fromSelf, float left, float top)
{
LineIndex = lineIndex;
Content = content;
FromSelf = fromSelf;
Left = left;
Top = top;
}
}
readonly object _lock = new object();
const int Speed = -120;
float screenWidth;
float lineHeight = 30;
float[] Lines;
readonly List<Danmaku> Danmakus = new List<Danmaku>(80);
public void AddDanmaku(string content, bool fromSelf)
{
lock (_lock)
{
if (Canvas == null || Lines?.Length < 1)
return;
float min = float.MaxValue;
int minIndex = -1;
for (int i = 0; i < Lines.Length; ++i)
{
var v = Lines[i];
if (v <= screenWidth)
{
minIndex = i;
break;
}
else if (v < min)
{
min = v;
minIndex = i;
}
}
var danmaku = new Danmaku(minIndex, content, fromSelf, screenWidth + 20, minIndex * lineHeight);
createLayout(danmaku);
Danmakus.Add(danmaku);
Lines[minIndex] = danmaku.Right;
if (Danmakus.Count != 0)
Canvas.Paused = false;
}
}
ShadowEffect shadowEffect = new ShadowEffect()
{
BlurAmount = 2,
Optimization = EffectOptimization.Speed
};
CanvasSolidColorBrush brush;
CanvasTextFormat format = new CanvasTextFormat() { FontSize = 24, FontFamily = "Microsoft YaHei UI" };
void createLayout(Danmaku danmaku)
{
using (var layout = new CanvasTextLayout(Canvas, danmaku.Content, format, float.MaxValue, float.MaxValue))
{
layout.Options = CanvasDrawTextOptions.NoPixelSnap;
danmaku.Width = (float)layout.LayoutBounds.Width + 8;
using (var command = new CanvasCommandList(Canvas))
{
using (var session = command.CreateDrawingSession())
{
session.DrawTextLayout(layout, 6, 2, brush);
if (danmaku.FromSelf)
session.DrawRectangle(2, 2, danmaku.Width, lineHeight, brush, 2);
}
shadowEffect.Source = command;
var result = new CanvasRenderTarget(Canvas, new Size(danmaku.Width + 4, lineHeight + 5));
using (var session = result.CreateDrawingSession())
{
session.Clear(Colors.Transparent);
session.DrawImage(shadowEffect);
session.DrawImage(command);
}
danmaku.Layout = result;
}
}
}
public void RemoveFromVisualTree()
{
lock (_lock)
{
Canvas?.RemoveFromVisualTree();
Canvas = null;
brush?.Dispose();
brush = null;
shadowEffect?.Dispose();
shadowEffect = null;
foreach (var danmaku in Danmakus)
danmaku.Layout?.Dispose();
}
}
private void DanmakuControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
lock (_lock)
{
if (Canvas == null)
return;
var size = e.NewSize;
if (size.Width == 0 && size.Height == 0)
return;
screenWidth = (float)size.Width;
createResource(false);
}
}
void createResource(bool dpiChanged)
{
if (!Canvas.ReadyToDraw)
return;
brush?.Dispose();
brush = new CanvasSolidColorBrush(Canvas, Colors.White);
if (dpiChanged)
{
float scale = 1;
switch (DisplayInformation.GetForCurrentView().ResolutionScale)
{
case ResolutionScale.Scale120Percent:
scale = 1.2F;
break;
case ResolutionScale.Scale125Percent:
scale = 1.25F;
break;
case ResolutionScale.Scale140Percent:
scale = 1.4F;
break;
case ResolutionScale.Scale150Percent:
scale = 1.5F;
break;
case ResolutionScale.Scale160Percent:
scale = 1.6F;
break;
case ResolutionScale.Scale175Percent:
scale = 1.75F;
break;
case ResolutionScale.Scale180Percent:
scale = 1.80F;
break;
case ResolutionScale.Scale200Percent:
scale = 2F;
break;
case ResolutionScale.Scale225Percent:
scale = 2.25F;
break;
case ResolutionScale.Scale250Percent:
scale = 2.5F;
break;
case ResolutionScale.Scale300Percent:
scale = 3F;
break;
case ResolutionScale.Scale350Percent:
scale = 3.5F;
break;
case ResolutionScale.Scale400Percent:
scale = 4F;
break;
case ResolutionScale.Scale450Percent:
scale = 4.5F;
break;
case ResolutionScale.Scale500Percent:
scale = 5F;
break;
}
format.FontSize = 12 + 10 / scale;
using (var testLayout = new CanvasTextLayout(Canvas, "T", format, float.MaxValue, float.MaxValue))
lineHeight = (float)testLayout.LayoutBounds.Height + 1;
}
var newLineCount = (int)(ActualHeight / lineHeight);
Lines = new float[newLineCount];
for (var i = 0; i < Danmakus.Count; i++)
{
var danmaku = Danmakus[i];
danmaku.Layout?.Dispose();
var lineIndex = danmaku.LineIndex;
if (lineIndex < newLineCount)
{
danmaku.Top = danmaku.LineIndex * lineHeight;
createLayout(danmaku);
if (Lines[lineIndex] != 0)
danmaku.Left = Lines[lineIndex] + 20;
Lines[lineIndex] = danmaku.Right;
}
else
{
Danmakus.RemoveAt(i);
i--;
}
}
}
private void Canvas_CreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
lock (_lock)
{
createResource(true);
}
}
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
{
lock (_lock)
{
if (Canvas == null)
return;
var distance = Speed * (float)args.Timing.ElapsedTime.TotalSeconds;
for (var i = 0; i < Danmakus.Count; i++)
{
var danmaku = Danmakus[i];
danmaku.Left += distance;
if (danmaku.Right < -5)
{
danmaku.Layout?.Dispose();
Danmakus.RemoveAt(i);
i--;
}
}
for (var i = 0; i < Lines.Length; i++)
Lines[i] += distance;
}
}
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
if (!IsVisible)
return;
lock (_lock)
{
if (Canvas == null)
return;
if (Danmakus.Count == 0)
{
Canvas.Paused = true;
return;
}
var session = args.DrawingSession;
foreach (var danmaku in Danmakus)
session.DrawImage(danmaku.Layout, danmaku.Left, danmaku.Top);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment