Search the source of every open source package.
3956 results
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Twitch Poop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "twitchpoop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "garry" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "garry.twitchpoop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "6/6/2026 7:39:31 PM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "25" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]
[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.128.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.128.0")]@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox
@*
The floating name tag above an avatar. StreamPlayer.Setup() points its Viewer at the right viewer,
and we render their display name in their chat colour.
*@
<root>
<div class="username" style="@NameStyle">
@Viewer.DisplayName
</div>
</root>
@code
{
/// <summary>
/// The viewer this tag is labelling. Set by StreamPlayer when the avatar is spawned.
/// </summary>
public Streamer.Viewer Viewer { get; set; }
/// <summary>
/// Colour the name with the viewer's chat colour, if they have one set.
/// </summary>
string NameStyle => Viewer.Color.HasValue ? $"color: {Viewer.Color.Value.Hex}" : null;
/// <summary>
/// PanelComponent rebuilds the markup whenever this hash changes - so include anything we display.
/// </summary>
protected override int BuildHash() => System.HashCode.Combine( Viewer.DisplayName, Viewer.Color );
}
.switchcontrol
{
flex-direction: row;
width: 100px;
min-height: 24px;
align-items: center;
cursor: pointer;
.switch-frame
{
flex-grow: 0;
flex-shrink: 1;
width: 48px;
height: 16px;
background-color: #fff1;
margin: 0px 5px;
align-items: center;
border-radius: 100px;
transition: all 0.4s linear;
.switch-inner
{
position: relative;
flex-grow: 0;
flex-shrink: 1;
background-color: #999;
width: 25px;
height: 25px;
border-radius: 100px;
left: 20%;
transform: translateX( -50% );
transition: all 0.3s ease-out;
}
}
&.active
{
.switch-frame
{
background-color: #fffa;
}
.switch-inner
{
left: 80%;
background-color: #fff;
}
}
}
ColorAlphaControl
{
gap: 0.5rem;
flex-grow: 1;
pointer-events: all;
background: linear-gradient( to right, black, white );
border-radius: 4px;
padding: 2px;
height: 12px;
position: relative;
cursor: pointer;
border: 1px solid #333;
&:hover
{
border: 1px solid #08f;
}
&:active
{
border: 1px solid #fff;
}
.handle
{
top: -5px;
bottom: -5px;
aspect-ratio: 1;
border-radius: 100px;
border: 2px solid #444;
position: absolute;
background-color: white;
box-shadow: 2px 2px 16px #000a;
transform: translateX( -50% );
pointer-events: none;
}
}
ColorSaturationValueControl
{
width: 240px;
height: 240px;
background-color: red;
position: relative;
border-radius: 4px;
cursor: pointer;
border: 1px solid #333;
&:hover
{
border: 1px solid #08f;
}
&:active
{
border: 1px solid #fff;
}
.handle
{
width: 16px;
height: 16px;
border-radius: 100px;
border: 2px solid #444;
position: absolute;
background-color: white;
box-shadow: 2px 2px 16px #000a;
transform: translateX( -50% ) translateY( -50% );
pointer-events: none;
z-index: 100;
z-index: 100;
}
.gradient
{
position: absolute;
width: 100%;
height: 100%;
border-radius: 4px;
background: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );
&:after
{
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 4px;
background: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );
}
}
}
.slidercontrol
{
flex-direction: row;
min-width: 50px;
position: relative;
flex-shrink: 0;
flex-direction: row;
cursor: pointer;
gap: 8px;
flex-grow: 1;
align-items: center;
pointer-events: all;
> .inner
{
flex-direction: column;
flex-shrink: 1;
flex-grow: 1;
min-height: 32px;
justify-content: center;
> .values
{
width: 100%;
pointer-events: none;
font-size: 14px;
color: #aaa;
> .left
{
flex-grow: 1;
}
}
> .track
{
position: relative;
background-color: #888;
height: 7px;
margin: 8px;
align-items: center;
border-radius: 4px;
> .track-active
{
background-color: #fff;
position: absolute;
height: 100%;
left: 0px;
border-radius: 4px;
}
> .thumb
{
position: relative;
background-color: #fff;
border-radius: 100px;
width: 16px;
height: 16px;
transform: translateX( -50% );
}
}
}
> .entry
{
flex-shrink: 0;
flex-grow: 0;
width: 50px;
> numberentry
{
background-color: transparent;
> .content-label
{
padding: 0 4px;
}
}
}
}
.slidercontrol .value-tooltip
{
position: absolute;
bottom: 150%;
left: -8px;
z-index: 1000;
flex-direction: column;
> .label
{
background-color: black;
padding: 8px 12px;
border-radius: 8px;
}
>.tail
{
bottom: -0px;
background-color: black;
width: 10px;
height: 10px;
transform: rotateZ(45 deg) translateX( 4px );
position: absolute;
}
}using System;
using Sandbox;
namespace Goo.Animation;
public record struct SmoothVector2
{
public Vector2 Current;
public Vector2 Target;
public Vector2 Velocity;
public float SmoothTime;
public SmoothVector2(Vector2 initial, float smoothTime)
{
Current = initial;
Target = initial;
Velocity = default;
SmoothTime = smoothTime;
}
public void Update(float dt)
{
float vx = Velocity.x, vy = Velocity.y;
Current = new Vector2(
MathX.SmoothDamp(Current.x, Target.x, ref vx, SmoothTime, dt),
MathX.SmoothDamp(Current.y, Target.y, ref vy, SmoothTime, dt));
Velocity = new Vector2(vx, vy);
}
public bool IsSettled =>
MathF.Abs(Target.x - Current.x) < 0.0001f &&
MathF.Abs(Target.y - Current.y) < 0.0001f &&
MathF.Abs(Velocity.x) < 0.0001f &&
MathF.Abs(Velocity.y) < 0.0001f;
/// <summary>Advances by dt and returns true while still moving; chain calls with | (not ||) so every damper advances each frame.</summary>
public bool Tick(float dt) { Update(dt); return !IsSettled; }
}
using Sandbox;
using Sandbox.Rendering;
using Sandbox.UI;
namespace Goo;
// Style helpers hoisted so generated Blob facades share them. Keep the early-return form: an engine-type ternary that resolves bare null via an implicit string operator silently produces magenta (Color.Parse fallback) instead of the intended absent-property. See engine-fact memories.
internal static class StyleAccumulator
{
static StyleList Rent(StyleList current)
=> ReferenceEquals(current, StyleList.Empty)
? BuildContext.Current.RentStyleList()
: current;
public static StyleList Add(StyleList current, StyleField field, Length? value)
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromLength(value.Value));
return list;
}
public static StyleList Add<TEnum>(StyleList current, StyleField field, TEnum? value, System.Func<TEnum, StyleValue> wrap) where TEnum : struct
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, wrap(value.Value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, Color? value, System.Func<Color, StyleValue> wrap)
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, wrap(value.Value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, string? value)
{
if (value is null) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromString(value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, float? value)
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromSingle(value.Value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, bool? value)
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromBoolean(value.Value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, int? value)
{
if (!value.HasValue) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromInt32(value.Value));
return list;
}
public static StyleList Add(StyleList current, StyleField field, Texture? value)
{
if (value is null) return current;
var list = Rent(current);
list.Add(field, StyleValue.FromTexture(value));
return list;
}
}
using System;
using Sandbox;
namespace Goo.FpsUI;
// ============================================================================
// ALL demo / self-preview behaviour for the FPS UI pack lives in this ONE file.
//
// DELETE THIS FILE to remove demo functionality.
// deleting this file makes every Demo inspector toggle disappear
// ============================================================================
static class FpsDemo
{
static readonly string[] Names = { "Vex", "Korr", "Juno", "Rhys", "Mara", "Dane", "Iko", "Sol" };
static readonly (int Amount, string Action)[] Grants =
{
(100, "Kill"), (125, "Headshot"), (50, "Assist"), (200, "Double Kill"), (25, "Hitmarker"),
};
public static void Health( HealthModel m, ref float t, float dt ) // bleed in chunks, big heal when low
{
t += dt;
if ( t < 2.4f ) return;
t = 0f;
if ( m.Health <= m.MaxHealth * 0.3f ) m.Heal( m.MaxHealth * 0.65f );
else m.Damage( m.MaxHealth * 0.22f );
}
public static void Stamina( StaminaModel m, ref float t, float dt ) // sprint on/off cycle
{
t += dt;
if ( t >= 3.1f ) { t = 0f; m.SetSprinting( !m.Sprinting ); }
if ( m.Stamina <= 0f ) m.SetSprinting( false );
}
public static void Ammo( AmmoModel m, ref float t, float dt ) // steady fire, auto-reload on empty
{
t += dt;
if ( m.Reloading ) return;
if ( m.Mag <= 0 ) { m.Reload(); return; }
if ( t >= 0.18f ) { t = 0f; m.Fire(); }
}
public static void Crosshair( CrosshairModel m, ref float t, float dt ) // steady fire cadence
{
t += dt;
if ( t >= 0.18f ) { t = 0f; m.Fire(); }
}
public static void Hitmarker( HitmarkerModel m, ref float t, float dt ) // periodic pop
{
t += dt;
if ( t >= 0.5f ) { t = 0f; m.Pop( false ); }
}
public static void Killfeed( KillfeedModel m, ref float t, ref int pick, float dt ) // periodic fake kills
{
t += dt;
if ( t < 2.2f ) return;
t = 0f;
string a = Names[pick % Names.Length];
string v = Names[(pick + 3) % Names.Length];
// Alternate teams, occasionally make the local player the killer so the preview shows both.
bool youKill = pick % 3 == 0;
KillTeam at = youKill || pick % 2 == 0 ? KillTeam.Friendly : KillTeam.Enemy;
KillTeam vt = at == KillTeam.Friendly ? KillTeam.Enemy : KillTeam.Friendly;
pick++;
m.Add( a, at, v, vt, attackerLocal: youKill );
}
public static void Xp( XpModel m, ref float t, ref int pick, float dt ) // periodic grants
{
t += dt;
if ( t < 1.6f ) return;
t = 0f;
var g = Grants[pick % Grants.Length];
pick++;
m.Add( g.Amount, g.Action );
}
public static void Scoreboard( ScoreboardModel m, ref float t, float dt ) // loop the clock, nudge scores, cycle variant
{
if ( m.TimeRemaining <= 0f ) m.TimeRemaining = 600f;
t += dt;
if ( t < 4f ) return;
t = 0f;
m.Mode = m.Mode switch
{
ScoreboardMode.Tdm => ScoreboardMode.Domination,
ScoreboardMode.Domination => ScoreboardMode.Ffa,
_ => ScoreboardMode.Tdm,
};
m.FriendlyScore = (m.FriendlyScore + 7) % (m.ScoreLimit + 1);
m.EnemyScore = (m.EnemyScore + 5) % (m.ScoreLimit + 1);
m.PlayerScore += 150;
m.LeaderScore = Math.Max( m.LeaderScore, m.PlayerScore ) + 50;
for ( int i = 0; i < m.Points.Length; i++ ) // rotate cap ownership
m.Points[i] = (CapOwner)(((int)m.Points[i] + 1) % 3);
}
}
// ---- per-widget demo hooks (the [Property] Demo toggle + timers + StepDemo body) ----
public sealed partial class HealthWidget
{
[Property] public bool Demo { get; set; } = true; // self-animate in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Health( _m, ref _demoT, dt );
}
}
public sealed partial class StaminaWidget
{
[Property] public bool Demo { get; set; } = true; // self-animate in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Stamina( _m, ref _demoT, dt );
}
}
public sealed partial class AmmoWidget
{
[Property] public bool Demo { get; set; } = true; // self-fire in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Ammo( _m, ref _demoT, dt );
}
}
public sealed partial class CrosshairWidget
{
[Property] public bool Demo { get; set; } = true; // self-fire in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Crosshair( _m, ref _demoT, dt );
}
}
public sealed partial class HitmarkerWidget
{
[Property] public bool Demo { get; set; } = true; // periodic pop in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Hitmarker( _m, ref _demoT, dt );
}
}
public sealed partial class KillfeedWidget
{
[Property] public bool Demo { get; set; } = true; // fake kills in editor
float _demoT;
int _demoPick;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Killfeed( _m, ref _demoT, ref _demoPick, dt );
}
}
public sealed partial class XpWidget
{
[Property] public bool Demo { get; set; } = true; // fake grants in editor
float _demoT;
int _demoPick;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Xp( _m, ref _demoT, ref _demoPick, dt );
}
}
public sealed partial class ScoreboardWidget
{
[Property] public bool Demo { get; set; } = true; // self-animate in editor
float _demoT;
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
FpsDemo.Scoreboard( _m, ref _demoT, dt );
}
}
// ---- the assembled HUD: one coordinated synthetic firefight ----
public sealed partial class FpsHud
{
[Property, Group( "General" )] public bool Demo { get; set; } = true; // self-run a synthetic firefight in editor
readonly Random _demoRng = new();
float _hpT, _stT, _kfT, _xpT, _sbT; // per-subsystem demo timers
int _kfPick, _xpPick; // demo name / grant cursors
float _triggerT, _modeT; // synthetic trigger square-wave + fire-mode cycle
partial void StepDemo( float dt, ref bool active )
{
if ( !Demo ) return;
active = true;
// Cycle the fire mode every few seconds, then drive a synthetic trigger square-wave (held ~1s,
// released ~0.4s) through the real fire control so each mode visibly fires differently.
_modeT -= dt;
if ( _modeT <= 0f ) { _modeT = 3.0f; CycleFireMode(); }
_fire.Mode = FireMode; _fire.BurstCount = BurstCount; _fire.Rpm = RoundsPerMinute;
_triggerT += dt;
bool trigger = _triggerT % 1.4f < 1.0f;
if ( !_ammo.Reloading )
{
if ( _ammo.Mag <= 0 ) _ammo.Reload();
else if ( _fire.Tick( trigger, dt ) )
{
_ammo.Fire(); _crosshair.Fire();
if ( _demoRng.NextSingle() < 0.5f ) _hitmarker.Pop( _demoRng.NextSingle() < 0.15f );
}
}
FpsDemo.Health( _health, ref _hpT, dt );
FpsDemo.Stamina( _stamina, ref _stT, dt );
FpsDemo.Killfeed( _killfeed, ref _kfT, ref _kfPick, dt );
FpsDemo.Xp( _xp, ref _xpT, ref _xpPick, dt );
if ( ShowScoreboard ) FpsDemo.Scoreboard( _scoreboard, ref _sbT, dt );
}
}
using System;
using Sandbox;
namespace Goo.FpsUI;
// A drop-in shootable target for the FPS pack. Attach to any GameObject that has a Collider; the HUD's
// hitscan damages it through the engine's Component.IDamageable interface (the same path TriggerHurt uses).
// Exposes Health / IsDead / Name so FpsHud can drive the hitmarker, killfeed, and XP on a hit or kill.
// Engine-side component, not a Goo panel.
[Title( "FPS Target" ), Category( "FPS UI" ), Icon( "my_location" )]
public sealed class FpsTarget : Component, Component.IDamageable
{
[Property] public string Name { get; set; } = "Target"; // shown as the victim in the killfeed
[Property, Range( 1f, 1000f )] public float MaxHealth { get; set; } = 100f; // health restored on enable / respawn
[Property] public bool RespawnOnDeath { get; set; } = true; // refill health a moment after dying
[Property, Range( 0.5f, 10f )] public float RespawnDelay { get; set; } = 3f; // seconds dead before respawning
public float Health { get; private set; }
public bool IsDead => Health <= 0f;
float _respawnAt;
protected override void OnEnabled() => Health = MaxHealth;
// Engine damage entry point: TriggerHurt volumes and the HUD's hitscan both call this.
public void OnDamage( in DamageInfo damage )
{
if ( IsDead ) return; // already down, ignore extra hits
Health = Math.Max( 0f, Health - damage.Damage );
if ( IsDead && RespawnOnDeath ) _respawnAt = Time.Now + RespawnDelay;
}
protected override void OnUpdate()
{
if ( IsDead && RespawnOnDeath && Time.Now >= _respawnAt ) Health = MaxHealth;
}
}
using System;
namespace Goo.FpsUI;
// Compass logic: a 0..360 heading plus the shortest signed angular delta the view uses to
// place cardinal ticks. Engine-free.
public sealed class CompassModel
{
public float Heading { get; private set; } // current yaw, normalized 0..360
public void SetHeading( float yawDeg ) // set heading from game code (any range)
=> Heading = ((yawDeg % 360f) + 360f) % 360f;
// Signed delta from `from` to `to`, in (-180, 180]. Used to position ticks around center.
public static float ShortestDelta( float from, float to ) => ((to - from + 540f) % 360f) - 180f;
public bool Tick( float dt ) => false; // heading changes drive rebuilds, not the damper
}
using System;
using Goo;
using Sandbox;
using Sandbox.UI;
using PanelTransform = Goo.PanelTransform; // rule 18: ambiguous with Sandbox.UI otherwise
namespace Goo.FpsUI;
// Shared, stateless view helpers. Each returns a fresh Container (never cache a Container).
static class Parts
{
public enum Corner { TopLeft, TopCenter, TopRight, BottomLeft, BottomRight, Center }
// Absolute fill spanning `frac` (0..1) of its Relative parent's width, full height.
public static Container FillRect( string key, float frac, Color color, float radius ) => new()
{
Key = key, Position = PositionMode.Absolute, Top = 0, Left = 0,
Height = Length.Percent( 100 ),
Width = Length.Percent( Math.Clamp( frac, 0f, 1f ) * 100f ),
BorderRadius = radius, BackgroundColor = color,
};
// Absolute full-cover tint at a given opacity (flashes, warnings).
public static Container Overlay( string key, Color color, float opacity, float radius ) => new()
{
Key = key, Position = PositionMode.Absolute, Top = 0, Left = 0,
Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
BorderRadius = radius, BackgroundColor = color, Opacity = opacity,
};
// Pin `child` to a screen corner/center inside a Relative, full-size root. `m` is the edge inset.
// Container properties are init-only (record struct), so we use `with` expressions per corner.
public static Container Anchor( string key, Corner c, float m, Container child )
{
var box = new Container
{
Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
Children = { child },
};
return c switch
{
Corner.TopLeft => box with { Top = m, Left = m },
Corner.TopRight => box with { Top = m, Right = m },
Corner.BottomLeft => box with { Bottom = m, Left = m },
Corner.BottomRight => box with { Bottom = m, Right = m },
Corner.TopCenter => box with
{
Top = m, Left = Length.Percent( 50 ),
Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Length.Percent( 0 ) ?? default ),
},
_ => box with // Center
{
Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Length.Percent( -50 ) ?? default ),
},
};
}
// Pin `child` horizontally centered, offset `dy` px from the screen center (positive = down, negative = up).
public static Container CenterOffset( string key, float dy, Container child ) => new()
{
Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Px.Of( dy ) ),
Children = { child },
};
// Pin `child`'s top-left corner at a pixel offset from the screen center (positive dx = right, positive dy = down).
public static Container Offset( string key, float dx, float dy, Container child ) => new()
{
Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
Transform = PanelTransform.Translate( Px.Of( dx ), Px.Of( dy ) ),
Children = { child },
};
// Semi-opaque rounded backing behind a HUD readout, so text/bars stay legible over the world.
// Wraps one child and sizes to it, all card styling comes from the theme.
public static Container Panel( string key, FpsTheme t, Container child ) => new()
{
Key = key, Position = PositionMode.Relative,
Padding = t.PanelPad, BorderRadius = t.PanelRadius,
BackgroundColor = t.BackingBg,
Children = { child },
};
// A full-size, pointer-through, Relative root used by every standalone widget and the FpsHud.
public static Container Root( string key ) => new()
{
Key = key, Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
Position = PositionMode.Relative, PointerEvents = PointerEvents.None,
};
}
using System;
using Goo;
using Sandbox;
using Sandbox.UI;
namespace Goo.FpsUI;
// Stateless presenter: the secondary (stamina/armor) bar.
static class StaminaView
{
public const float TrackW = 320f, TrackH = 10f;
public static Container Build( StaminaModel m, FpsTheme t )
{
float frac = m.ShownFraction;
Color fill = Color.Lerp( t.Warn, t.Good, frac );
var track = new Container
{
Key = "stTrack", Position = PositionMode.Relative,
Width = TrackW, Height = TrackH, BackgroundColor = t.TrackBg,
BorderRadius = t.Radius, Overflow = OverflowMode.Hidden,
Children = { Parts.FillRect( "fill", frac, fill, t.Radius ) },
};
if ( m.Flash > 0.001f ) track.Children.Add( Parts.Overlay( "flash", Color.White, m.Flash * 0.8f, t.Radius ) );
return track;
}
}
// Standalone stamina bar. Call Sprinting(bool) from your movement code each frame.
public sealed partial class StaminaWidget : GooPanel<Container>
{
[Property, Range( 1f, 1000f )] public float MaxStamina { get; set; } = 100f; // full-bar value
[Property, Range( 0.05f, 2f )] public float DrainRate { get; set; } = 0.55f; // fraction of max drained per second while sprinting
[Property] public PlayerController? Player { get; set; } // player to gate sprint on when stamina is empty (null = skip gating)
readonly StaminaModel _m = new();
readonly FpsTheme _t = new();
bool _booted;
bool _sprintBlocked;
float _cachedRunSpeed;
void Boot() { _m.MaxStamina = MaxStamina; _m.Reset(); _booted = true; }
public void Sprinting( bool on ) => _m.SetSprinting( on ); // toggle sprint drain
// Demo-only seam: implemented in FpsDemo.cs, compiles out when that file is deleted.
partial void StepDemo( float dt, ref bool active );
protected override bool Tick( float dt )
{
if ( !_booted ) Boot();
_m.DrainRate = DrainRate; // live-tunable
bool demo = false;
StepDemo( dt, ref demo );
if ( !demo )
{
Sprinting( Sandbox.Input.Down( "run" ) );
FpsInput.ApplySprintGate( Player, _m.Stamina, ref _sprintBlocked, ref _cachedRunSpeed );
}
bool moving = _m.Tick( dt );
return demo || moving;
}
protected override Container Build()
{
if ( !_booted ) Boot();
var root = Parts.Root( "fpsStamina" );
// Sits just above where the health bar would be (health height + gap).
root.Children.Add( Parts.Anchor( "a", Parts.Corner.BottomLeft, _t.Margin + HealthView.TrackH + 10f, Parts.Panel( "bg", _t, StaminaView.Build( _m, _t ) ) ) );
return root;
}
}
using System;
using Sandbox;
using Sandbox.UI;
namespace Goo.Internal;
internal sealed class StatefulShapePanel : Panel, IStatefulEventHost
{
internal Action<MousePanelEvent>? _onClick;
internal Action<MousePanelEvent>? _onRightClick;
internal Action<MousePanelEvent>? _onMiddleClick;
internal Action<MousePanelEvent>? _onMouseEnter;
internal Action<MousePanelEvent>? _onMouseLeave;
internal Action<MousePanelEvent>? _onMouseDown;
internal Action<MousePanelEvent>? _onMouseUp;
internal Action<MousePanelEvent>? _onMouseMove;
internal bool _userSetPointerEvents;
internal Action? _requestRebuild;
public Action? RequestRebuild { set => _requestRebuild = value; }
public void ApplyEvents(in BlobEvents events)
{
_onClick = events.OnClick;
_onRightClick = events.OnRightClick;
_onMiddleClick = events.OnMiddleClick;
_onMouseEnter = events.OnMouseEnter;
_onMouseLeave = events.OnMouseLeave;
_onMouseDown = events.OnMouseDown;
_onMouseUp = events.OnMouseUp;
_onMouseMove = events.OnMouseMove;
}
public bool HasEventHandlers =>
_onClick != null || _onRightClick != null || _onMiddleClick != null || _onMouseEnter != null || _onMouseLeave != null ||
_onMouseDown != null || _onMouseUp != null || _onMouseMove != null;
public bool UserSetPointerEvents
{
get => _userSetPointerEvents;
set => _userSetPointerEvents = value;
}
// Apply a baked alpha texture as BackgroundImage. The shape color comes from
// BackgroundColor flowing through the engine's BgTint multiplier; this method
// is only responsible for the alpha mask.
public void ApplyShape(BlobKind kind, in ShapeParams shape)
{
Style.BackgroundImage = ShapeTextureCache.GetOrBake(kind, in shape);
Style.BackgroundSizeX = Length.Percent(100);
Style.BackgroundSizeY = Length.Percent(100);
}
public void ApplyPolygon(Vector2[] points)
{
Style.BackgroundImage = ShapeTextureCache.GetOrBakePolygon(points);
Style.BackgroundSizeX = Length.Percent(100);
Style.BackgroundSizeY = Length.Percent(100);
}
protected override void OnClick(MousePanelEvent e) { base.OnClick(e); EventDispatch.Fire(_onClick, e, _requestRebuild); }
protected override void OnRightClick(MousePanelEvent e) { base.OnRightClick(e); EventDispatch.Fire(_onRightClick, e, _requestRebuild); }
protected override void OnMiddleClick(MousePanelEvent e) { base.OnMiddleClick(e); EventDispatch.Fire(_onMiddleClick, e, _requestRebuild); }
protected override void OnMouseOver(MousePanelEvent e) { base.OnMouseOver(e); EventDispatch.Fire(_onMouseEnter, e, _requestRebuild); }
protected override void OnMouseOut(MousePanelEvent e) { base.OnMouseOut(e); EventDispatch.Fire(_onMouseLeave, e, _requestRebuild); }
protected override void OnMouseDown(MousePanelEvent e) { base.OnMouseDown(e); EventDispatch.Fire(_onMouseDown, e, _requestRebuild); }
protected override void OnMouseUp(MousePanelEvent e) { base.OnMouseUp(e); EventDispatch.Fire(_onMouseUp, e, _requestRebuild); }
protected override void OnMouseMove(MousePanelEvent e) { base.OnMouseMove(e); EventDispatch.Fire(_onMouseMove, e, _requestRebuild); }
}
using System;
using Sandbox.UI;
namespace Goo.Internal;
internal sealed class StatefulSvgPanel : Sandbox.UI.SvgPanel, IStatefulEventHost
{
internal Action<MousePanelEvent>? _onClick;
internal Action<MousePanelEvent>? _onRightClick;
internal Action<MousePanelEvent>? _onMiddleClick;
internal Action<MousePanelEvent>? _onMouseEnter;
internal Action<MousePanelEvent>? _onMouseLeave;
internal Action<MousePanelEvent>? _onMouseDown;
internal Action<MousePanelEvent>? _onMouseUp;
internal Action<MousePanelEvent>? _onMouseMove;
internal bool _userSetPointerEvents;
internal Action? _requestRebuild;
public Action? RequestRebuild { set => _requestRebuild = value; }
public void ApplyEvents(in BlobEvents events)
{
_onClick = events.OnClick;
_onRightClick = events.OnRightClick;
_onMiddleClick = events.OnMiddleClick;
_onMouseEnter = events.OnMouseEnter;
_onMouseLeave = events.OnMouseLeave;
_onMouseDown = events.OnMouseDown;
_onMouseUp = events.OnMouseUp;
_onMouseMove = events.OnMouseMove;
}
public bool HasEventHandlers =>
_onClick != null || _onRightClick != null || _onMiddleClick != null || _onMouseEnter != null || _onMouseLeave != null ||
_onMouseDown != null || _onMouseUp != null || _onMouseMove != null;
public bool UserSetPointerEvents
{
get => _userSetPointerEvents;
set => _userSetPointerEvents = value;
}
protected override void OnClick(MousePanelEvent e) { base.OnClick(e); EventDispatch.Fire(_onClick, e, _requestRebuild); }
protected override void OnRightClick(MousePanelEvent e) { base.OnRightClick(e); EventDispatch.Fire(_onRightClick, e, _requestRebuild); }
protected override void OnMiddleClick(MousePanelEvent e) { base.OnMiddleClick(e); EventDispatch.Fire(_onMiddleClick, e, _requestRebuild); }
protected override void OnMouseOver(MousePanelEvent e) { base.OnMouseOver(e); EventDispatch.Fire(_onMouseEnter, e, _requestRebuild); }
protected override void OnMouseOut(MousePanelEvent e) { base.OnMouseOut(e); EventDispatch.Fire(_onMouseLeave, e, _requestRebuild); }
protected override void OnMouseDown(MousePanelEvent e) { base.OnMouseDown(e); EventDispatch.Fire(_onMouseDown, e, _requestRebuild); }
protected override void OnMouseUp(MousePanelEvent e) { base.OnMouseUp(e); EventDispatch.Fire(_onMouseUp, e, _requestRebuild); }
protected override void OnMouseMove(MousePanelEvent e) { base.OnMouseMove(e); EventDispatch.Fire(_onMouseMove, e, _requestRebuild); }
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.button.popupbutton.dropdown
{
cursor: pointer;
transition: all .1s ease-out;
position: relative;
> .dropdown_indicator
{
position: absolute;
right: 8px;
}
&.open
{
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
transition: border-radius 0.2s ease-out;
}
}
select
{
min-height: 40px;
> option
{
display: none;
}
}.navigator-body
{
&.hidden
{
display: none;
}
}
@import "base/_splitcontainer.scss";
@import "base/_navigator.scss";
button
{
cursor: pointer;
}
IconPanel
{
font-family: Material Icons;
}
.is-half
{
width: 50%;
}
.is-third
{
width: 33%;
}
.is-quarter
{
width: 25%;
}
button.has-subtitle
{
position: relative;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding-left: 40px; // icon space
.iconpanel
{
position: absolute;
left: 5px;
top: 0;
bottom: 0;
align-items: center;
}
.button-label
{
font-weight: bold;
}
.button-subtitle
{
font-size: 12px;
opacity: 0.5;
mix-blend-mode: lighten;
}
}.package-list
{
flex-shrink: 1;
flex-wrap: wrap;
flex-grow: 1;
h1
{
width: 100%;
margin-top: 50px;
font-size: 40px;
}
PackageCard
{
&:hover
{
sound-in: "ui.button.over";
}
}
VirtualGrid
{
width: 100%;
height: 100%;
.cell
{
}
}
}.dropdown
{
gap: 2px;
flex-grow: 1;
cursor: pointer;
justify-content: flex-end;
align-items: center;
padding: 0px 12px;
.button-right-column
{
flex-grow: 1;
}
}
RootPanel
{
font-family: Roboto;
color: white;
}
IconPanel, i
{
font-family: Material Icons;
background-position: center;
background-size: contain;
}
.tooltip
{
background-color: rgba( black, 0.8 );
border: 1px solid rgba( #555, 0.1 );
padding: 10px;
border-radius: 4px;
color: white;
font-family: Inter;
font-weight: bold;
transition: opacity 0.09s ease;
opacity: 1;
margin: 0px;
&:intro, &:outro
{
opacity: 0;
}
}
.textentry
{
cursor: text;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow: hidden;
flex-direction: row;
position: relative;
padding: 10px;
&.disabled
{
cursor: default;
}
&.is-multiline
{
align-items: flex-start;
white-space: normal;
}
.content-label,
.placeholder
{
flex-grow: 1;
}
.placeholder
{
opacity: 0.2;
pointer-events: none;
}
.iconpanel
{
position: absolute;
right: 0;
padding-right: 10px;
pointer-events: none;
&.clearbutton
{
color: #fff;
pointer-events: all;
cursor: pointer;
opacity: 0.1;
&:hover
{
opacity: 0.5;
}
&:active
{
opacity: 1;
}
}
}
&:empty
{
.clearbutton
{
display: none;
}
}
&.has-icon
{
padding-right: 30px;
}
.content-label,
.prefix-label,
.suffix-label,
.placeholder
{
overflow: hidden;
}
.content-label
{
order: 1;
}
.suffix-label
{
order: 2;
}
.prefix-label,
.suffix-label
{
flex-shrink: 0;
opacity: 0.5;
}
&.invalid
{
box-shadow: 0px 0px 1px 2px #f8356b;
}
}
.popup-panel
{
background-color: #1b202c;
flex-direction: column;
color: #8e9ec2;
transition: all 0.1s ease-out;
position: absolute;
z-index: 2000;
min-height: 20px;
min-width: 10px;
overflow: scroll;
pointer-events: all;
box-shadow: 3px 3px 64px #000;
border-radius: 4px;
&.medium
{
max-width: 300px;
}
&.center-horizontal
{
transform-origin: top center;
}
&.below-center
{
transform: translateX( -50% );
transform-origin: 0% 0%;
}
&.above-left
{
transform-origin: 0% 100%;
}
&.above-center
{
transform: translateX( -50% );
transform-origin: 0% 100%;
}
&.left
{
transform-origin: 100% 50%;
transform: translate( 00% -50% );
}
&.right
{
transform-origin: 0 50%;
transform: translate( 0% -50% );
}
&:intro
{
transform: scale( 0 );
pointer-events: none;
opacity: 0;
}
&:outro
{
opacity: 0;
transition: all 0.1s ease-in;
margin-top: 50px;
pointer-events: none;
}
&.below-stretch
{
transform-origin: 0% 0%;
&:intro
{
transform: scaleY( 0.1 );
}
}
> .canvas
{
flex-direction: column;
}
.information
{
padding: 16px;
font-size: 14px;
opacity: 0.5;
}
button
{
padding: 2px 8px;
cursor: pointer;
font-family: Poppins;
font-weight: 600;
flex-shrink: 0;
&:hover
{
background-color: #08f;
color: #fff;
}
}
}
.slidercontrol
{
flex-direction: row;
min-width: 50px;
position: relative;
flex-shrink: 0;
flex-direction: row;
pointer-events: all;
cursor: pointer;
gap: 8px;
flex-grow: 1;
align-items: center;
> .inner
{
flex-direction: column;
flex-shrink: 1;
flex-grow: 1;
min-height: 32px;
> .values
{
width: 100%;
pointer-events: none;
font-size: 14px;
color: #aaa;
> .left
{
flex-grow: 1;
}
}
> .track
{
position: relative;
background-color: #888;
height: 7px;
margin: 8px;
align-items: center;
border-radius: 4px;
> .track-active
{
background-color: #fff;
position: absolute;
height: 100%;
left: 0px;
border-radius: 4px;
}
> .thumb
{
position: relative;
background-color: #fff;
border-radius: 100px;
width: 16px;
height: 16px;
transform: translateX( -50% );
}
}
}
>.entry
{
width: 50px;
flex-shrink: 0;
flex-grow: 0;
> textentry
{
text-align: right;
width: 100%;
border-radius: 4px;
padding: 0;
min-height: 0px;
> .content-label
{
padding: 0 4px;
}
}
}
}
.slidercontrol .value-tooltip
{
position: absolute;
bottom: 150%;
left: -8px;
z-index: 1000;
flex-direction: column;
> .label
{
background-color: black;
padding: 8px 12px;
border-radius: 8px;
}
>.tail
{
left: 8px;
background-color: black;
width: 10px;
height: 10px;
transform: rotateZ(45 deg);
}
}@import "base/_popup.scss";
@import "base/_splitcontainer.scss";
@import "base/_tabcontainer.scss";
@import "base/_navigator.scss";
@import "base/_textentry.scss";
.button
{
cursor: pointer;
}
IconPanel
{
font-family: Material Icons;
}
.is-half
{
width: 50%;
}
.is-third
{
width: 33%;
}
.is-quarter
{
width: 25%;
}
.button.has-subtitle
{
position: relative;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding-left: 40px; // icon space
.iconpanel
{
position: absolute;
left: 5px;
top: 0;
bottom: 0;
align-items: center;
}
.button-label
{
font-weight: bold;
}
.button-subtitle
{
font-size: 12px;
opacity: 0.5;
mix-blend-mode: lighten;
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.coloreditor
{
flex-direction: column;
min-width: 250px;
max-width: 250px;
> .canvas
{
border-top: 1px solid rgba( white, 0.5 );
flex-grow: 1;
flex-direction: column;
padding: 10px 15px;
> textentry.textentry
{
flex-grow: 0;
flex-shrink: 0;
width: auto;
}
}
.slider
{
padding: 6.5px;
}
.slider .track
{
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
transition: all 0.1s ease-out;
.inner
{
display: none;
}
}
.slider:hover, .slider:active
{
padding: 7px;
.track
{
height: 20px;
border-radius: 6px;
}
.thumb
{
width: 10px;
height: 16px;
margin-left: 2px;
}
}
.slider .thumb
{
width: 10px;
height: 10px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
backdrop-filter: invert( 1 );
margin-left: 1px;
pointer-events: none;
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
.hue .track
{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
.inner
{
display: none;
}
}
.satval
{
background: linear-gradient(to right, white, transparent);
height: 200px;
position: relative;
&:active
{
z-index: 10;
cursor: none;
}
.value_gradient
{
background: linear-gradient(to bottom, black, white);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
mix-blend-mode: multiply;
}
.thumb
{
z-index: 10;
}
}
.presets
{
flex-wrap: wrap;
align-content: center;
justify-content: center;
padding: 15px;
border-top: 1px solid rgba( white, 0.5 );
.preset
{
background-color: white;
width: 16px;
height: 16px;
margin: 2px;
border-radius: 20px;
cursor: pointer;
&:hover
{
box-shadow: 0px 0px 1px 2px white;
}
}
}
}
.coloreditorrgba
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.red_slider .track
{
background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
}
.green_slider .track
{
background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
}
.blue_slider .track
{
background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
.coloreditorhsva
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.hue_slider .track
{
background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}MapChooser {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: all;
background: linear-gradient( 140deg, #0f141a 0%, #0a0e14 60%, #111926 100% );
justify-content: flex-start;
align-items: stretch;
font-weight: bold;
font-family: Poppins;
color: white;
flex-direction: column;
padding: 48px 60px;
gap: 24px;
.header {
flex-direction: column;
gap: 6px;
h1 {
font-size: 48px;
letter-spacing: 1px;
}
.subtitle {
font-size: 16px;
opacity: 0.65;
}
}
.content {
flex-grow: 1;
flex-direction: row;
gap: 24px;
}
.map-list {
flex-direction: column;
flex-grow: 1;
min-width: 0;
.map-entries {
flex-direction: column;
gap: 10px;
padding: 12px;
border-radius: 16px;
background: #0a0c10cc;
border: 1px solid #ffffff14;
overflow-y: scroll;
}
button {
padding: 12px 14px;
border-radius: 12px;
background: #ffffff0f;
text-align: left;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
gap: 4px;
cursor: pointer;
flex-direction: row;
align-items: center;
}
button:hover {
transform: translateY( -1px );
box-shadow: 0 6px 18px rgba( 0, 0, 0, 0.35 );
}
button.active {
background: #db3984;
box-shadow: 0 6px 18px rgba( 219, 57, 132, 0.35 );
}
.title {
font-size: 18px;
}
.thumb {
width: 84px;
height: 48px;
border-radius: 10px;
background-color: #0b0f15;
background-position: center;
background-size: cover;
flex-shrink: 0;
box-shadow: inset 0 0 0 1px #ffffff12;
}
.text {
flex-direction: column;
gap: 2px;
}
.ident {
font-size: 12px;
opacity: 0.6;
}
}
.settings {
width: 360px;
padding: 16px;
border-radius: 16px;
background: #0a0c10cc;
border: 1px solid #ffffff14;
flex-direction: column;
gap: 14px;
}
.section-title {
font-size: 18px;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.85;
}
.field {
flex-direction: column;
gap: 6px;
label {
font-size: 14px;
opacity: 0.7;
}
textentry {
width: 100%;
padding: 8px 10px;
background: #ffffff0f;
border-radius: 8px;
}
}
.field.split {
flex-direction: row;
gap: 12px;
.field {
flex-grow: 1;
}
}
.privacy {
flex-direction: row;
gap: 8px;
button {
flex-grow: 1;
padding: 8px 10px;
border-radius: 8px;
background: #ffffff0f;
opacity: 0.7;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease, background 0.12s ease;
}
button:hover {
opacity: 0.9;
transform: translateY( -1px );
}
button.active {
background: #db3984;
opacity: 1;
box-shadow: 0 6px 16px rgba( 219, 57, 132, 0.35 );
transform: translateY( -1px );
}
}
.selected-map {
padding: 10px 12px;
border-radius: 10px;
background: #ffffff0a;
flex-direction: column;
gap: 4px;
.label {
font-size: 12px;
opacity: 0.6;
}
}
.launch {
margin-top: 4px;
padding: 12px;
border-radius: 10px;
background: #47c27c;
color: #0b0f12;
font-size: 18px;
}
.launch.disabled {
opacity: 0.4;
background: #ffffff1f;
color: white;
}
}
@import "/styles/_theme.scss";
$background-size: 8px;
PackageCard
{
flex-shrink: 0;
&:hover
{
sound-in: "ui.button.over";
}
&:active
{
sound-in: "ui.button.press";
}
flex-direction: column;
position: relative;
background-color: rgba( $default-950, 0 );
border-radius: $rounding-default;
transition: all 150ms ease;
cursor: pointer;
z-index: 0;
height: 200px;
.image
{
flex-grow: 1;
flex-shrink: 0;
transition: all 150ms ease;
border: 1px solid rgba( white, 0.025 );
aspect-ratio: 16 / 9;
background-position: center;
background-size: cover;
border-radius: $rounding-small;
position: relative;
}
column
{
padding: 8px 2px; // Optically aligned
}
&.list
{
flex-grow: 1;
height: 64px;
flex-direction: row;
gap: 12px;
padding: 4px;
.inner column
{
flex-grow: 1;
}
.image
{
height: 100%;
aspect-ratio: 1;
flex-grow: 0;
flex-shrink: 0;
}
column
{
gap: 3px;
justify-content: center;
}
.package-title
{
flex-shrink: 0;
max-width: 500px;
}
.package-users
{
top: 1px;
right: 1px;
}
&:hover
{
background-color: $default-800;
transform: none;
&::after
{
display: none;
}
}
}
&.wide
{
height: 250px;
.package-title
{
max-width: 300px;
}
}
&.small
{
height: 200px;
aspect-ratio: 3/4;
.image
{
aspect-ratio: 1;
}
.package-title
{
max-width: 140px;
}
}
&.tall
{
height: 400px;
.image
{
aspect-ratio: 9/16;
}
.package-title
{
max-width: 200px;
}
}
.package-title
{
text-overflow: ellipsis;
max-height: 24px;
flex-shrink: 1;
font-size: 14px;
}
.package-users
{
position: absolute;
bottom: 4px;
right: 4px;
background-color: rgba( 10, 40, 10, 0.95 );
padding: 3px 5px;
border-radius: 2px;
justify-content: center;
align-items: center;
gap: 3px;
font-size: 11px;
color: #def;
border: 1px solid #252;
color: #2f3;
&:before
{
content: '●';
font-size: 0.5rem;
}
}
// Hover effect (not for list)
&:not(.list)
{
&::after
{
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba( $default-950, 0 );
transition: all 150ms ease;
z-index: -10;
border-radius: $rounding-large;
pointer-events: none;
}
&:hover
{
transform: scale( 1.05 );
&::after
{
background-color: $default-800;
top: -$background-size;
left: -$background-size;
right: -$background-size;
bottom: -$background-size;
box-shadow: 0 0 25px rgba( black, 0.3 );
}
z-index: 100;
background-color: $default-900;
}
}
}
namespace Skateboard.Entities;
public sealed class GrindPathNodeEntity : Component
{
}
using SWB.Base.UI;
namespace SWB.Base.Attachments;
public abstract class Scope2DAttachment : Attachment
{
public override string Name => "2D Scope";
public override AttachmentCategory Category => AttachmentCategory.Sight;
public override string Description => "A high magnification scope that is utilized for firing at long ranges.";
public override string[] Positives => new string[]
{
"x12 magnification",
"100% accurate while scoped in"
};
public override string[] Negatives => new string[]
{
};
/// <summary>The new aim position offset</summary>
[Property, Group( "Scope" )] public AngPos AimAnimData { get; set; }
AngPos oldAimAnimData;
/// <summary>Scope Information</summary>
[Property, Group( "Scope" )] public virtual ScopeInfo ScopeInfo { get; set; } = new();
ScopeInfo oldScopeInfo = new();
SniperScope sniperScope;
public override void OnEquip()
{
oldAimAnimData = Weapon.AimAnimData;
oldScopeInfo = Weapon.ScopeInfo;
Weapon.Scoping = true;
Weapon.AimAnimData = AimAnimData;
Weapon.ScopeInfo = ScopeInfo;
}
public override void OnUnequip()
{
Weapon.Scoping = false;
Weapon.AimAnimData = oldAimAnimData;
Weapon.ScopeInfo = oldScopeInfo;
}
public override void CreateHudElements()
{
base.CreateHudElements();
sniperScope = new SniperScope( Weapon, Weapon.ScopeInfo.LensTexture, Weapon.ScopeInfo.ScopeTexture );
Weapon.RootPanel.Panel.AddChild( sniperScope );
}
public override void DestroyHudElements()
{
base.DestroyHudElements();
sniperScope?.Delete();
}
}
namespace SWB.Base;
public class CrosshairSettings
{
[Property] public bool Enabled { get; set; } = true;
[Property] public bool ShowDot { get; set; } = true;
[Property] public bool ShowTop { get; set; } = true;
[Property] public bool ShowBottom { get; set; } = true;
[Property] public bool ShowLeft { get; set; } = true;
[Property] public bool ShowRight { get; set; } = true;
}
namespace SWB.Base;
public class ScopeInfo
{
/// <summary>2D lens texture</summary>
[Property, ImageAssetPathAttribute] public string LensTexture { get; set; } = "/materials/swb/scopes/swb_lens_hunter.png";
/// <summary>2D scope texture</summary>
[Property, ImageAssetPathAttribute] public string ScopeTexture { get; set; } = "/materials/swb/scopes/swb_scope_hunter.png";
/// <summary>Delay between ADS and scoping in ms</summary>
[Property] public float ScopeInDelay { get; set; } = 0.2f;
/// <summary>Sound that plays when scoping starts</summary>
[Property] public SoundEvent ScopeInSound { get; set; }
/// <summary>Sound that plays when scoping ends</summary>
[Property] public SoundEvent ScopeOutSound { get; set; }
/// <summary>Player FOV while scoping</summary>
[Property] public float FOV { get; set; } = 8f;
/// <summary>Mouse sensitivity while scoping (lower is slower, 0 to use AimInfo.Sensitivity while scoped)</summary>
[Property] public float Sensitivity { get; set; } = 0.25f;
/// <summary>Spread while scoping (lower is more accurate, 0 = perfect, 1 = normal )</summary>
[Property] public float Spread { get; set; } = 0f;
}
namespace SWB.Base;
public partial class Weapon
{
// Burst Fire
public void ResetBurstFireCount( ShootInfo shootInfo, string inputButton )
{
if ( shootInfo is null || shootInfo.FiringType != FiringType.burst ) return;
if ( Input.Released( inputButton ) )
{
burstCount = 0;
}
}
// Barrel heat
public void BarrelHeatCheck()
{
if ( TimeSincePrimaryShoot > 3 && TimeSinceSecondaryShoot > 0 )
{
barrelHeat = 0;
}
}
// Tucking
public virtual float GetTuckDist()
{
if ( TuckRange == -1 )
return -1;
if ( !Owner.IsValid ) return -1;
// Increase tuck range around ladders
var tuckRange = Owner.IsClimbingLadder ? TuckRange * 1.25f : TuckRange;
var pos = Owner.EyePos;
var forward = Owner.EyeAngles.ToRotation().Forward;
var trace = TraceBullet( Owner.EyePos, pos + forward * tuckRange, 2, TuckingTraceIgnoreTags );
if ( !trace.Hit )
return -1;
return trace.Distance;
}
public bool ShouldTuck( out float dist )
{
dist = GetTuckDist();
return dist != -1;
}
}
using SWB.Base.Particles;
using SWB.Shared;
using System;
namespace SWB.Base;
public partial class Weapon
{
public static readonly string[] BulletTraceIgnoreTags =
{
TagsHelper.Trigger,
TagsHelper.PlayerClip,
TagsHelper.PassBullets,
TagsHelper.ViewModel,
TagsHelper.Sky
};
public static readonly string[] TuckingTraceIgnoreTags =
[
..BulletTraceIgnoreTags,
TagsHelper.Player,
TagsHelper.DeadPlayer
];
/// <summary>
/// Checks if the weapon can do the provided attack
/// </summary>
/// <param name="shootInfo">Attack information</param>
/// <param name="lastAttackTime">Time since this attack</param>
/// <param name="inputButton">The input button for this attack</param>
/// <returns></returns>
public virtual bool CanShoot( ShootInfo shootInfo, TimeSince lastAttackTime, string inputButton )
{
if ( (IsReloading && !ShellReloading) || (IsReloading && ShellReloading && !ShellReloadingShootCancel) || InBoltBack ) return false;
if ( shootInfo is null || !Owner.IsValid() || (!Owner.IsBot && !Input.Down( inputButton )) || ((IsRunning || TimeSinceRunning < 0.1f) && Secondary is null) ) return false;
if ( !HasAmmo() )
{
if ( Input.Pressed( inputButton ) )
{
// Check for auto reloading
if ( Settings.AutoReload && Owner.AmmoCount( shootInfo.AmmoType ) > 0 && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
{
TimeSincePrimaryShoot = 999;
TimeSinceSecondaryShoot = 999;
if ( ShellReloading )
OnShellReload();
else
Reload();
return false;
}
// Dry fire
if ( shootInfo.DryShootSound is not null )
PlaySound( shootInfo.DryShootSound );
}
return false;
}
if ( shootInfo.FiringType == FiringType.semi && !Owner.IsBot && !Input.Pressed( inputButton ) ) return false;
if ( shootInfo.FiringType == FiringType.burst )
{
if ( burstCount > 2 ) return false;
if ( (Owner.IsBot || Input.Down( inputButton )) && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
{
burstCount++;
return true;
}
return false;
}
;
if ( shootInfo.RPM <= 0 ) return true;
return lastAttackTime > GetRealRPM( shootInfo.RPM );
}
/// <summary>
/// Checks if weapon can do the primary attack
/// </summary>
public virtual bool CanPrimaryShoot()
{
return CanShoot( Primary, TimeSincePrimaryShoot, InputButtonHelper.PrimaryAttack );
}
/// <summary>
/// Checks if weapon can do the secondary attack
/// </summary>
public virtual bool CanSecondaryShoot()
{
return CanShoot( Secondary, TimeSinceSecondaryShoot, InputButtonHelper.SecondaryAttack );
}
public virtual void Shoot( ShootInfo shootInfo, bool isPrimary )
{
// Ammo
if ( shootInfo.InfiniteAmmo != InfiniteAmmoType.clip )
shootInfo.Ammo -= 1;
// Animations
var shootAnim = GetShootAnimation( shootInfo );
if ( !string.IsNullOrEmpty( shootAnim ) )
ApplyShootAnimation( shootAnim );
// Sound
if ( shootInfo.ShootSound is not null )
PlaySound( shootInfo.ShootSound );
// Particles
HandleShootEffects( isPrimary );
if ( !Owner.IsBot )
{
// Barrel smoke
barrelHeat += 1;
// Recoil
Owner.ApplyEyeAnglesOffset( GetRecoilAngles( shootInfo ) );
// Screenshake
if ( shootInfo.ScreenShake is not null )
Owner.ShakeScreen( shootInfo.ScreenShake );
// UI
BroadcastUIEvent( "shoot", GetRealRPM( shootInfo.RPM ) );
}
// Bullet
for ( int i = 0; i < shootInfo.Bullets; i++ )
{
var realSpread = GetRealSpread( shootInfo.Spread );
var spreadOffset = shootInfo.BulletType.GetRandomSpread( realSpread );
shootInfo?.BulletType?.Shoot( this, isPrimary, spreadOffset );
}
}
protected virtual void ApplyShootAnimation( string anim )
{
ViewModelRenderer?.Set( anim, true );
}
/// <summary> A single bullet trace from start to end with a certain radius.</summary>
public static SceneTraceResult TraceBullet( GameObject toIgnoreGO, Vector3 start, Vector3 end, float radius = 2.0f, string[] ignoreTags = null )
{
// TODO: find another solution when water becomes more available
// var startsInWater = SurfaceUtil.IsPointWater( start );
// if ( startsInWater )
// withoutTags.Add( TagsHelper.Water );
var tr = Game.ActiveScene.Trace.Ray( start, end )
.UseHitboxes()
.WithoutTags( ignoreTags ?? BulletTraceIgnoreTags )
.Size( radius )
.IgnoreGameObjectHierarchy( toIgnoreGO )
.Run();
// Log.Info( tr.GameObject );
return tr;
}
/// <summary> A single bullet trace from start to end with a certain radius.</summary>
public virtual SceneTraceResult TraceBullet( Vector3 start, Vector3 end, float radius = 2.0f, string[] ignoreTags = null )
{
return TraceBullet( Owner.GameObject, start, end, radius, ignoreTags );
}
[Rpc.Broadcast( NetFlags.Unreliable )]
public virtual void HandleShootEffects( bool isPrimary )
{
if ( !IsValid || Owner is null || Application.IsDedicatedServer ) return;
// Player
Owner.TriggerAnimation( Shared.Animations.Attack );
// Weapon
var shootInfo = GetShootInfo( isPrimary );
if ( shootInfo is null ) return;
// Bullet eject
if ( shootInfo.BulletEjectParticle is not null )
{
if ( !BoltBack )
{
if ( !ShellReloading || (ShellReloading && ShellEjectDelay == 0) )
{
CreateBulletEjectParticle( shootInfo.BulletEjectParticle, "ejection_point" );
}
else
{
var delayedEject = async () =>
{
await GameTask.DelaySeconds( ShellEjectDelay );
if ( !IsValid ) return;
CreateBulletEjectParticle( shootInfo.BulletEjectParticle, "ejection_point" );
};
delayedEject();
}
}
else if ( shootInfo.Ammo > 0 )
{
AsyncBoltBack( GetRealRPM( shootInfo.RPM ) );
}
}
var muzzleObj = GetMuzzleObject();
if ( muzzleObj is null ) return;
var muzzleScale = CanSeeViewModel ? shootInfo.VMParticleScale : shootInfo.WMMuzzleParticleScale;
// Muzzle flash
if ( shootInfo.MuzzleFlashParticle is not null )
CreateParticle( shootInfo.MuzzleFlashParticle, muzzleObj, muzzleScale );
// Barrel smoke
if ( !IsProxy && !Owner.IsBot && shootInfo.BarrelSmokeParticle is not null && barrelHeat >= shootInfo.ClipSize * 0.75 )
CreateParticle( shootInfo.BarrelSmokeParticle, muzzleObj, muzzleScale );
}
/// <summary>Create a bullet impact effect</summary>
public static GameObject CreateBulletImpact( SceneTraceResult tr )
{
return CreateBulletImpact( tr.HitPosition, tr.Normal, tr.Surface?.SoundCollection.Bullet, tr.Surface?.PrefabCollection.BulletImpact );
}
/// <summary>Create a bullet impact effect</summary>
public static GameObject CreateBulletImpact( Vector3 pos, Vector3 normal, SoundEvent sound, GameObject particles )
{
// Sound
SoundHandle soundHandle = null;
if ( sound is not null )
{
sound.Distance = 10000;
soundHandle = Sound.Play( sound );
}
soundHandle ??= Sound.Play( "impact-bullet-generic" );
soundHandle.Position = pos;
// Decal & Particles
if ( !particles.IsValid() ) return null;
var cloneConfig = new CloneConfig()
{
Name = "bullet_decal",
StartEnabled = true,
Transform = new()
{
Position = pos,
Rotation = Rotation.LookAt( -normal ),
},
//Parent = tr.GameObject,
};
var decalGO = particles.Clone( cloneConfig );
decalGO.NetworkMode = NetworkMode.Never;
decalGO.DestroyAsync( 30f );
WeaponParticleManager.Instance?.AddDecal( decalGO );
return decalGO;
}
/// <summary>Create a bullet eject particle (always world)</summary>
public virtual GameObject CreateBulletEjectParticle( GameObject particle, string attachment, Action<GameObject> OnParticleCreated = null )
{
var effectRenderer = GetEffectRenderer();
if ( effectRenderer is null || effectRenderer.SceneModel is null ) return null;
var transform = effectRenderer.GetAttachment( attachment );
if ( !transform.HasValue ) return null;
// Rotate bullet with attachment yaw
var spawnInViewSpace = CanSeeViewModel && !IsScoping;
var pitch = spawnInViewSpace ? ViewModelHandler.WorldRotation.Pitch() : WorldRotation.Pitch();
var yaw = transform.Value.Rotation.Yaw();
var newRot = Rotation.From( new Angles( 0, yaw, -pitch ) );
transform = transform.Value.WithRotation( newRot );
if ( spawnInViewSpace )
{
var viewSpacePos = CameraUtil.ProjectToViewSpace( transform.Value.Position, Owner.ViewModelCamera, Owner.Camera );
transform = transform.Value.WithPosition( viewSpacePos );
}
var go = CreateParticle( particle, null, transform.Value, 1, false, OnParticleCreated );
WeaponParticleManager.Instance?.AddEject( go );
// Attach owner
var ejectParticle = go.GetComponentInChildren<BulletEjectParticle>();
ejectParticle?.Owner = Owner;
return go;
}
/// <summary>Create a weapon particle</summary>
public virtual GameObject CreateParticle( GameObject particle, GameObject parent, float scale, Action<GameObject> OnParticleCreated = null )
{
return CreateParticle( particle, parent, new Transform(), scale, OnParticleCreated );
}
/// <summary>Create a weapon particle</summary>
public virtual GameObject CreateParticle( GameObject particle, Transform transform, float scale, Action<GameObject> OnParticleCreated = null )
{
return CreateParticle( particle, null, transform, scale, OnParticleCreated );
}
/// <summary>Create a weapon particle</summary>
public virtual GameObject CreateParticle( GameObject particle, GameObject parent, Transform transform, float scale, Action<GameObject> OnParticleCreated = null )
{
return CreateParticle( particle, parent, transform, scale, CanSeeViewModel, OnParticleCreated );
}
public virtual GameObject CreateParticle( GameObject particle, GameObject parent, Transform transform, float scale, bool forViewModel, Action<GameObject> OnParticleCreated = null )
{
var go = particle.Clone( transform.WithScale( scale ), parent );
if ( forViewModel )
go.Tags.Add( TagsHelper.ViewModel );
if ( OnParticleCreated is not null )
{
var p = go.GetComponentInChildren<ParticleEffect>();
p.OnParticleCreated += ( p ) =>
{
OnParticleCreated.Invoke( go );
};
}
return go;
}
}
using Sandbox.UI;
using Sandbox.UI.Construct;
using SWB.Base;
using SWB.Player;
using SWB.Shared;
using System;
namespace SWB.HUD;
public class AmmoDisplay : Panel
{
PlayerBase player;
Label clipLabel;
Label reserveLabel;
Color reserveColor = new Color32( 200, 200, 200 ).ToColor();
Color emptyColor = new Color32( 175, 175, 175, 200 ).ToColor();
public AmmoDisplay( PlayerBase player )
{
this.player = player;
StyleSheet.Load( "/swb_hud/AmmoDisplay.cs.scss" );
Add.Label( "ammo", "name" );
clipLabel = Add.Label( "", "clip" );
reserveLabel = Add.Label( "", "reserve" );
}
public override void Tick()
{
var isAlive = player.IsAlive;
var weapon = player.Inventory.Active?.Components.Get<Weapon>();
var hide = !isAlive || weapon is null;
SetClass( "hide", hide );
if ( hide ) return;
var hasClipSize = weapon.Primary.ClipSize > 0;
var reserveAmmo = Math.Min( player.AmmoCount( weapon.Primary.AmmoType ), 999 );
var hasAmmo = weapon.Primary.Ammo > 0;
if ( weapon.Primary.InfiniteAmmo != InfiniteAmmoType.clip )
{
var clipAmmo = hasClipSize ? weapon.Primary.Ammo : reserveAmmo;
clipAmmo = Math.Min( clipAmmo, 999 );
clipLabel.Text = clipAmmo.ToString();
clipLabel.Style.FontColor = clipAmmo == 0 ? emptyColor : Color.White;
}
else
{
clipLabel.Text = "∞";
clipLabel.Style.FontColor = Color.White;
}
if ( hasClipSize )
{
if ( weapon.Primary.InfiniteAmmo == 0 )
{
reserveLabel.Text = " / " + reserveAmmo;
reserveLabel.Style.FontColor = reserveAmmo == 0 ? emptyColor : reserveColor;
}
else
{
reserveLabel.Text = " / ∞";
reserveLabel.Style.FontColor = reserveColor;
}
}
else if ( hasAmmo )
{
clipLabel.Text = weapon.Primary.Ammo.ToString();
}
else
{
reserveLabel.Text = "";
}
}
}
using System;
using System.Collections.Generic;
/*
* Utility class for tables
*/
namespace SWB.Shared;
class TableUtil
{
public static T GetRandom<T>( IList<T> list )
{
if ( list.Count == 0 ) return default;
var randI = Random.Shared.Next( list.Count );
return list[randI];
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.coloreditor
{
flex-direction: column;
min-width: 250px;
max-width: 250px;
> .canvas
{
border-top: 1px solid rgba( white, 0.5 );
flex-grow: 1;
flex-direction: column;
padding: 10px 15px;
> textentry.textentry
{
flex-grow: 0;
flex-shrink: 0;
width: auto;
}
}
.slider
{
padding: 6.5px;
}
.slider .track
{
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
transition: all 0.1s ease-out;
.inner
{
display: none;
}
}
.slider:hover, .slider:active
{
padding: 7px;
.track
{
height: 20px;
border-radius: 6px;
}
.thumb
{
width: 10px;
height: 16px;
margin-left: 2px;
}
}
.slider .thumb
{
width: 10px;
height: 10px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
backdrop-filter: invert( 1 );
margin-left: 1px;
pointer-events: none;
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
.hue .track
{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
.inner
{
display: none;
}
}
.satval
{
background: linear-gradient(to right, white, transparent);
height: 200px;
position: relative;
&:active
{
z-index: 10;
cursor: none;
}
.value_gradient
{
background: linear-gradient(to bottom, black, white);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
mix-blend-mode: multiply;
}
.thumb
{
z-index: 10;
}
}
.presets
{
flex-wrap: wrap;
align-content: center;
justify-content: center;
padding: 15px;
border-top: 1px solid rgba( white, 0.5 );
.preset
{
background-color: white;
width: 16px;
height: 16px;
margin: 2px;
border-radius: 20px;
cursor: pointer;
&:hover
{
box-shadow: 0px 0px 1px 2px white;
}
}
}
}
.coloreditorrgba
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.red_slider .track
{
background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
}
.green_slider .track
{
background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
}
.blue_slider .track
{
background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
.coloreditorhsva
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.hue_slider .track
{
background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}.dropdown
{
gap: 2px;
flex-grow: 1;
cursor: pointer;
justify-content: flex-end;
align-items: center;
padding: 0px 12px;
.button-right-column
{
flex-grow: 1;
}
}
$dragMode: #e67e22;
OffsetEditor {
position: absolute;
width: 100%;
height: 100%;
opacity: 1;
pointer-events: all;
.canvas {
position: absolute;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
pointer-events: none;
& > .dot {
background-color: red;
margin: 2px 0 0 2px;
width: 2px;
height: 2px;
box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.4);
}
}
.hint {
position: absolute;
flex-direction: column;
top: 25%;
right: 50%;
transform: translate(50%,-50%);
font-family: Poppins;
text-shadow: 0px 1px 1px rgba(0,0,0,0.5);
text {
&.highlight {
color: $dragMode;
}
}
.title {
font-size: 34px;
color: white;
}
.subTitle {
font-size: 24px;
text-align: center;
color: $dragMode;
label {
width: 100%;
}
}
}
.menu {
position: absolute;
flex-direction: column;
width: 400px;
height: 800px;
background-color: rgb(31, 31, 31);
top: 10px;
left: 10px;
font-family: Poppins;
border-radius: 4px;
color: white;
.title {
font-size: 50px;
height: 75px;
text-align: center;
margin-bottom: 10px;
}
.subTitle {
font-size: 20px;
margin: 20px 0 10px 0;
width: 100%;
text-align: center;
}
button {
cursor: pointer;
color: white;
text-align: center;
width: 120px;
margin: 0 25px 0 25px;
border: 2px;
height: 30px;
border-color: transparent;
border-width: 1px;
border-radius: 4px;
align-items: center;
& > label {
width: 100%;
font-size: 18px;
font-weight: bold;
&:active {
font-size: 20px;
}
}
&:hover {
border-color: white;
}
&:active {
border-color: transparent;
}
}
.dataButtons {
justify-content: center;
align-items: center;
margin-top: 0px;
height: 20px;
flex-wrap: wrap;
button {
margin: 8px;
width: 180px;
&.large {
width: 240px;
}
label {
font-size: 16px;
&:active {
font-size: 20px;
}
}
}
}
.buttonContainer {
justify-content: center;
align-items: center;
margin-top: 80px;
height: 60px;
}
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.checkbox.switch
{
cursor: pointer;
> .checkmark
{
font-size: 22px;
border: 0px solid $primary;
border-radius: 100px;
text-align: center;
justify-content: center;
align-items: center;
color: $primary-alt;
padding: $switch-padding;
padding-right: 32px;
padding-left: $switch-padding;
transition: all 0.3s ease;
background-color: rgba( $primary, 0.1 );
> .handle
{
background-color: $primary-alt;
width: 20px;
height: 20px;
border-radius: 100px;
box-shadow: 2px 2px 12px black;
}
}
&.checked
{
> .checkmark
{
background-color: $primary;
padding-left: 32px;
padding-right: $switch-padding;
}
}
&:active
{
transform: scale( 0.9 );
transform-origin: 20px 50%;
}
}
.slidercontrol
{
flex-direction: row;
min-width: 50px;
position: relative;
flex-shrink: 0;
flex-direction: row;
cursor: pointer;
gap: 8px;
flex-grow: 1;
align-items: center;
pointer-events: all;
> .inner
{
flex-direction: column;
flex-shrink: 1;
flex-grow: 1;
min-height: 32px;
justify-content: center;
> .values
{
width: 100%;
pointer-events: none;
font-size: 14px;
color: #aaa;
> .left
{
flex-grow: 1;
}
}
> .track
{
position: relative;
background-color: #888;
height: 7px;
margin: 8px;
align-items: center;
border-radius: 4px;
> .track-active
{
background-color: #fff;
position: absolute;
height: 100%;
left: 0px;
border-radius: 4px;
}
> .thumb
{
position: relative;
background-color: #fff;
border-radius: 100px;
width: 16px;
height: 16px;
transform: translateX( -50% );
}
}
}
> .entry
{
flex-shrink: 0;
flex-grow: 0;
width: 50px;
> numberentry
{
background-color: transparent;
> .content-label
{
padding: 0 4px;
}
}
}
}
.slidercontrol .value-tooltip
{
position: absolute;
bottom: 150%;
left: -8px;
z-index: 1000;
flex-direction: column;
> .label
{
background-color: black;
padding: 8px 12px;
border-radius: 8px;
}
>.tail
{
bottom: -0px;
background-color: black;
width: 10px;
height: 10px;
transform: rotateZ(45 deg) translateX( 4px );
position: absolute;
}
}
.switchcontrol
{
flex-direction: row;
width: 100px;
min-height: 24px;
align-items: center;
cursor: pointer;
.switch-frame
{
flex-grow: 0;
flex-shrink: 1;
width: 48px;
height: 16px;
background-color: #fff1;
margin: 0px 5px;
align-items: center;
border-radius: 100px;
transition: all 0.4s linear;
.switch-inner
{
position: relative;
flex-grow: 0;
flex-shrink: 1;
background-color: #999;
width: 25px;
height: 25px;
border-radius: 100px;
left: 20%;
transform: translateX( -50% );
transition: all 0.3s ease-out;
}
}
&.active
{
.switch-frame
{
background-color: #fffa;
}
.switch-inner
{
left: 80%;
background-color: #fff;
}
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.button.popupbutton.dropdown
{
cursor: pointer;
transition: all .1s ease-out;
position: relative;
> .dropdown_indicator
{
position: absolute;
right: 8px;
}
&.open
{
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
transition: border-radius 0.2s ease-out;
}
}
select
{
min-height: 40px;
> option
{
display: none;
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.coloreditor
{
flex-direction: column;
min-width: 250px;
max-width: 250px;
> .canvas
{
border-top: 1px solid rgba( white, 0.5 );
flex-grow: 1;
flex-direction: column;
padding: 10px 15px;
> textentry.textentry
{
flex-grow: 0;
flex-shrink: 0;
width: auto;
}
}
.slider
{
padding: 6.5px;
}
.slider .track
{
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
transition: all 0.1s ease-out;
.inner
{
display: none;
}
}
.slider:hover, .slider:active
{
padding: 7px;
.track
{
height: 20px;
border-radius: 6px;
}
.thumb
{
width: 10px;
height: 16px;
margin-left: 2px;
}
}
.slider .thumb
{
width: 10px;
height: 10px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
backdrop-filter: invert( 1 );
margin-left: 1px;
pointer-events: none;
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
.hue .track
{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
.inner
{
display: none;
}
}
.satval
{
background: linear-gradient(to right, white, transparent);
height: 200px;
position: relative;
&:active
{
z-index: 10;
cursor: none;
}
.value_gradient
{
background: linear-gradient(to bottom, black, white);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
mix-blend-mode: multiply;
}
.thumb
{
z-index: 10;
}
}
.presets
{
flex-wrap: wrap;
align-content: center;
justify-content: center;
padding: 15px;
border-top: 1px solid rgba( white, 0.5 );
.preset
{
background-color: white;
width: 16px;
height: 16px;
margin: 2px;
border-radius: 20px;
cursor: pointer;
&:hover
{
box-shadow: 0px 0px 1px 2px white;
}
}
}
}
.coloreditorrgba
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.red_slider .track
{
background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
}
.green_slider .track
{
background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
}
.blue_slider .track
{
background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
.coloreditorhsva
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.hue_slider .track
{
background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
menupanel
{
position: absolute;
z-index: 1000;
pointer-events: all;
font-size: 12px;
flex-shrink: 0;
.background
{
position: absolute;
left: -5000px;
right: -5000px;
top: -5000px;
bottom: -5000px;
}
> .inner
{
min-width: 200px;
min-height: 20px;
flex-direction: column;
font-family: Poppins;
font-weight: bold;
border-radius: 10px;
box-shadow: 5px 5px 30px #000e;
background-color: #2a2a2a;
flex-shrink: 0;
.spacer
{
height: 1px;
background-color: #0005;
}
.option
{
color: #fffa;
padding: 0px 8px;
cursor: pointer;
flex-shrink: 0;
height: 32px;
&:first-child
{
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
&:last-child
{
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
.icon
{
padding: 8px;
font-family: Material Icons;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.text
{
padding: 8px;
flex-shrink: 0;
}
&:hover
{
background-color: #3472e6;
color: #f5f8fe;
}
}
}
}
$form-control-height: 28px !default;
@import "form/_checkbox.scss";
@import "form/_switch.scss";
@import "form/_dropdown.scss";
@import "form/_coloreditor.scss";
@import "form/_colorproperty.scss";
.form
{
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
overflow: scroll;
}
.field-group
{
flex-direction: column;
flex-shrink: 0;
}
.field-header
{
flex-shrink: 0;
}
.field
{
color: white;
font-size: 14px;
flex-shrink: 0;
flex-grow: 0;
> .label
{
flex-grow: 0;
flex-shrink: 0;
font-weight: 600;
opacity: 0.4;
width: 20%;
font-size: 13px;
}
> .control
{
flex-shrink: 0;
flex-grow: 1;
flex-direction: column;
}
}
.is-vertical > .field, .field.is-vertical
{
flex-direction: column;
> .label
{
width: auto;
height: auto;
}
}
RootPanel
{
font-family: Roboto;
color: white;
}
IconPanel, i
{
font-family: Material Icons;
background-position: center;
background-size: contain;
}
.tooltip
{
background-color: rgba( black, 0.8 );
border: 1px solid rgba( #555, 0.1 );
padding: 10px;
border-radius: 4px;
color: white;
font-family: Inter;
font-weight: bold;
transition: opacity 0.09s ease;
opacity: 1;
margin: 0px;
&:intro, &:outro
{
opacity: 0;
}
}
.textentry
{
cursor: text;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow: hidden;
flex-direction: row;
position: relative;
padding: 10px;
&.disabled
{
cursor: default;
}
&.is-multiline
{
align-items: flex-start;
white-space: normal;
}
.content-label,
.placeholder
{
flex-grow: 1;
}
.placeholder
{
opacity: 0.2;
pointer-events: none;
}
.iconpanel
{
position: absolute;
right: 0;
padding-right: 10px;
pointer-events: none;
&.clearbutton
{
color: #fff;
pointer-events: all;
cursor: pointer;
opacity: 0.1;
&:hover
{
opacity: 0.5;
}
&:active
{
opacity: 1;
}
}
}
&:empty
{
.clearbutton
{
display: none;
}
}
&.has-icon
{
padding-right: 30px;
}
.content-label,
.prefix-label,
.suffix-label,
.placeholder
{
overflow: hidden;
}
.content-label
{
order: 1;
}
.suffix-label
{
order: 2;
}
.prefix-label,
.suffix-label
{
flex-shrink: 0;
opacity: 0.5;
}
&.invalid
{
box-shadow: 0px 0px 1px 2px #f8356b;
}
}
.popup-panel
{
background-color: #1b202c;
flex-direction: column;
color: #8e9ec2;
transition: all 0.1s ease-out;
position: absolute;
z-index: 2000;
min-height: 20px;
min-width: 10px;
overflow: scroll;
pointer-events: all;
box-shadow: 3px 3px 64px #000;
border-radius: 4px;
&.medium
{
max-width: 300px;
}
&.center-horizontal
{
transform-origin: top center;
}
&.below-center
{
transform: translateX( -50% );
transform-origin: 0% 0%;
}
&.above-left
{
transform-origin: 0% 100%;
}
&.above-center
{
transform: translateX( -50% );
transform-origin: 0% 100%;
}
&.left
{
transform-origin: 100% 50%;
transform: translate( 00% -50% );
}
&.right
{
transform-origin: 0 50%;
transform: translate( 0% -50% );
}
&:intro
{
transform: scale( 0 );
pointer-events: none;
opacity: 0;
}
&:outro
{
opacity: 0;
transition: all 0.1s ease-in;
margin-top: 50px;
pointer-events: none;
}
&.below-stretch
{
transform-origin: 0% 0%;
&:intro
{
transform: scaleY( 0.1 );
}
}
> .canvas
{
flex-direction: column;
}
.information
{
padding: 16px;
font-size: 14px;
opacity: 0.5;
}
button
{
padding: 2px 8px;
cursor: pointer;
font-family: Poppins;
font-weight: 600;
flex-shrink: 0;
&:hover
{
background-color: #08f;
color: #fff;
}
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.button.popupbutton.dropdown
{
cursor: pointer;
transition: all .1s ease-out;
position: relative;
> .dropdown_indicator
{
position: absolute;
right: 8px;
}
&.open
{
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
transition: border-radius 0.2s ease-out;
}
}
select
{
min-height: 40px;
> option
{
display: none;
}
}
$primary: red !default;
$primary-alt: white !default;
$switch-padding: 6px !default;
.coloreditor
{
flex-direction: column;
min-width: 250px;
max-width: 250px;
> .canvas
{
border-top: 1px solid rgba( white, 0.5 );
flex-grow: 1;
flex-direction: column;
padding: 10px 15px;
> textentry.textentry
{
flex-grow: 0;
flex-shrink: 0;
width: auto;
}
}
.slider
{
padding: 6.5px;
}
.slider .track
{
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
transition: all 0.1s ease-out;
.inner
{
display: none;
}
}
.slider:hover, .slider:active
{
padding: 7px;
.track
{
height: 20px;
border-radius: 6px;
}
.thumb
{
width: 10px;
height: 16px;
margin-left: 2px;
}
}
.slider .thumb
{
width: 10px;
height: 10px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
backdrop-filter: invert( 1 );
margin-left: 1px;
pointer-events: none;
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
.hue .track
{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
.inner
{
display: none;
}
}
.satval
{
background: linear-gradient(to right, white, transparent);
height: 200px;
position: relative;
&:active
{
z-index: 10;
cursor: none;
}
.value_gradient
{
background: linear-gradient(to bottom, black, white);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
mix-blend-mode: multiply;
}
.thumb
{
z-index: 10;
}
}
.presets
{
flex-wrap: wrap;
align-content: center;
justify-content: center;
padding: 15px;
border-top: 1px solid rgba( white, 0.5 );
.preset
{
background-color: white;
width: 16px;
height: 16px;
margin: 2px;
border-radius: 20px;
cursor: pointer;
&:hover
{
box-shadow: 0px 0px 1px 2px white;
}
}
}
}
.coloreditorrgba
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.red_slider .track
{
background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
}
.green_slider .track
{
background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
}
.blue_slider .track
{
background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
.coloreditorhsva
{
flex-direction: column;
.slider
{
padding-right: 16px;
}
.slider .track
{
background-color: transparent;
height: 14px;
border-radius: 14px;
left: 0px;
right: 0px;
.inner
{
display: none;
}
}
.slider .thumb
{
width: 12px;
height: 12px;
box-shadow: 0px 0px 0px transparent;
background-color: white;
margin-left: 2px;
backdrop-filter: invert( 1 );
}
.hue_slider .track
{
background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
}
.alpha_slider .track
{
background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
}
}
menupanel
{
position: absolute;
z-index: 1000;
pointer-events: all;
font-size: 12px;
flex-shrink: 0;
.background
{
position: absolute;
left: -5000px;
right: -5000px;
top: -5000px;
bottom: -5000px;
}
> .inner
{
min-width: 200px;
min-height: 20px;
flex-direction: column;
font-family: Poppins;
font-weight: bold;
border-radius: 10px;
box-shadow: 5px 5px 30px #000e;
background-color: #2a2a2a;
flex-shrink: 0;
.spacer
{
height: 1px;
background-color: #0005;
}
.option
{
color: #fffa;
padding: 0px 8px;
cursor: pointer;
flex-shrink: 0;
height: 32px;
&:first-child
{
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
&:last-child
{
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
.icon
{
padding: 8px;
font-family: Material Icons;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.text
{
padding: 8px;
flex-shrink: 0;
}
&:hover
{
background-color: #3472e6;
color: #f5f8fe;
}
}
}
}
ColorAlphaControl
{
gap: 0.5rem;
flex-grow: 1;
pointer-events: all;
background: linear-gradient( to right, black, white );
border-radius: 4px;
padding: 2px;
height: 12px;
position: relative;
cursor: pointer;
border: 1px solid #333;
&:hover
{
border: 1px solid #08f;
}
&:active
{
border: 1px solid #fff;
}
.handle
{
top: -5px;
bottom: -5px;
aspect-ratio: 1;
border-radius: 100px;
border: 2px solid #444;
position: absolute;
background-color: white;
box-shadow: 2px 2px 16px #000a;
transform: translateX( -50% );
pointer-events: none;
}
}
@import "/styles/_theme.scss";
$primary: $primary-blue !default;
$primary-alt: white !default;
$form-control-height: 24px !default;
.checkbox
{
cursor: pointer;
color: rgba( $primary-alt, 0.6 );
align-items: center;
gap: 8px;
label
{
pointer-events: none;
}
> .checkmark
{
padding: 1px;
font-size: 22px;
border: 1px solid $primary;
border-radius: $rounding-small;
text-align: center;
justify-content: center;
align-items: center;
color: transparent;
min-height: $form-control-height;
pointer-events: none;
flex-shrink: 0;
}
&.checked
{
> .checkmark
{
color: $primary-alt;
background-color: $primary;
}
}
&:active
{
color: $primary-alt;
}
&:hover
{
color: $primary-alt;
}
}