using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
public class TranspCtrl : Control
{
public TranspCtrl()
{
Items = new ItemCollection(this);
ResizeRedraw = true;
DoubleBuffered = true;
}
public ItemCollection Items { get; private set; }
//
static readonly Brush BrushShadow = new SolidBrush(Color.FromArgb(32, 0, 0, 0));
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.TranslateTransform(-VIEWPOINT_X, -VIEWPOINT_Y);
foreach (var item in Items.Items)
{
if (item.Buffer == null)
{
item.Render();
}
if (item.CastShadow)
g.FillRectangle(BrushShadow, item.X + 5, item.Y + 5, item.Width, item.Height);
g.DrawImage(item.Buffer, item.X, item.Y, item.Width, item.Height);
if (item.Selected)
g.DrawRectangle(Pens.Lime, item.X - 1, item.Y - 1, item.Width + 1, item.Height + 1);
}
if (MouseJobParam == null)
return;
MouseJobParam.Paint(this,g);
}
volatile int Updating = 0;
public void BeginUpdate()
{
Updating++;
}
public void EndUpdate()
{
Updating--;
if (Updating == 0)
Invalidate();
}
internal void TryUpdate()
{
if (Updating == 0)
Invalidate();
}
//
double GetDistance(float x, float y, float x2, float y2)
{
double dx = x2 - x, dy = y2 - y;
return Math.Sqrt(dx * dx + dy * dy);
}
public void DeselectAll()
{
foreach (var item in Items.Items)
{
if (!item.Selected)
continue;
item.Deselect();
}
}
Item MouseHitTest(int x, int y)
{
var worldMouse = GetWorldPos(x, y);
var nx = (int)worldMouse.X;
var ny = (int)worldMouse.Y;
Item hitTarget = null;
foreach (var item in Items.Items)
{
if (!item.Bound.Contains(nx, ny))
continue;
hitTarget = item;
}
return hitTarget;
}
//
volatile float VIEWPOINT_X = 0;
volatile float VIEWPOINT_Y = 0;
void ViewTo(float x, float y)
{
VIEWPOINT_X = x;
VIEWPOINT_Y = y;
TryUpdate();
}
void ViewToward(float x, float y)
{
VIEWPOINT_X += x;
VIEWPOINT_Y += y;
TryUpdate();
}
PointF GetWorldPos(Point p) => GetWorldPos(p.X, p.Y);
PointF GetWorldPos(int x, int y)
{
return new PointF(
VIEWPOINT_X + x,
VIEWPOINT_Y + y
);
}
Point GetControlPos(PointF p) => GetControlPos(p.X, p.Y);
Point GetControlPos(float x, float y)
{
return new Point(
(int)(x),
(int)(y)
);
}
//
enum MouseJobType
{
Idle,
Drag,
}
enum MouseDragSelectType
{
None,
Additive,
Toggle,
}
class MouseDragJobTypeParam : MouseJobTypeParam
{
public MouseDragSelectType SelectType;
public bool IsDrag;
public int OriginX;
public int OriginY;
public PointF OriginWorldLocation;
public PointF PrevWorldLocation;
public PointF OffsetWorldLocation;
public Rectangle Bound
{
get
{
var ox = (OriginWorldLocation.X < OffsetWorldLocation.X)
? OriginWorldLocation.X
: OffsetWorldLocation.X;
var oy = (OriginWorldLocation.Y < OffsetWorldLocation.Y)
? OriginWorldLocation.Y
: OffsetWorldLocation.Y;
var w = Math.Abs(OriginWorldLocation.X - OffsetWorldLocation.X);
var h = Math.Abs(OriginWorldLocation.Y - OffsetWorldLocation.Y);
return new Rectangle((int)ox, (int)oy, (int)w, (int)h);
}
}
public override void Paint(TranspCtrl sender, Graphics g)
{
if (!IsDrag)
return;
switch (SelectType)
{
case MouseDragSelectType.Additive:
{
var bound = Bound;
g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Aqua)), bound);
g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Aqua)), bound);
}
break;
case MouseDragSelectType.Toggle:
{
var bound = Bound;
g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Maroon)), bound);
g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Maroon)), bound);
}
break;
default:
var dx = (int)(OffsetWorldLocation.X - OriginWorldLocation.X);
var dy = (int)(OffsetWorldLocation.Y - OriginWorldLocation.Y);
foreach (var item in sender.Items.Items)
{
if (!item.Selected)
continue;
if (item.Buffer == null)
item.Render();
var itembound = item.Bound;
itembound.Offset(dx, dy);
g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Aqua)), itembound);
g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Aqua)), itembound);
}
break;
}
}
}
abstract class MouseJobTypeParam { public abstract void Paint(TranspCtrl sender,Graphics g); }
MouseJobType MouseJob = MouseJobType.Idle;
MouseJobTypeParam MouseJobParam = null;
void MouseJobClear()
{
MouseJobParam = null;
MouseJob = MouseJobType.Idle;
}
protected override void OnMouseMove(MouseEventArgs e)
{
BeginUpdate();
switch (MouseJob)
{
case MouseJobType.Idle:
var hitItem = MouseHitTest(e.X, e.Y);
if (hitItem != null)
Cursor = Cursors.Hand;
else
Cursor = Cursors.Default;
break;
case MouseJobType.Drag:
var dragJob = MouseJobParam as MouseDragJobTypeParam;
dragJob.OffsetWorldLocation = GetWorldPos(e.Location);
if (GetDistance(dragJob.OffsetWorldLocation.X, dragJob.OffsetWorldLocation.Y, dragJob.OriginWorldLocation.X, dragJob.OriginWorldLocation.Y) < 3)
break;
dragJob.IsDrag = true;
/*
var dx = dragJob.OffsetWorldLocation.X - dragJob.PrevWorldLocation.X;
var dy = dragJob.OffsetWorldLocation.Y - dragJob.PrevWorldLocation.Y;
dragJob.PrevWorldLocation = dragJob.OffsetWorldLocation;
foreach(var item in Items.Items)
{
if (!item.Selected)
continue;
item.MoveToward((int)dx, (int)dy);
}
*/
break;
}
EndUpdate();
base.OnMouseMove(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
BeginUpdate();
switch (MouseJob)
{
case MouseJobType.Idle:
switch (e.Button)
{
case MouseButtons.Left:
{
if (e.Clicks == 1)
{
var hitItem = MouseHitTest(e.X, e.Y);
MouseJob = MouseJobType.Drag;
MouseJobParam = new MouseDragJobTypeParam()
{
SelectType = (hitItem == null)
? (ModifierKeys == Keys.Shift)
? MouseDragSelectType.Additive
: (ModifierKeys == Keys.Control)
? MouseDragSelectType.Toggle
: MouseDragSelectType.Additive
: (ModifierKeys == Keys.Control)
? MouseDragSelectType.Toggle
: MouseDragSelectType.None,
IsDrag = false,
OriginX = e.X,
OriginY = e.Y,
OriginWorldLocation = GetWorldPos(e.Location),
OffsetWorldLocation = GetWorldPos(e.Location),
PrevWorldLocation = GetWorldPos(e.Location),
};
if (hitItem != null)
{
if (!hitItem.Selected)
{
if ((ModifierKeys != Keys.Control) && (ModifierKeys != Keys.Shift))
{
DeselectAll();
}
}
Cursor = Cursors.Hand;
hitItem.Select();
}
else
{
if ((ModifierKeys != Keys.Control) && (ModifierKeys != Keys.Shift))
{
DeselectAll();
}
}
}
else
{
Console.WriteLine("Double Click!");
}
}
break;
case MouseButtons.Right:
{
var hitItem = MouseHitTest(e.X, e.Y);
if (hitItem == null)
{
DeselectAll();
}
else
Items.Remove(hitItem);
}
break;
}
break;
}
EndUpdate();
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
BeginUpdate();
switch (MouseJob)
{
case MouseJobType.Drag:
var dragJob = MouseJobParam as MouseDragJobTypeParam;
dragJob.OffsetWorldLocation = GetWorldPos(e.Location);
if (dragJob.IsDrag)
{
switch (dragJob.SelectType)
{
case MouseDragSelectType.Additive:
{
var dragSelectBound = dragJob.Bound;
foreach (var item in Items.Items)
{
if (item.Selected)
continue;
if (!dragSelectBound.IntersectsWith(item.Bound))
continue;
item.Select();
}
}
break;
case MouseDragSelectType.Toggle:
{
var dragSelectBound = dragJob.Bound;
foreach (var item in Items.Items)
{
if (!dragSelectBound.IntersectsWith(item.Bound))
continue;
if (item.Selected)
item.Deselect();
else
item.Select();
}
}
break;
default:
var dx = dragJob.OffsetWorldLocation.X - dragJob.OriginWorldLocation.X;
var dy = dragJob.OffsetWorldLocation.Y - dragJob.OriginWorldLocation.Y;
foreach (var item in Items.Items)
{
if (!item.Selected)
continue;
item.MoveToward((int)dx, (int)dy);
}
break;
}
}
Cursor = Cursors.Default;
MouseJobClear();
break;
}
EndUpdate();
base.OnMouseUp(e);
}
//
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
base.OnPreviewKeyDown(e);
switch (e.KeyCode)
{
case Keys.Up:
ViewToward(0, -100);
break;
case Keys.Down:
ViewToward(0, 100);
break;
case Keys.Left:
ViewToward(-100, 0);
break;
case Keys.Right:
ViewToward(100, 0);
break;
}
}
//
public class ItemCollection
{
internal List<Item> Items;
public TranspCtrl Parent { get; private set; }
internal ItemCollection(TranspCtrl parent)
{
Parent = parent;
Items = new List<Item>();
}
public Item Add(string text, int x, int y)
{
var item = new Item()
{
Parent = Parent,
Text = text,
};
item.MoveTo(x, y);
return Add(item);
}
public Item Add(Item item)
{
item.Parent = Parent;
lock (Items)
{
Items.Add(item);
}
Parent.TryUpdate();
return item;
}
public void Remove(Item item)
{
item.Parent = null;
Items.Remove(item);
Parent.TryUpdate();
}
}
public class Item
{
public TranspCtrl Parent { get; internal set; }
public Bitmap Buffer = null;
public Rectangle Bound { get; private set; } = new Rectangle(0, 0, 100, 100);
//
public void MoveToward(int x, int y) => MoveTo(Bound.X + x, Bound.Y + y);
public void MoveTo(int x, int y)
{
Bound = new Rectangle(x, y, Bound.Width, Bound.Height);
Parent?.TryUpdate();
}
//
public bool CastShadow { get; set; } = true;
public int X { get => Bound.X; }
public int Y { get => Bound.Y; }
public int Width { get => Bound.Width; }
public int Height { get => Bound.Width; }
public string Text;
SolidBrush BackBrush = new SolidBrush(Color.FromArgb(63, 63, 70));
public Color BackColor { get => BackBrush.Color; set => BackBrush = new SolidBrush(value); }
//
public bool Selected { get; private set; }
public void Deselect()
{
Selected = false;
Parent.TryUpdate();
}
public void Select()
{
Selected = true;
Parent.TryUpdate();
}
//
public void Render()
{
var buf = new Bitmap(Width, Height);
using (var g = Graphics.FromImage(buf))
{
g.Clear(Color.Transparent);
g.DrawRectangle(Pens.Black, 0, 0, Width - 1, Height - 1);
g.FillRectangle(BackBrush, 0, 0, Width, Height);
g.DrawString(Text, Parent.Font, Brushes.White, 4, 4);
}
if (Buffer != null)
Buffer.Dispose();
Buffer = buf;
}
}
}