diff --git a/README.md b/README.md index 32fc7d2d8..a1c204153 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,20 @@ Set WindowBar Colorful Example of setting the margin +#### Set Window Title + +Set a title on the window bar of the terminal window with the `Set WindowTitle` command. + +```elixir +Set WindowTitle "Live in the Terminal" +``` + + + + + Example of setting the window title + + #### Set Border Radius Set the border radius (in pixels) of the terminal window with the `Set BorderRadius` command. diff --git a/command.go b/command.go index d60f78520..574614c70 100644 --- a/command.go +++ b/command.go @@ -436,6 +436,7 @@ var Settings = map[string]CommandFunc{ "MarginFill": ExecuteSetMarginFill, "Margin": ExecuteSetMargin, "WindowBar": ExecuteSetWindowBar, + "WindowTitle": ExecuteSetWindowTitle, "WindowBarSize": ExecuteSetWindowBarSize, "BorderRadius": ExecuteSetBorderRadius, "WaitPattern": ExecuteSetWaitPattern, @@ -575,6 +576,7 @@ func ExecuteSetTheme(c parser.Command, v *VHS) error { v.Options.Video.Style.BackgroundColor = v.Options.Theme.Background v.Options.Video.Style.WindowBarColor = v.Options.Theme.Background + v.Options.Video.Style.WindowTitleColor = v.Options.Theme.Foreground return nil } @@ -677,6 +679,11 @@ func ExecuteSetWindowBar(c parser.Command, v *VHS) error { return nil } +func ExecuteSetWindowTitle(c parser.Command, v *VHS) error { + v.Options.Video.Style.WindowTitle = c.Args + return nil +} + // ExecuteSetWindowBar sets window bar size func ExecuteSetWindowBarSize(c parser.Command, v *VHS) error { windowBarSize, err := strconv.Atoi(c.Args) diff --git a/draw.go b/draw.go index 366d4e3b9..7fbd7bd0d 100644 --- a/draw.go +++ b/draw.go @@ -8,6 +8,11 @@ import ( "image/png" "math" "os" + + "github.com/golang/freetype" + "github.com/golang/freetype/truetype" + "golang.org/x/image/font" + "golang.org/x/image/font/gofont/goregular" ) type circle struct { @@ -233,6 +238,72 @@ const ( barToDotBorderRatio = 5 ) +func drawWindowTitle( + dotsSpace, + borderSpace int, + dotsRight bool, + opts StyleOptions, + windowBar *image.RGBA, +) error { + ttf, err := freetype.ParseFont(goregular.TTF) + if err != nil { + return err + } + + title := opts.WindowTitle + + titleMinX := dotsSpace + titleMaxX := borderSpace + if dotsRight { + titleMinX, titleMaxX = titleMaxX, titleMinX + } + + img := image.NewRGBA( + image.Rectangle{ + image.Point{ + windowBar.Rect.Min.X + titleMinX, + windowBar.Rect.Min.Y, + }, + image.Point{ + windowBar.Rect.Max.X - titleMaxX, + windowBar.Rect.Max.Y, + }, + }, + ) + + fg, _ := parseHexColor(opts.WindowTitleColor) + + c := freetype.NewContext() + c.SetDPI(72) + c.SetFont(ttf) + c.SetFontSize(float64(half(opts.WindowBarSize))) + c.SetClip(img.Bounds()) + c.SetDst(windowBar) + c.SetSrc(&image.Uniform{fg}) + c.SetHinting(font.HintingNone) + + fontFace := truetype.NewFace(ttf, &truetype.Options{}) + titleTextWidth := font.MeasureString(fontFace, title).Ceil() + titleBarWidth := img.Rect.Max.X - img.Rect.Min.X + + // Center-align title text if it will fit, else left-align (truncated by bounds) + var textStartX int + if titleBarWidth >= titleTextWidth { + textStartX = (img.Rect.Max.X - titleTextWidth - titleMaxX) / 2 + } else { + textStartX = titleMinX + } + + // Font size is half window bar size, thus Y start pos is 0.75 of window bar size + textStartY := int(c.PointToFixed(float64(opts.WindowBarSize)*0.75) >> 6) + + pt := freetype.Pt(textStartX, textStartY) + if _, err := c.DrawString(title, pt); err != nil { + return err + } + return nil +} + func makeColorfulBar(termWidth int, termHeight int, isRight bool, opts StyleOptions, targetpng string) error { // Radius of dots dotRad := opts.WindowBarSize / barToDotRatio @@ -305,6 +376,21 @@ func makeColorfulBar(termWidth int, termHeight int, isRight bool, opts StyleOpti draw.Over, ) + if opts.WindowTitle != "" { + titleDotsSpace := dotGap + dotRad*3 + dotSpace*3 + titleBorderSpace := dotGap + dotSpace + + if err := drawWindowTitle( + titleDotsSpace, + titleBorderSpace, + isRight, + opts, + img, + ); err != nil { + fmt.Println(ErrorStyle.Render(fmt.Sprintf("Couldn't draw window title: %s.", err))) + } + } + f, err := os.Create(targetpng) if err != nil { fmt.Println(ErrorStyle.Render("Couldn't draw colorful bar: unable to save file.")) @@ -379,6 +465,21 @@ func makeRingBar(termWidth int, termHeight int, isRight bool, opts StyleOptions, ) } + if opts.WindowTitle != "" { + titleDotsSpace := ringGap + outerRad*3 + ringSpace*3 + titleBorderSpace := ringGap + ringSpace + + if err := drawWindowTitle( + titleDotsSpace, + titleBorderSpace, + isRight, + opts, + img, + ); err != nil { + fmt.Println(ErrorStyle.Render(fmt.Sprintf("Couldn't draw window title: %s.", err))) + } + } + f, err := os.Create(targetpng) if err != nil { fmt.Println(ErrorStyle.Render("Couldn't draw ring bar: unable to save file.")) diff --git a/examples/decorations/decorations.tape b/examples/decorations/decorations.tape index 2dd4b182a..da25bdced 100644 --- a/examples/decorations/decorations.tape +++ b/examples/decorations/decorations.tape @@ -12,4 +12,4 @@ Set WindowBarSize 40 Set BorderRadius 8 Type "I can't believe it's not butter." -Sleep 2s \ No newline at end of file +Sleep 2s diff --git a/examples/demo.tape b/examples/demo.tape index 405a00bbd..5f9d6c11a 100644 --- a/examples/demo.tape +++ b/examples/demo.tape @@ -25,6 +25,7 @@ # Set BorderRadius Set terminal border radius, in pixels. # Set WindowBar Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight) # Set WindowBarSize Set window bar size, in pixels. Default is 40. +# Set WindowTitle Set window title. Text color is taken from theme. # Set TypingSpeed