@@ -3458,15 +3458,200 @@ public void Complete(TextArea textArea, ISegment completionSegment, EventArgs in
34583458 textArea . Document . Replace ( completionSegment , _item . Text ) ;
34593459 }
34603460
3461- public ImageSource Image => null ;
3461+ public ImageSource Image => CompletionItemStyle . GetIcon ( _item . Kind ) ;
34623462
34633463 public string Text => _item . Text ;
34643464
3465- public object Content => _item . Text ;
3465+ public object Content => new TextBlock
3466+ {
3467+ Text = _item . Text ,
3468+ Foreground = CompletionItemStyle . GetBrush ( _item . Kind ) ,
3469+ VerticalAlignment = VerticalAlignment . Center
3470+ } ;
34663471
34673472 public object Description => _item . Type ;
34683473
34693474 public double Priority => 0 ;
34703475 }
3476+
3477+ /// <summary>
3478+ /// Small vector icons and colors used to visually distinguish completion item categories
3479+ /// (builtin functions/constants/variables, resources, locals, instance and global variables).
3480+ /// </summary>
3481+ private static class CompletionItemStyle
3482+ {
3483+ private static readonly Dictionary < string , ImageSource > _darkIcons = new ( ) ;
3484+ private static readonly Dictionary < string , ImageSource > _lightIcons = new ( ) ;
3485+ private static readonly Dictionary < string , Brush > _darkBrushes = new ( ) ;
3486+ private static readonly Dictionary < string , Brush > _lightBrushes = new ( ) ;
3487+
3488+ private static bool IsDark => Settings . Instance ? . EnableDarkMode ?? true ;
3489+
3490+ public static ImageSource GetIcon ( string kind )
3491+ {
3492+ Dictionary < string , ImageSource > cache = IsDark ? _darkIcons : _lightIcons ;
3493+ if ( ! cache . TryGetValue ( kind , out ImageSource icon ) )
3494+ {
3495+ icon = CreateIcon ( kind , IsDark ) ;
3496+ cache [ kind ] = icon ;
3497+ }
3498+ return icon ;
3499+ }
3500+
3501+ public static Brush GetBrush ( string kind )
3502+ {
3503+ Dictionary < string , Brush > cache = IsDark ? _darkBrushes : _lightBrushes ;
3504+ if ( ! cache . TryGetValue ( kind , out Brush brush ) )
3505+ {
3506+ Color color = GetColor ( kind , IsDark ) ;
3507+ brush = new SolidColorBrush ( color ) ;
3508+ brush . Freeze ( ) ;
3509+ cache [ kind ] = brush ;
3510+ }
3511+ return brush ;
3512+ }
3513+
3514+ private static Color GetColor ( string kind , bool dark )
3515+ {
3516+ ( Color DarkColor , Color LightColor ) = kind switch
3517+ {
3518+ "function" => ( Color . FromRgb ( 0xA5 , 0x73 , 0xE8 ) , Color . FromRgb ( 0x7A , 0x3D , 0xB8 ) ) ,
3519+ "constant" => ( Color . FromRgb ( 0xC9 , 0xA6 , 0xF2 ) , Color . FromRgb ( 0x8A , 0x5C , 0xB8 ) ) ,
3520+ "variable" => ( Color . FromRgb ( 0xC9 , 0xA6 , 0xF2 ) , Color . FromRgb ( 0x8A , 0x5C , 0xB8 ) ) ,
3521+ "local" => ( Color . FromRgb ( 0x8F , 0xC7 , 0xFF ) , Color . FromRgb ( 0x1F , 0x6F , 0xB2 ) ) ,
3522+ "instance_var" => ( Color . FromRgb ( 0x4A , 0x7A , 0xDE ) , Color . FromRgb ( 0x12 , 0x3C , 0x96 ) ) ,
3523+ "global_var" => ( Color . FromRgb ( 0x4D , 0xC9 , 0xC9 ) , Color . FromRgb ( 0x00 , 0x8C , 0x8C ) ) ,
3524+ "keyword" => ( Color . FromRgb ( 0xF9 , 0xB4 , 0x6F ) , Color . FromRgb ( 0xB0 , 0x5A , 0x00 ) ) ,
3525+ "user" => ( Color . FromRgb ( 0x9D , 0xA5 , 0xB4 ) , Color . FromRgb ( 0x56 , 0x59 , 0x5E ) ) ,
3526+ _ => ( Color . FromRgb ( 0xFF , 0x6B , 0x6B ) , Color . FromRgb ( 0xC0 , 0x19 , 0x19 ) ) // scripts and all asset kinds
3527+ } ;
3528+ return dark ? DarkColor : LightColor ;
3529+ }
3530+
3531+ private static ImageSource CreateIcon ( string kind , bool dark )
3532+ {
3533+ Color color = GetColor ( kind , dark ) ;
3534+ SolidColorBrush brush = new ( color ) ;
3535+ brush . Freeze ( ) ;
3536+ Pen pen = new ( brush , 1.4 ) ;
3537+ pen . Freeze ( ) ;
3538+
3539+ // All variable kinds (builtin, instance, global) use a cube icon
3540+ if ( kind is "variable" or "instance_var" or "global_var" )
3541+ return CreateCubeIcon ( pen ) ;
3542+
3543+ // Functions use a pair of curly braces
3544+ if ( kind == "function" )
3545+ return CreateTextIcon ( "{ }" , 12 , "Consolas" , FontWeights . Normal , brush ) ;
3546+
3547+ // Scripts and all asset kinds use a "RES" text badge
3548+ if ( kind is not ( "constant" or "local" or "keyword" or "user" ) )
3549+ return CreateTextIcon ( "RES" , 9 , "Segoe UI" , FontWeights . Bold , brush ) ;
3550+
3551+ // Remaining kinds keep simple geometric glyphs
3552+ Geometry outline = null ;
3553+ Geometry filledShape = kind switch
3554+ {
3555+ "constant" => CreatePolygon ( new Point ( 8 , 3 ) , new Point ( 13 , 8 ) , new Point ( 8 , 13 ) , new Point ( 3 , 8 ) ) ,
3556+ "local" => CreatePolygon ( new Point ( 5 , 4 ) , new Point ( 12 , 8 ) , new Point ( 5 , 12 ) ) ,
3557+ "keyword" => new RectangleGeometry ( new Rect ( 4.5 , 5 , 7 , 6 ) , 3 , 3 ) ,
3558+ _ => null // user
3559+ } ;
3560+ if ( kind == "user" )
3561+ outline = new RectangleGeometry ( new Rect ( 3.5 , 3.5 , 9 , 9 ) , 2 , 2 ) ;
3562+
3563+ DrawingGroup drawings = new ( ) ;
3564+ if ( outline is not null )
3565+ drawings . Children . Add ( new GeometryDrawing ( null , pen , outline ) ) ;
3566+ if ( filledShape is not null )
3567+ drawings . Children . Add ( new GeometryDrawing ( brush , null , filledShape ) ) ;
3568+
3569+ DrawingImage image = new ( drawings ) ;
3570+ image . Freeze ( ) ;
3571+ return image ;
3572+ }
3573+
3574+ private static ImageSource CreateCubeIcon ( Pen pen )
3575+ {
3576+ PathGeometry cube = new ( ) ;
3577+ cube . Figures . Add ( new PathFigure ( new Point ( 8 , 2.5 ) , new PathSegment [ ]
3578+ {
3579+ new LineSegment ( new Point ( 3.5 , 5.25 ) , true ) ,
3580+ new LineSegment ( new Point ( 8 , 8 ) , true ) ,
3581+ new LineSegment ( new Point ( 12.5 , 5.25 ) , true ) ,
3582+ new LineSegment ( new Point ( 8 , 2.5 ) , true )
3583+ } , true ) ) ;
3584+ cube . Figures . Add ( new PathFigure ( new Point ( 3.5 , 5.25 ) , new PathSegment [ ]
3585+ {
3586+ new LineSegment ( new Point ( 3.5 , 10.75 ) , true ) ,
3587+ new LineSegment ( new Point ( 8 , 13.5 ) , true ) ,
3588+ new LineSegment ( new Point ( 8 , 8 ) , true ) ,
3589+ new LineSegment ( new Point ( 3.5 , 5.25 ) , true )
3590+ } , true ) ) ;
3591+ cube . Figures . Add ( new PathFigure ( new Point ( 12.5 , 5.25 ) , new PathSegment [ ]
3592+ {
3593+ new LineSegment ( new Point ( 12.5 , 10.75 ) , true ) ,
3594+ new LineSegment ( new Point ( 8 , 13.5 ) , true ) ,
3595+ new LineSegment ( new Point ( 8 , 8 ) , true ) ,
3596+ new LineSegment ( new Point ( 12.5 , 5.25 ) , true )
3597+ } , true ) ) ;
3598+ cube . Freeze ( ) ;
3599+
3600+ DrawingGroup drawings = new ( ) ;
3601+ drawings . Children . Add ( new GeometryDrawing ( null , pen , cube ) ) ;
3602+
3603+ DrawingImage image = new ( drawings ) ;
3604+ image . Freeze ( ) ;
3605+ return image ;
3606+ }
3607+
3608+ private static ImageSource CreateTextIcon ( string text , double fontSize , string fontFamily , FontWeight weight , Brush brush )
3609+ {
3610+ FormattedText formatted = new ( text , CultureInfo . InvariantCulture , FlowDirection . LeftToRight ,
3611+ new Typeface ( new FontFamily ( fontFamily ) , FontStyles . Normal , weight , FontStretches . Normal ) ,
3612+ fontSize , Brushes . Black , 1.0 ) ;
3613+ Geometry glyph = FitGeometry ( formatted . BuildGeometry ( new Point ( 0 , 0 ) ) , 1.5 ) ;
3614+
3615+ DrawingGroup drawings = new ( ) ;
3616+ drawings . Children . Add ( new GeometryDrawing ( brush , null , glyph ) ) ;
3617+
3618+ DrawingImage image = new ( drawings ) ;
3619+ image . Freeze ( ) ;
3620+ return image ;
3621+ }
3622+
3623+ private static Geometry FitGeometry ( Geometry source , double padding )
3624+ {
3625+ Rect bounds = source . Bounds ;
3626+ if ( bounds . IsEmpty )
3627+ return source ;
3628+
3629+ double scale = Math . Min ( ( 16 - 2 * padding ) / bounds . Width , ( 16 - 2 * padding ) / bounds . Height ) ;
3630+ double offsetX = ( 16 - bounds . Width * scale ) / 2 - bounds . X * scale ;
3631+ double offsetY = ( 16 - bounds . Height * scale ) / 2 - bounds . Y * scale ;
3632+
3633+ TransformGroup transform = new ( ) ;
3634+ transform . Children . Add ( new ScaleTransform ( scale , scale ) ) ;
3635+ transform . Children . Add ( new TranslateTransform ( offsetX , offsetY ) ) ;
3636+ transform . Freeze ( ) ;
3637+
3638+ Geometry result = source . Clone ( ) ;
3639+ result . Transform = transform ;
3640+ result . Freeze ( ) ;
3641+ return result ;
3642+ }
3643+
3644+ private static Geometry CreatePolygon ( params Point [ ] points )
3645+ {
3646+ List < PathSegment > segments = new ( points . Length - 1 ) ;
3647+ for ( int i = 1 ; i < points . Length ; i ++ )
3648+ segments . Add ( new LineSegment ( points [ i ] , true ) ) ;
3649+
3650+ PathGeometry geometry = new ( ) ;
3651+ geometry . Figures . Add ( new PathFigure ( points [ 0 ] , segments , true ) ) ;
3652+ geometry . Freeze ( ) ;
3653+ return geometry ;
3654+ }
3655+ }
34713656 }
34723657}
0 commit comments