Inside F#

Brian's thoughts on F# and .NET

A quick standalone app to edit colors for the F# colorizer

Posted by Brian on November 20, 2010

What kind of fool posts a cool extension for colorizing F# source code structure, but then only lets you configure the colors by editing the registry (and then having to restart Visual Studio to see if the new colors look ok)?

Oh, that was me.  :)

Like I said in the previous blog, I don’t have the skills to design a good GUI to edit the colors or figure out the most discoverable place in VS to edit them.  But maybe you do!  And so in the spirit of sharing source, here’s a crummy (but working) standalone app for experimenting with the colors.  A screenshot is suggestive:

FSColorEditor

Basically, you can click on one of the rectangles, and then use the buttons to change the R/G/B values of the left-edge color or the main color.  And once you have colors you like, you can click a different button which prints out a .reg file you can use to install those colors.  (The app only reads the registry, it does not write it, so you have to copy-paste the text from the console window, save it to a .reg file, and run it if you want to install your updated colors.)

Anyway, the code is below.  Just throw this in an F# Console Application, add references to the WPF assemblies (PresentationCore, PresentationFramework, System.Xaml, System.Xml, UIAutomationTypes, and WindowsBase), press Ctrl-F5 to build and run and you’re off to the races.  Feel free to steal all this code to build your own better app/extension, or just to use to build your own attractive color schemes.  The code is just a quick ugly hack, but hopefully you can work with it.

And yes, I’ll be releasing the code for the actual editor colorizer extension soon, I just want to finish tidying it up a little.  (Thanks to those who have already left feedback and pointed out minor bugs!)

Quickie code for app to manage editing colors:

open System.Windows 
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Media 
open System.Windows.Shapes 
open Microsoft.Win32 
 
type IIndexable<'a> =
    abstract member Item : int -> 'a
 
let makeBinding(sourceObj:obj, sourcePath:string, converter:'T->'U, 
                targetObj:DependencyObject, targetDependencyProperty:DependencyProperty) =
    let b = new System.Windows.Data.Binding()
    b.Source <- sourceObj
    b.Path <- new System.Windows.PropertyPath(sourcePath)
    b.Converter <- 
        { new System.Windows.Data.IValueConverter with
            member this.Convert(v, targetType, param, culInfo) =
                box(converter (unbox v))
            member this.ConvertBack(v, targetType, param, culInfo) =
                raise <| new System.NotImplementedException() }
    BindingOperations.SetBinding(targetObj, targetDependencyProperty, b) |> ignore
 
