Skip to content

Commit

Permalink
feat: add command to set window title
Browse files Browse the repository at this point in the history
  • Loading branch information
nixpig committed Jul 23, 2024
1 parent 5a207ef commit 0d064e4
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 24 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,20 @@ Set WindowBar Colorful
<img width="600" alt="Example of setting the margin" src="https://vhs.charm.sh/vhs-4VgviCu38DbaGtbRzhtOUI.gif">
</picture>

#### 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"
```

<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-6s07y8kWtPAC5TBLzxhRfS.gif">
<source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-6s07y8kWtPAC5TBLzxhRfS.gif">
<img width="600" alt="Example of setting the window title" src="https://vhs.charm.sh/vhs-6s07y8kWtPAC5TBLzxhRfS.gif">
</picture>

#### Set Border Radius

Set the border radius (in pixels) of the terminal window with the `Set BorderRadius` command.
Expand Down
7 changes: 7 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ var Settings = map[string]CommandFunc{
"MarginFill": ExecuteSetMarginFill,
"Margin": ExecuteSetMargin,
"WindowBar": ExecuteSetWindowBar,
"WindowTitle": ExecuteSetWindowTitle,
"WindowBarSize": ExecuteSetWindowBarSize,
"BorderRadius": ExecuteSetBorderRadius,
"CursorBlink": ExecuteSetCursorBlink,
Expand Down Expand Up @@ -506,6 +507,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
}
Expand Down Expand Up @@ -588,6 +590,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)
Expand Down
101 changes: 101 additions & 0 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -227,6 +232,72 @@ func MakeWindowBar(termWidth, termHeight int, opts StyleOptions, file string) {
const barToDotRatio = 6
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
Expand Down Expand Up @@ -299,6 +370,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."))
Expand Down Expand Up @@ -373,6 +459,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."))
Expand Down
2 changes: 1 addition & 1 deletion examples/decorations/decorations.tape
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Set WindowBarSize 40
Set BorderRadius 8

Type "I can't believe it's not butter."
Sleep 2s
Sleep 2s
1 change: 1 addition & 0 deletions examples/demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# Set BorderRadius <number> Set terminal border radius, in pixels.
# Set WindowBar <string> Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)
# Set WindowBarSize <number> Set window bar size, in pixels. Default is 40.
# Set WindowTitle <string> Set window title. Text color is taken from theme.
# Set TypingSpeed <time> Set the typing speed of the terminal. Default is 50ms.
#
# Sleep:
Expand Down
1 change: 1 addition & 0 deletions examples/fixtures/all.tape
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Set TypingSpeed .1
Set LoopOffset 60.4
Set LoopOffset 20.99%
Set CursorBlink false
Set WindowTitle "Hello, world!"

# Sleep:
Sleep 1
Expand Down
Binary file added examples/settings/set-window-title.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions examples/settings/set-window-title.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Output examples/settings/set-window-title.gif

Set FontSize 25
Set Width 800
Set Height 400
Set Padding 20

Set WindowBar Colorful
Set WindowTitle "Live in the Terminal"
Set WindowBarSize 40

Type "This terminal window has a title."
Sleep 2s
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
Expand All @@ -60,8 +61,9 @@ require (
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/text v0.16.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-rod/rod v0.116.0 h1:ypRryjTys3EnqHskJ/TdgodFMvXV0EHvmy4bSkKZgHM=
github.com/go-rod/rod v0.116.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
Expand Down Expand Up @@ -125,10 +127,14 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
Expand All @@ -137,6 +143,8 @@ golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
7 changes: 7 additions & 0 deletions lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func TestNextToken(t *testing.T) {
input := `
Output examples/out.gif
Set WindowTitle "Hello, world!"
Set FontSize 42
Set Padding 5
Set CursorBlink false
Expand All @@ -33,6 +34,9 @@ Sleep 2`
{token.OUTPUT, "Output"},
{token.STRING, "examples/out.gif"},
{token.SET, "Set"},
{token.WINDOW_TITLE, "WindowTitle"},
{token.STRING, "Hello, world!"},
{token.SET, "Set"},
{token.FONT_SIZE, "FontSize"},
{token.NUMBER, "42"},
{token.SET, "Set"},
Expand Down Expand Up @@ -156,6 +160,9 @@ func TestLexTapeFile(t *testing.T) {
{token.SET, "Set"},
{token.CURSOR_BLINK, "CursorBlink"},
{token.BOOLEAN, "false"},
{token.SET, "Set"},
{token.WINDOW_TITLE, "WindowTitle"},
{token.STRING, "Hello, world!"},
{token.COMMENT, " Sleep:"},
{token.SLEEP, "Sleep"},
{token.NUMBER, "1"},
Expand Down
3 changes: 3 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@ func (p *Parser) parseSet() Command {
NewError(p.cur, windowBar+" is not a valid bar style."),
)
}
case token.WINDOW_TITLE:
cmd.Args = p.peek.Literal
p.nextToken()
case token.MARGIN_FILL:
cmd.Args = p.peek.Literal
p.nextToken()
Expand Down
1 change: 1 addition & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestParseTapeFile(t *testing.T) {
{Type: token.SET, Options: "LoopOffset", Args: "60.4%"},
{Type: token.SET, Options: "LoopOffset", Args: "20.99%"},
{Type: token.SET, Options: "CursorBlink", Args: "false"},
{Type: token.SET, Options: "WindowTitle", Args: "Hello, world!"},
{Type: token.SLEEP, Options: "", Args: "1s"},
{Type: token.SLEEP, Options: "", Args: "500ms"},
{Type: token.SLEEP, Options: "", Args: ".5s"},
Expand Down
44 changes: 24 additions & 20 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,34 @@ var (

// StyleOptions represents the ui options for video and screenshots.
type StyleOptions struct {
Width int
Height int
Padding int
BackgroundColor string
MarginFill string
Margin int
WindowBar string
WindowBarSize int
WindowBarColor string
BorderRadius int
Width int
Height int
Padding int
BackgroundColor string
MarginFill string
Margin int
WindowBar string
WindowTitle string
WindowTitleColor string
WindowBarSize int
WindowBarColor string
BorderRadius int
}

// DefaultStyleOptions returns default Style config.
func DefaultStyleOptions() *StyleOptions {
return &StyleOptions{
Width: defaultWidth,
Height: defaultHeight,
Padding: defaultPadding,
MarginFill: DefaultTheme.Background,
Margin: 0,
WindowBar: "",
WindowBarSize: defaultWindowBarSize,
WindowBarColor: DefaultTheme.Background,
BorderRadius: 0,
BackgroundColor: DefaultTheme.Background,
Width: defaultWidth,
Height: defaultHeight,
Padding: defaultPadding,
MarginFill: DefaultTheme.Background,
Margin: 0,
WindowBar: "",
WindowTitle: "",
WindowTitleColor: DefaultTheme.Foreground,
WindowBarSize: defaultWindowBarSize,
WindowBarColor: DefaultTheme.Background,
BorderRadius: 0,
BackgroundColor: DefaultTheme.Background,
}
}
4 changes: 3 additions & 1 deletion token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const (
MARGIN_FILL = "MARGIN_FILL" //nolint:revive
MARGIN = "MARGIN" //nolint:revive
WINDOW_BAR = "WINDOW_BAR" //nolint:revive
WINDOW_TITLE = "WINDOW_TITLE" //nolint:revive
WINDOW_BAR_SIZE = "WINDOW_BAR_SIZE" //nolint:revive
BORDER_RADIUS = "CORNER_RADIUS" //nolint:revive
CURSOR_BLINK = "CURSOR_BLINK" //nolint:revive
Expand Down Expand Up @@ -129,6 +130,7 @@ var Keywords = map[string]Type{
"MarginFill": MARGIN_FILL,
"Margin": MARGIN,
"WindowBar": WINDOW_BAR,
"WindowTitle": WINDOW_TITLE,
"WindowBarSize": WINDOW_BAR_SIZE,
"BorderRadius": BORDER_RADIUS,
"FontSize": FONT_SIZE,
Expand Down Expand Up @@ -157,7 +159,7 @@ func IsSetting(t Type) bool {
switch t {
case SHELL, FONT_FAMILY, FONT_SIZE, LETTER_SPACING, LINE_HEIGHT,
FRAMERATE, TYPING_SPEED, THEME, PLAYBACK_SPEED, HEIGHT, WIDTH,
PADDING, LOOP_OFFSET, MARGIN_FILL, MARGIN, WINDOW_BAR,
PADDING, LOOP_OFFSET, MARGIN_FILL, MARGIN, WINDOW_BAR, WINDOW_TITLE,
WINDOW_BAR_SIZE, BORDER_RADIUS, CURSOR_BLINK:
return true
default:
Expand Down

0 comments on commit 0d064e4

Please sign in to comment.