using Sandbox.Rendering; using System.Runtime.InteropServices; namespace Sandbox.UI; public partial class Panel { bool backgroundRenderDirty = true; RenderAttributes bgAttribs; BlendMode bgBlendMode; internal virtual void DrawBackground( PanelRenderer renderer, ref RenderState state ) { if ( HasBackdropFilter ) { renderer.DrawBackdropFilters( this, state ); } if ( backgroundRenderDirty ) { UpdateBackgroundData(); backgroundRenderDirty = false; } if ( HasBackground ) { // Texture has just loaded, rect needs to be recalculated { var texture = ComputedStyle.BackgroundImage; if ( texture is not null && texture.IsDirty ) { var imageCalc = ImageRect.Calculate( new ImageRect.Input { ScaleToScreen = ScaleToScreen, Image = texture, PanelRect = Box.Rect, DefaultSize = Length.Auto, ImagePositionX = ComputedStyle.BackgroundPositionX, ImagePositionY = ComputedStyle.BackgroundPositionY, ImageSizeX = ComputedStyle.BackgroundSizeX, ImageSizeY = ComputedStyle.BackgroundSizeY, } ); bgAttribs.Set( "BgPos", imageCalc.Rect ); texture.IsDirty = false; } } var color = ComputedStyle.BackgroundColor.Value; var opacity = Opacity * state.RenderOpacity; color.a *= opacity; // parameters inflienced by opacity ! { bgAttribs.Set( "BorderColorL", ComputedStyle.BorderLeftColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorT", ComputedStyle.BorderTopColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorR", ComputedStyle.BorderRightColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorB", ComputedStyle.BorderBottomColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderImageTint", ComputedStyle.BorderImageTint.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BgTint", ComputedStyle.BackgroundTint.Value.WithAlphaMultiplied( opacity ) ); } var rect = Box.Rect; { bgAttribs.Set( "BoxPosition", new Vector2( rect.Left, rect.Top ) ); bgAttribs.Set( "BoxSize", new Vector2( rect.Width, rect.Height ) ); } if ( bgBlendMode == BlendMode.Normal || ComputedStyle.BackgroundImage == null ) { bgAttribs.SetComboEnum( "D_BLENDMODE", renderer.OverrideBlendMode ); bgAttribs.Set( "Texture", ComputedStyle.BackgroundImage ); Graphics.DrawQuad( rect, Material.UI.Box, color, bgAttribs ); } else { // Draw background color bgAttribs.SetComboEnum( "D_BLENDMODE", renderer.OverrideBlendMode ); bgAttribs.Set( "Texture", Texture.Invalid ); Graphics.DrawQuad( rect, Material.UI.Box, color, bgAttribs ); // Draw background image with specified background-blend-mode bgAttribs.SetComboEnum( "D_BLENDMODE", bgBlendMode ); bgAttribs.Set( "Texture", ComputedStyle.BackgroundImage ); Graphics.DrawQuad( rect, Material.UI.Box, color, bgAttribs ); } } DrawBackground( ref state ); } void UpdateBackgroundData() { bgBlendMode = ParseBlendMode( ComputedStyle?.BackgroundBlendMode ); var style = ComputedStyle; if ( style is null ) return; bgAttribs ??= new RenderAttributes(); var rect = Box.Rect; var opacity = Opacity * 1; var color = style.BackgroundColor.Value; color.a *= opacity; var size = (rect.Width + rect.Height) * 0.5f; var borderSize = new Vector4( style.BorderLeftWidth.Value.GetPixels( size ), style.BorderTopWidth.Value.GetPixels( size ), style.BorderRightWidth.Value.GetPixels( size ), style.BorderBottomWidth.Value.GetPixels( size ) ); var borderRadius = new Vector4( style.BorderBottomRightRadius.Value.GetPixels( size ), style.BorderTopRightRadius.Value.GetPixels( size ), style.BorderBottomLeftRadius.Value.GetPixels( size ), style.BorderTopLeftRadius.Value.GetPixels( size ) ); bgAttribs.Set( "BorderRadius", borderRadius ); if ( borderSize.x == 0 && borderSize.y == 0 && borderSize.z == 0 && borderSize.w == 0 ) { bgAttribs.Set( "HasBorder", 0 ); } else { bgAttribs.Set( "HasBorder", 1 ); bgAttribs.Set( "BorderSize", borderSize ); bgAttribs.Set( "BorderColorL", style.BorderLeftColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorT", style.BorderTopColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorR", style.BorderRightColor.Value.WithAlphaMultiplied( opacity ) ); bgAttribs.Set( "BorderColorB", style.BorderBottomColor.Value.WithAlphaMultiplied( opacity ) ); } // We have a border image if ( style.BorderImageSource != null ) { bgAttribs.Set( "BorderImageTexture", style.BorderImageSource ); bgAttribs.Set( "BorderImageSlice", new Vector4( style.BorderImageWidthLeft.Value.GetPixels( size ), style.BorderImageWidthTop.Value.GetPixels( size ), style.BorderImageWidthRight.Value.GetPixels( size ), style.BorderImageWidthBottom.Value.GetPixels( size ) ) ); bgAttribs.SetCombo( "D_BORDER_IMAGE", (byte)(style.BorderImageRepeat == BorderImageRepeat.Stretch ? 2 : 1) ); bgAttribs.Set( "HasBorderImageFill", (byte)(style.BorderImageFill == BorderImageFill.Filled ? 1 : 0) ); bgAttribs.Set( "BorderImageTint", style.BorderImageTint.Value.WithAlphaMultiplied( opacity ) ); } else { bgAttribs.SetCombo( "D_BORDER_IMAGE", 0 ); } var texture = style.BackgroundImage; var backgroundRepeat = style.BackgroundRepeat ?? BackgroundRepeat.Repeat; if ( texture is not null && texture != Texture.Invalid ) { var imageRectInput = new ImageRect.Input { ScaleToScreen = ScaleToScreen, Image = texture, PanelRect = rect, DefaultSize = Length.Auto, ImagePositionX = style.BackgroundPositionX, ImagePositionY = style.BackgroundPositionY, ImageSizeX = style.BackgroundSizeX, ImageSizeY = style.BackgroundSizeY, }; var imageCalc = ImageRect.Calculate( imageRectInput ); bgAttribs.Set( "Texture", texture ); bgAttribs.Set( "BgPos", imageCalc.Rect ); bgAttribs.Set( "BgAngle", style.BackgroundAngle.Value.GetPixels( 1.0f ) ); bgAttribs.Set( "BgRepeat", (int)backgroundRepeat ); bgAttribs.SetCombo( "D_BACKGROUND_IMAGE", 1 ); bgAttribs.Set( "BgTint", style.BackgroundTint.Value.WithAlphaMultiplied( opacity ) ); } else { bgAttribs.SetCombo( "D_BACKGROUND_IMAGE", 0 ); } var filter = (style?.ImageRendering ?? ImageRendering.Anisotropic) switch { ImageRendering.Point => FilterMode.Point, ImageRendering.Bilinear => FilterMode.Bilinear, ImageRendering.Trilinear => FilterMode.Trilinear, _ => FilterMode.Anisotropic }; var sampler = backgroundRepeat switch { BackgroundRepeat.RepeatX => new SamplerState { AddressModeV = TextureAddressMode.Clamp, Filter = filter }, BackgroundRepeat.RepeatY => new SamplerState { AddressModeU = TextureAddressMode.Clamp, Filter = filter }, BackgroundRepeat.Clamp => new SamplerState { AddressModeU = TextureAddressMode.Clamp, AddressModeV = TextureAddressMode.Clamp, Filter = filter }, _ => new SamplerState { Filter = filter } }; bgAttribs.Set( "SamplerIndex", SamplerState.GetBindlessIndex( sampler ) ); bgAttribs.Set( "ClampSamplerIndex", SamplerState.GetBindlessIndex( new SamplerState { AddressModeU = TextureAddressMode.Clamp, AddressModeV = TextureAddressMode.Clamp, Filter = filter } ) ); bgAttribs.SetComboEnum( "D_BLENDMODE", bgBlendMode ); } private BlendMode ParseBlendMode( string blendModeStr ) { var blendMode = blendModeStr switch { "lighten" => BlendMode.Lighten, "multiply" => BlendMode.Multiply, _ => BlendMode.Normal, }; return blendMode; } internal virtual void DrawContent( PanelRenderer renderer, ref RenderState state ) { DrawContent( ref state ); } /// /// Called when is set to to custom draw the panels content. /// public virtual void DrawContent( ref RenderState state ) { // nothing by default } /// /// Called to draw the panels background. /// public virtual void DrawBackground( ref RenderState state ) { // nothing by default } internal void RenderChildren( PanelRenderer render, ref RenderState state ) { using var _ = render.Clip( this ); if ( _renderChildrenDirty ) { _renderChildren.Sort( ( x, y ) => x.GetRenderOrderIndex() - y.GetRenderOrderIndex() ); _renderChildrenDirty = false; } // Render Children { for ( int i = 0; i < _renderChildren.Count; i++ ) { render.Render( _renderChildren[i], state ); } } } }