type MyWindow() as this =
    inherit Window()
 
    static let selectedRectangleNumber = 
        DependencyProperty.Register("SelectedRectangleNumber", typeof<int>, typeof<MyWindow>, 
            new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender, 
                new PropertyChangedCallback(fun depObj ea -> 
                    match box depObj, box ea.NewValue with 
                    | (:? MyWindow as w), (:? int as x) -> w.TriggerSelectedRectangleNumberChanged(x) 
                    | _ -> ())))
 
    let selectedChanged = new Event<Handler<int>,int>()
    let getNum() = this.GetValue(MyWindow.SelectedRectangleNumber) :?> int
    let setNum(n) = this.SetValue(MyWindow.SelectedRectangleNumber, n)
 
    let colors =
        try
            let key = Registry.CurrentUser.OpenSubKey(
                @"Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer")
            Array.init 10 (fun i -> key.GetValue(sprintf "Depth%d" i) :?> string)
            |> Array.map (fun s -> let [|r1;g1;b1;r2;g2;b2|] = s.Split[|','|] |> Array.map byte
                                   r1,g1,b1,r2,g2,b2)
        with e ->
            [|  // greyscale colors
            190uy,190uy,190uy,230uy,230uy,230uy
            170uy,170uy,170uy,210uy,210uy,210uy
            184uy,184uy,184uy,224uy,224uy,224uy
            164uy,164uy,164uy,204uy,204uy,204uy
            178uy,178uy,178uy,218uy,218uy,218uy
            158uy,158uy,158uy,198uy,198uy,198uy
            172uy,172uy,172uy,212uy,212uy,212uy
            152uy,152uy,152uy,192uy,192uy,192uy
            166uy,166uy,166uy,206uy,206uy,206uy
            146uy,146uy,146uy,186uy,186uy,186uy
            |]
    // why the SolidColorBrush below? 
    // to have a DependencyObject to data-bind... there is probably a cleaner way, but oh well
    let edgeColors = colors |> Array.map (fun (r,g,b,_,_,_) -> SolidColorBrush(Color.FromRgb(r,g,b)))
    let mainColors = colors |> Array.map (fun (_,_,_,r,g,b) -> SolidColorBrush(Color.FromRgb(r,g,b)))
    let brushes = 
        let makeGradientStop(b:SolidColorBrush, pct) =
            let gs = new GradientStop(b.Color, pct)
            makeBinding(b, "Color", (fun (c:Color) -> c), gs, GradientStop.ColorProperty)
            gs
        { new IIndexable<Brush> with 
            member this.Item depth =
                upcast new LinearGradientBrush(
                    new GradientStopCollection( 
                        [|
                        makeGradientStop(edgeColors.[depth], 0.0)
                        makeGradientStop(mainColors.[depth], 0.01)
                        makeGradientStop(mainColors.[depth], 1.0)
                        |] ), new Point(0.0, 0.5), new Point(1.0, 0.5)) }
 
    let printCurrentSettings() =
        printfn "Windows Registry Editor Version 5.00"
        printfn ""
        printfn "[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer]"
        for i in 0..9 do
            let ec = edgeColors.[i].Color
            let mc = mainColors.[i].Color
            printfn "\"Depth%d\"=\"%d,%d,%d,%d,%d,%d\"" i ec.R ec.G ec.B mc.R mc.G mc.B 
 
    let mkButton(colors:SolidColorBrush[], text, dr, dg, db) =
        let b = new Button()
        b.Content <- text
        b.Click.Add(fun _ -> colors.[getNum()].Color <- 
                                Color.FromRgb(colors.[getNum()].Color.R + byte dr, 
                                              colors.[getNum()].Color.G + byte dg, 
                                              colors.[getNum()].Color.B + byte db))
        b
 
    let mkButtonPanel(colors:SolidColorBrush[],text,dr,dg,db,f) =
        let panel = new StackPanel(Orientation = Orientation.Horizontal)
        let tb = new TextBlock()
        selectedChanged.Publish.Add(fun _ ->
            makeBinding(colors.[getNum()], "Color", (fun (c:Color) -> f(c).ToString()), 
                        tb, System.Windows.Controls.TextBlock.TextProperty))
        panel.Children.Add(mkButton(colors, text+" down",-dr,-dg,-db)) |> ignore
        panel.Children.Add(tb) |> ignore
        panel.Children.Add(mkButton(colors, text+" up",dr,dg,db)) |> ignore
        panel
 
    do
        let leftPanel = new StackPanel(Orientation = Orientation.Vertical)
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge R",2,0,0,(fun c->c.R))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge G",0,2,0,(fun c->c.G))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge B",0,0,2,(fun c->c.B))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main R",2,0,0,(fun c->c.R))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main G",0,2,0,(fun c->c.G))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main B",0,0,2,(fun c->c.B))) |> ignore
        let printButton = new Button(Content="Print current settings to console")
        printButton.Click.Add(fun _ -> printCurrentSettings())
        leftPanel.Children.Add(printButton) |> ignore
        let mainPanel = new StackPanel(Orientation = Orientation.Horizontal)
        mainPanel.Children.Add(leftPanel) |> ignore
        let canvas = new Canvas()
        for i in 0..mainColors.Length-1 do
            let rect = new Rectangle(Width=600., Height=500., Fill = brushes.[i])
            rect.MouseDown.Add(fun _ -> setNum(i))
            Canvas.SetLeft(rect, (float i)*30.)
            Canvas.SetTop(rect, (float i)*30.)
            Canvas.SetZIndex(rect, i)
            canvas.Children.Add(rect) |> ignore
        let rect = new Rectangle(Width=600., Height=40.)
        selectedChanged.Publish.Add(fun _ -> rect.Fill <- brushes.[getNum()])
        Canvas.SetLeft(rect, 0.)
        Canvas.SetTop(rect, (float mainColors.Length)*30. + 20.)
        Canvas.SetZIndex(rect, mainColors.Length)
        canvas.Children.Add(rect) |> ignore
        mainPanel.Children.Add(canvas) |> ignore
        this.Content <- mainPanel
 
    member private this.TriggerSelectedRectangleNumberChanged(x:int) = selectedChanged.Trigger(this, x)
    static member SelectedRectangleNumber = selectedRectangleNumber 
 
[<System.STAThread>]
do
    (new Application()).Run(MyWindow()) |> ignore
About these ads

4 Responses to “A quick standalone app to edit colors for the F# colorizer”

  1. […] « The F# compiler source release: making it easy to write cool Visual Studio extensions A quick standalone app to edit colors for the F# colorizer […]

  2. […] Inside F#: A quick standalone app to edit colors for the F# colorize, Source code for F# Depth Colorizer […]

  3. nyi nyi said

    Hi Brian,

    For the purpose of learning F#, I wrote a small colorizer editor(http://fscolorizereditor.codeplex.com) based on your code.
    Thanks for the extension and excellent F# blog posts.

    Regards,
    Nyi Nyi

  4. […] A quick standalone app to edit colors for the F# colorizer « Inside F# http://lorgonblog.wordpress.com/2010/11/20/a-quick-standalone-app-to-edit-colors-for-the-f-colorizer… […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 30 other followers

%d bloggers like this: