PixelStudio.cs
namespace PixelPusher;
public static class PixelStudio
{
public static PushTool Tool = PushTool.Push;
public static byte Fg = 7;
public static byte Bg;
public static int Brush = 1;
public static bool Circle = true;
public static int Zoom = 16;
public static bool Grid = true;
public static bool FillShape;
public static StampRole Role = StampRole.Solid;
public static int PanX;
public static int PanY;
public static bool Onion = true;
public static bool Mirror;
public static int HoverPx;
public static int HoverPy;
public static bool Playing;
public static int Fps = 8;
public static int CursorX;
public static int CursorY;
public static bool PadAim;
static readonly PushTool[] ToolOrder =
{
PushTool.Push, PushTool.Flood, PushTool.Sip, PushTool.Block, PushTool.Line, PushTool.Oval
};
static float _padAccX;
static float _padAccY;
static float _playT;
static float _paintHold;
static bool _stroke;
static int _sx;
static int _sy;
static int _lx;
static int _ly;
public static PixelDoc Doc => Desk.Sprite;
public static void SelectTool( PushTool tool )
{
Tool = tool;
GameFlow.Bump();
}
public static void SelectColor( byte index )
{
Fg = index;
GameFlow.Bump();
}
public static void SwapFgBg()
{
(Fg, Bg) = (Bg, Fg);
GameFlow.Bump();
}
public static void SetBrush( int size )
{
Brush = Math.Clamp( size, 1, 8 );
GameFlow.Bump();
}
public static void SetZoom( int zoom )
{
Zoom = Math.Clamp( zoom, 1, 32 );
GameFlow.Bump();
}
public static void ZoomAt( int vx, int vy, int dir )
{
var doc = Doc;
var old = Math.Max( 1, Zoom );
var next = Math.Clamp( old + dir, 1, 32 );
if ( next == old || doc is null )
{
SetZoom( next );
return;
}
var ox = (CrtView.VW - doc.Width * old) / 2 - PanX;
var oy = (CrtView.VH - doc.Height * old) / 2 - PanY;
var px = (vx - ox) / (float)old;
var py = (vy - oy) / (float)old;
Zoom = next;
PanX = (int)MathF.Round( (CrtView.VW - doc.Width * next) / 2f - vx + px * next );
PanY = (int)MathF.Round( (CrtView.VH - doc.Height * next) / 2f - vy + py * next );
GameFlow.Bump();
}
public static void NewCanvas( int size )
{
size = Math.Clamp( size, 8, 256 );
Desk.Sprite = new PixelDoc( size, size, Palettes.Named( Desk.Sprite?.PaletteName ) )
{
Name = $"sprite_{size}"
};
PanX = 0;
PanY = 0;
GameFlow.ShowToast( $"{size}×{size} canvas" );
GameFlow.Bump();
}
public static void SetPalette( PalettePack pack )
{
Doc?.ApplyPalette( pack );
GameFlow.Bump();
}
public static void Undo()
{
Doc?.Undo();
GameFlow.Bump();
}
public static void Redo()
{
Doc?.Redo();
GameFlow.Bump();
}
public static void Clear()
{
Doc?.Clear();
GameFlow.Bump();
}
public static void SetRole( StampRole role )
{
Role = role;
GameFlow.Bump();
}
public static void ToggleFill()
{
FillShape = !FillShape;
GameFlow.Bump();
}
public static void ToggleCircle()
{
Circle = !Circle;
GameFlow.Bump();
}
public static void SendToTileset()
{
if ( Doc is null || Desk.Tileset is null )
return;
var id = Math.Max( 1, StageStudio.TileId );
Desk.Tileset.StampFromSprite( Doc, id );
Desk.Tileset.SetFlag( id, TilesetDoc.FlagFromRole( Role ) );
GameFlow.ShowToast( $"stamped tile {id} · {TilesetDoc.FlagName( TilesetDoc.FlagFromRole( Role ) )}" );
GameFlow.Bump();
}
public static void FlipH() { Doc?.FlipH(); Desk.Touch(); GameFlow.Bump(); }
public static void FlipV() { Doc?.FlipV(); Desk.Touch(); GameFlow.Bump(); }
public static void Rotate() { Doc?.Rotate90(); Desk.Touch(); GameFlow.Bump(); }
public static void ToggleMirror()
{
Mirror = !Mirror;
GameFlow.ShowToast( Mirror ? "mirror on" : "mirror off" );
GameFlow.Bump();
}
public static void ReplaceUnderCursor()
{
var doc = Doc;
if ( doc is null )
return;
var from = (byte)doc.IndexAt( HoverPx, HoverPy );
doc.Replace( from, Fg );
Desk.Touch();
GameFlow.Bump();
}
public static void Pointer( int px, int py, bool left, bool right, bool down, bool released )
{
var doc = Doc;
if ( doc is null )
return;
if ( DeskNet.GuestLocked )
return;
HoverPx = px;
HoverPy = py;
if ( StudioMouse.AltHeld )
{
if ( down || left || right )
{
var picked = (byte)doc.IndexAt( px, py );
if ( right ) Bg = picked;
else Fg = picked;
GameFlow.Bump();
}
return;
}
var index = right ? Bg : Fg;
if ( right && Tool != PushTool.Sip )
index = 0;
if ( down )
{
_stroke = true;
_sx = px;
_sy = py;
_lx = px;
_ly = py;
doc.BeginStroke();
switch ( Tool )
{
case PushTool.Sip:
var picked = (byte)doc.IndexAt( px, py );
if ( right ) Bg = picked;
else Fg = picked;
GameFlow.Bump();
break;
case PushTool.Flood:
doc.Flood( px, py, index );
break;
case PushTool.Push:
doc.StampBrush( px, py, index, Brush, Circle );
break;
}
Desk.Touch();
return;
}
if ( !_stroke )
return;
if ( left || right )
{
if ( Tool == PushTool.Push )
doc.Line( _lx, _ly, px, py, index, Brush, Circle );
_lx = px;
_ly = py;
Desk.Touch();
return;
}
if ( released )
{
if ( Tool == PushTool.Block )
doc.Rect( _sx, _sy, px, py, index, FillShape, Brush );
else if ( Tool == PushTool.Line )
doc.Line( _sx, _sy, px, py, index, Brush, Circle );
else if ( Tool == PushTool.Oval )
doc.Ellipse( _sx, _sy, px, py, index, FillShape, Brush );
_stroke = false;
Desk.Touch();
GameFlow.Bump();
}
}
public static bool Preview( out int x0, out int y0, out int x1, out int y1 )
{
x0 = _sx;
y0 = _sy;
x1 = _lx;
y1 = _ly;
return _stroke && (Tool == PushTool.Block || Tool == PushTool.Line || Tool == PushTool.Oval);
}
public static void ToggleOnion()
{
Onion = !Onion;
GameFlow.ShowToast( Onion ? "onion on" : "onion off" );
GameFlow.Bump();
}
public static void TogglePlay()
{
Playing = !Playing;
_playT = 0;
GameFlow.Bump();
}
public static void AddFrame()
{
Doc?.AddFrame();
Playing = false;
GameFlow.ShowToast( $"frame {(Doc?.Frame ?? 0) + 1}/{Doc?.FrameCount ?? 1}" );
GameFlow.Bump();
}
public static void DeleteFrame()
{
Doc?.DeleteFrame();
Playing = false;
GameFlow.Bump();
}
public static void PrevFrame()
{
var doc = Doc;
if ( doc is null )
return;
Playing = false;
doc.SetFrame( doc.Frame == 0 ? doc.FrameCount - 1 : doc.Frame - 1 );
GameFlow.Bump();
}
public static void NextFrame()
{
var doc = Doc;
if ( doc is null )
return;
Playing = false;
doc.SetFrame( (doc.Frame + 1) % doc.FrameCount );
GameFlow.Bump();
}
public static void CycleTool( int dir )
{
var i = Array.IndexOf( ToolOrder, Tool );
if ( i < 0 ) i = 0;
i = (i + dir + ToolOrder.Length) % ToolOrder.Length;
SelectTool( ToolOrder[i] );
}
public static void Tick()
{
if ( GameFlow.Screen != GameScreen.Pixel || Doc is null )
return;
TickKeys();
TickPad();
TickPlay();
}
static void TickKeys()
{
try
{
if ( Key( "z" ) ) Undo();
if ( Key( "y" ) ) Redo();
if ( Key( "o" ) ) ToggleOnion();
if ( Key( "n" ) ) AddFrame();
if ( Key( "backspace" ) || Key( "del" ) ) DeleteFrame();
if ( Key( "," ) ) PrevFrame();
if ( Key( "." ) ) NextFrame();
if ( Key( "-" ) ) SetBrush( Brush - 1 );
if ( Key( "=" ) ) SetBrush( Brush + 1 );
if ( Key( "g" ) ) { Grid = !Grid; GameFlow.Bump(); }
if ( Key( "x" ) ) SwapFgBg();
if ( Key( "h" ) ) FlipH();
if ( Key( "v" ) ) FlipV();
if ( Key( "r" ) ) Rotate();
if ( Key( "m" ) ) ToggleMirror();
if ( Key( "p" ) ) ReplaceUnderCursor();
}
catch
{
// raw keys not always available
}
if ( Input.Pressed( "Jump" ) )
TogglePlay();
if ( Input.Pressed( "SlotPrev" ) )
CycleTool( -1 );
if ( Input.Pressed( "SlotNext" ) )
CycleTool( 1 );
if ( Input.Pressed( "Reload" ) )
Undo();
if ( Input.Pressed( "Use" ) )
SelectTool( PushTool.Sip );
if ( Input.Pressed( "View" ) )
ToggleOnion();
}
static bool Key( string name )
{
try
{
return Input.Keyboard.Pressed( name );
}
catch
{
return false;
}
}
static void TickPad()
{
if ( !Input.UsingController )
{
PadAim = false;
_paintHold = 0f;
return;
}
if ( CrtView.PointerOn )
{
_paintHold = 0f;
return;
}
var move = Input.AnalogMove;
var ax = move.y;
var ay = -move.x;
if ( MathF.Abs( ax ) < 0.35f ) ax = 0;
if ( MathF.Abs( ay ) < 0.35f ) ay = 0;
if ( ax == 0 && ay == 0 )
{
_padAccX = 0;
_padAccY = 0;
}
else
{
PadAim = true;
var speed = 9f * Time.Delta * (Input.Down( "Run" ) ? 2.2f : 1f);
_padAccX += ax * speed;
_padAccY += ay * speed;
var doc = Doc;
while ( MathF.Abs( _padAccX ) >= 1f )
{
CursorX = Math.Clamp( CursorX + Math.Sign( _padAccX ), 0, doc.Width - 1 );
_padAccX -= Math.Sign( _padAccX );
}
while ( MathF.Abs( _padAccY ) >= 1f )
{
CursorY = Math.Clamp( CursorY + Math.Sign( _padAccY ), 0, doc.Height - 1 );
_padAccY -= Math.Sign( _padAccY );
}
GameFlow.Bump();
}
if ( !PadAim && !Input.UsingController )
return;
var paint = Input.Down( "Attack1" );
var erase = Input.Down( "Attack2" );
if ( paint || erase )
{
var down = _paintHold <= 0f;
Pointer( CursorX, CursorY, paint, erase, down, false );
_paintHold += Time.Delta;
Coach.OnPaint();
}
else if ( _paintHold > 0f )
{
Pointer( CursorX, CursorY, false, false, false, true );
_paintHold = 0;
}
}
static void TickPlay()
{
if ( !Playing || Doc is null || Doc.FrameCount < 2 )
return;
_playT += Time.Delta;
var step = 1f / Math.Clamp( Fps, 2, 24 );
if ( _playT < step )
return;
_playT = 0;
Doc.SetFrame( (Doc.Frame + 1) % Doc.FrameCount );
GameFlow.Bump();
}
}