mirror of https://github.com/liamg/aminal.git
Compare commits
No commits in common. "e492e6ea900dd855b501148ac8ed20dadc54a925" and "c18b702b61d1dbd58d10584f6f784b3716a3900c" have entirely different histories.
e492e6ea90
...
c18b702b61
|
@ -1,3 +1,2 @@
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
|
||||||
/darktile
|
/darktile
|
||||||
|
|
26
README.md
26
README.md
|
@ -8,19 +8,12 @@ Darktile is a GPU rendered terminal emulator designed for tiling window managers
|
||||||
|
|
||||||
- GPU rendering
|
- GPU rendering
|
||||||
- Unicode support
|
- Unicode support
|
||||||
- Variety of themes available (or build your own!)
|
|
||||||
- Compiled-in powerline font
|
- Compiled-in powerline font
|
||||||
- Works with your favourite monospaced TTF/OTF fonts
|
- Configurable/customisable, supports custom themes, fonts etc.
|
||||||
- Font ligatures (turn it off if you're not a ligature fan)
|
- Hints: Context-aware overlays e.g. hex colour viewer
|
||||||
- Hints: Context-aware overlays e.g. hex colour viewer, octal permission annotation
|
|
||||||
- Take screenshots with a single key-binding
|
- Take screenshots with a single key-binding
|
||||||
- Sixels
|
- Sixel support
|
||||||
- Window transparency (0-100%)
|
- Transparency
|
||||||
- Customisable cursor (most popular image formats supported)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="cursor.gif">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -50,14 +43,11 @@ Darktile will use sensible defaults if no config/theme files are available. The
|
||||||
Found in the config directory (see above) inside `config.yaml`.
|
Found in the config directory (see above) inside `config.yaml`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
opacity: 1.0 # Window opacity: 0.0 is fully transparent, 1.0 is fully opaque
|
opacity: 1.0 # window opacity: 0.0 is fully transparent, 1.0 is fully opaque
|
||||||
font:
|
font:
|
||||||
family: "" # Font family. Find possible values for this by running 'darktile list-fonts'
|
family: "" # Find possible values for this by running 'darktile list-fonts'
|
||||||
size: 16 # Font size
|
size: 16
|
||||||
dpi: 72 # DPI
|
dpi: 72
|
||||||
ligatures: true # Enable font ligatures e.g. render '≡' instead of '==='
|
|
||||||
cursor:
|
|
||||||
image: "" # Path to an image to render as your cursor (defaults to standard rectangular cursor)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Theme
|
### Example Theme
|
||||||
|
|
BIN
cursor.gif
BIN
cursor.gif
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/d-tsuji/clipboard v0.0.3
|
github.com/d-tsuji/clipboard v0.0.3
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.11.0.20210724070913-1706d9436a78
|
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.11.0.20210724070913-1706d9436a78
|
||||||
github.com/liamg/fontinfo v0.1.1
|
github.com/liamg/fontinfo v0.1.1-0.20210518075346-15a4f7cd9383
|
||||||
github.com/mvdan/xurls v1.1.0 // indirect
|
github.com/mvdan/xurls v1.1.0 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -198,8 +198,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/liamg/fontinfo v0.1.1 h1:NoDMyZGdur30O8Z97cBWRTBpTAoD3DQljsRbWPKv/kw=
|
github.com/liamg/fontinfo v0.1.1-0.20210518075346-15a4f7cd9383 h1:UimfTA954V0lnPnh7X6baeyEG0daATQgQ7wWshI0S6s=
|
||||||
github.com/liamg/fontinfo v0.1.1/go.mod h1:6REdGXLC8yXmxpX31DDwjjzT06g1c7UcvY75AGf9sH4=
|
github.com/liamg/fontinfo v0.1.1-0.20210518075346-15a4f7cd9383/go.mod h1:6REdGXLC8yXmxpX31DDwjjzT06g1c7UcvY75AGf9sH4=
|
||||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg=
|
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg=
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -49,8 +48,6 @@ var rootCmd = &cobra.Command{
|
||||||
if _, err := conf.Save(); err != nil {
|
if _, err := conf.Save(); err != nil {
|
||||||
return fmt.Errorf("failed to write config file: %w", err)
|
return fmt.Errorf("failed to write config file: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Config written.")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme *termutil.Theme
|
var theme *termutil.Theme
|
||||||
|
@ -94,16 +91,6 @@ var rootCmd = &cobra.Command{
|
||||||
gui.WithFontSize(conf.Font.Size),
|
gui.WithFontSize(conf.Font.Size),
|
||||||
gui.WithFontFamily(conf.Font.Family),
|
gui.WithFontFamily(conf.Font.Family),
|
||||||
gui.WithOpacity(conf.Opacity),
|
gui.WithOpacity(conf.Opacity),
|
||||||
gui.WithLigatures(conf.Font.Ligatures),
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Cursor.Image != "" {
|
|
||||||
img, err := getImageFromFilePath(conf.Cursor.Image)
|
|
||||||
if err != nil {
|
|
||||||
startupErrors = append(startupErrors, err)
|
|
||||||
} else {
|
|
||||||
options = append(options, gui.WithCursorImage(img))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if screenshotAfterMS > 0 {
|
if screenshotAfterMS > 0 {
|
||||||
|
@ -131,16 +118,6 @@ var rootCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageFromFilePath(filePath string) (image.Image, error) {
|
|
||||||
f, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
image, _, err := image.Decode(f)
|
|
||||||
return image, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Execute() error {
|
func Execute() error {
|
||||||
rootCmd.Flags().BoolVar(&showVersion, "version", showVersion, "Show darktile version information and exit")
|
rootCmd.Flags().BoolVar(&showVersion, "version", showVersion, "Show darktile version information and exit")
|
||||||
rootCmd.Flags().BoolVar(&rewriteConfig, "rewrite-config", rewriteConfig, "Write the resultant config after parsing config files and merging with defauls back to the config file")
|
rootCmd.Flags().BoolVar(&rewriteConfig, "rewrite-config", rewriteConfig, "Write the resultant config after parsing config files and merging with defauls back to the config file")
|
||||||
|
|
|
@ -12,18 +12,12 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Opacity float64
|
Opacity float64
|
||||||
Font Font
|
Font Font
|
||||||
Cursor Cursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Font struct {
|
type Font struct {
|
||||||
Family string
|
Family string
|
||||||
Size float64
|
Size float64
|
||||||
DPI float64
|
DPI float64
|
||||||
Ligatures bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cursor struct {
|
|
||||||
Image string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorFileNotFound struct {
|
type ErrorFileNotFound struct {
|
||||||
|
|
|
@ -11,10 +11,9 @@ import (
|
||||||
var defaultConfig = Config{
|
var defaultConfig = Config{
|
||||||
Opacity: 1.0,
|
Opacity: 1.0,
|
||||||
Font: Font{
|
Font: Font{
|
||||||
Family: "", // internally packed font will be loaded by default
|
Family: "", // internally packed font will be loaded by default
|
||||||
Size: 18.0,
|
Size: 18.0,
|
||||||
DPI: 72.0,
|
DPI: 72.0,
|
||||||
Ligatures: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,322 @@
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image/color"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/liamg/darktile/internal/app/darktile/gui/render"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/text"
|
||||||
|
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
||||||
|
imagefont "golang.org/x/image/font"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw renders the terminal GUI to the ebtien window. Required to implement the ebiten interface.
|
// Draw renders the terminal GUI to the ebtien window. Required to implement the ebiten interface.
|
||||||
func (g *GUI) Draw(screen *ebiten.Image) {
|
func (g *GUI) Draw(screen *ebiten.Image) {
|
||||||
render.
|
|
||||||
New(screen, g.terminal, g.fontManager, g.popupMessages, g.opacity, g.enableLigatures, g.cursorImage).
|
tmp := ebiten.NewImage(g.size.X, g.size.Y)
|
||||||
Draw()
|
|
||||||
|
cellSize := g.fontManager.CharSize()
|
||||||
|
dotDepth := g.fontManager.DotDepth()
|
||||||
|
|
||||||
|
buffer := g.terminal.GetActiveBuffer()
|
||||||
|
|
||||||
|
regularFace := g.fontManager.RegularFontFace()
|
||||||
|
boldFace := g.fontManager.BoldFontFace()
|
||||||
|
italicFace := g.fontManager.ItalicFontFace()
|
||||||
|
boldItalicFace := g.fontManager.BoldItalicFontFace()
|
||||||
|
|
||||||
|
var useFace imagefont.Face
|
||||||
|
|
||||||
|
defBg := g.terminal.Theme().DefaultBackground()
|
||||||
|
defFg := g.terminal.Theme().DefaultForeground()
|
||||||
|
|
||||||
|
var colour color.Color
|
||||||
|
|
||||||
|
endX := float64(cellSize.X * int(buffer.ViewWidth()))
|
||||||
|
endY := float64(cellSize.Y * int(buffer.ViewHeight()))
|
||||||
|
extraW := float64(g.size.X) - endX
|
||||||
|
extraH := float64(g.size.Y) - endY
|
||||||
|
if extraW > 0 {
|
||||||
|
ebitenutil.DrawRect(tmp, endX, 0, extraW, endY, defBg)
|
||||||
|
}
|
||||||
|
if extraH > 0 {
|
||||||
|
ebitenutil.DrawRect(tmp, 0, endY, float64(g.size.X), extraH, defBg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var inHighlight bool
|
||||||
|
var highlightRendered bool
|
||||||
|
var highlightMin termutil.Position
|
||||||
|
highlightMin.Col = uint16(g.size.X)
|
||||||
|
highlightMin.Line = uint64(g.size.Y)
|
||||||
|
var highlightMax termutil.Position
|
||||||
|
|
||||||
|
for y := int(buffer.ViewHeight() - 1); y >= 0; y-- {
|
||||||
|
py := cellSize.Y * y
|
||||||
|
|
||||||
|
ebitenutil.DrawRect(tmp, 0, float64(py), float64(g.size.X), float64(cellSize.Y), defBg)
|
||||||
|
inHighlight = false
|
||||||
|
for x := uint16(0); x < buffer.ViewWidth(); x++ {
|
||||||
|
cell := buffer.GetCell(x, uint16(y))
|
||||||
|
px := cellSize.X * int(x)
|
||||||
|
if cell != nil {
|
||||||
|
colour = cell.Bg()
|
||||||
|
} else {
|
||||||
|
colour = defBg
|
||||||
|
}
|
||||||
|
isCursor := g.terminal.GetActiveBuffer().IsCursorVisible() && int(buffer.CursorLine()) == y && buffer.CursorColumn() == x
|
||||||
|
if isCursor {
|
||||||
|
colour = g.terminal.Theme().CursorBackground()
|
||||||
|
} else if buffer.InSelection(termutil.Position{
|
||||||
|
Line: uint64(y),
|
||||||
|
Col: x,
|
||||||
|
}) {
|
||||||
|
colour = g.terminal.Theme().SelectionBackground()
|
||||||
|
} else if colour == nil {
|
||||||
|
colour = defBg
|
||||||
|
}
|
||||||
|
|
||||||
|
ebitenutil.DrawRect(tmp, float64(px), float64(py), float64(cellSize.X), float64(cellSize.Y), colour)
|
||||||
|
|
||||||
|
if buffer.IsHighlighted(termutil.Position{
|
||||||
|
Line: uint64(y),
|
||||||
|
Col: x,
|
||||||
|
}) {
|
||||||
|
|
||||||
|
if !inHighlight {
|
||||||
|
highlightRendered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(y) < highlightMin.Line {
|
||||||
|
highlightMin.Col = uint16(g.size.X)
|
||||||
|
highlightMin.Line = uint64(y)
|
||||||
|
}
|
||||||
|
if uint64(y) > highlightMax.Line {
|
||||||
|
highlightMax.Line = uint64(y)
|
||||||
|
}
|
||||||
|
if uint64(y) == highlightMax.Line && x > highlightMax.Col {
|
||||||
|
highlightMax.Col = x
|
||||||
|
}
|
||||||
|
if uint64(y) == highlightMin.Line && x < highlightMin.Col {
|
||||||
|
highlightMin.Col = x
|
||||||
|
}
|
||||||
|
|
||||||
|
inHighlight = true
|
||||||
|
|
||||||
|
} else if inHighlight {
|
||||||
|
inHighlight = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCursor && !ebiten.IsFocused() {
|
||||||
|
ebitenutil.DrawRect(tmp, float64(px)+1, float64(py)+1, float64(cellSize.X)-2, float64(cellSize.Y)-2, g.terminal.Theme().DefaultBackground())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for x := uint16(0); x < buffer.ViewWidth(); x++ {
|
||||||
|
cell := buffer.GetCell(x, uint16(y))
|
||||||
|
if cell == nil || cell.Rune().Rune == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
px := cellSize.X * int(x)
|
||||||
|
colour = cell.Fg()
|
||||||
|
if g.terminal.GetActiveBuffer().IsCursorVisible() && int(buffer.CursorLine()) == y && buffer.CursorColumn() == x {
|
||||||
|
colour = g.terminal.Theme().CursorForeground()
|
||||||
|
} else if buffer.InSelection(termutil.Position{
|
||||||
|
Line: uint64(y),
|
||||||
|
Col: x,
|
||||||
|
}) {
|
||||||
|
colour = g.terminal.Theme().SelectionForeground()
|
||||||
|
} else if colour == nil {
|
||||||
|
colour = defFg
|
||||||
|
}
|
||||||
|
|
||||||
|
useFace = regularFace
|
||||||
|
if cell.Bold() && cell.Italic() {
|
||||||
|
useFace = boldItalicFace
|
||||||
|
} else if cell.Bold() {
|
||||||
|
useFace = boldFace
|
||||||
|
} else if cell.Italic() {
|
||||||
|
useFace = italicFace
|
||||||
|
}
|
||||||
|
|
||||||
|
if cell.Underline() {
|
||||||
|
uly := float64(py + (dotDepth+cellSize.Y)/2)
|
||||||
|
ebitenutil.DrawLine(tmp, float64(px), uly, float64(px+cellSize.X), uly, colour)
|
||||||
|
}
|
||||||
|
|
||||||
|
text.Draw(tmp, string(cell.Rune().Rune), useFace, px, py+dotDepth, colour)
|
||||||
|
|
||||||
|
if cell.Strikethrough() {
|
||||||
|
ebitenutil.DrawLine(tmp, float64(px), float64(py+(cellSize.Y/2)), float64(px+cellSize.X), float64(py+(cellSize.Y/2)), colour)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sixel := range buffer.GetVisibleSixels() {
|
||||||
|
sx := float64(int(sixel.Sixel.X) * cellSize.X)
|
||||||
|
sy := float64(sixel.ViewLineOffset * cellSize.Y)
|
||||||
|
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Translate(sx, sy)
|
||||||
|
tmp.DrawImage(
|
||||||
|
ebiten.NewImageFromImage(sixel.Sixel.Image),
|
||||||
|
op,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw annotations and overlays
|
||||||
|
if highlightRendered {
|
||||||
|
if annotation := buffer.GetHighlightAnnotation(); annotation != nil {
|
||||||
|
|
||||||
|
if highlightMin.Col == uint16(g.size.X) {
|
||||||
|
highlightMin.Col = 0
|
||||||
|
}
|
||||||
|
if highlightMin.Line == uint64(g.size.Y) {
|
||||||
|
highlightMin.Line = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mx, _ := ebiten.CursorPosition()
|
||||||
|
padding := float64(cellSize.X) / 2
|
||||||
|
lineX := float64(mx)
|
||||||
|
var lineY float64
|
||||||
|
var lineHeight float64
|
||||||
|
annotationX := mx - cellSize.X*2
|
||||||
|
var annotationY float64
|
||||||
|
annotationWidth := float64(cellSize.X) * annotation.Width
|
||||||
|
var annotationHeight float64
|
||||||
|
|
||||||
|
if annotationX+int(annotationWidth)+int(padding*2) > g.size.X {
|
||||||
|
annotationX = g.size.X - (int(annotationWidth) + int(padding*2))
|
||||||
|
}
|
||||||
|
if annotationX < int(padding) {
|
||||||
|
annotationX = int(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highlightMin.Line + (highlightMax.Line-highlightMin.Line)/2) < uint64(buffer.ViewHeight()/2) {
|
||||||
|
// annotate underneath max
|
||||||
|
|
||||||
|
pixelsUnderHighlight := float64(g.size.Y) - float64((highlightMax.Line+1)*uint64(cellSize.Y))
|
||||||
|
// we need to reserve at least one cell height for the label line
|
||||||
|
pixelsAvailableY := pixelsUnderHighlight - float64(cellSize.Y)
|
||||||
|
annotationHeight = annotation.Height * float64(cellSize.Y)
|
||||||
|
if annotationHeight > pixelsAvailableY {
|
||||||
|
annotationHeight = pixelsAvailableY
|
||||||
|
}
|
||||||
|
|
||||||
|
lineHeight = pixelsUnderHighlight - padding - annotationHeight
|
||||||
|
if lineHeight > annotationHeight {
|
||||||
|
if annotationHeight > float64(cellSize.Y)*3 {
|
||||||
|
lineHeight = annotationHeight
|
||||||
|
} else {
|
||||||
|
lineHeight = float64(cellSize.Y) * 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
annotationY = float64((highlightMax.Line+1)*uint64(cellSize.Y)) + lineHeight + float64(padding)
|
||||||
|
lineY = float64((highlightMax.Line + 1) * uint64(cellSize.Y))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//annotate above min
|
||||||
|
|
||||||
|
pixelsAboveHighlight := float64((highlightMin.Line) * uint64(cellSize.Y))
|
||||||
|
// we need to reserve at least one cell height for the label line
|
||||||
|
pixelsAvailableY := pixelsAboveHighlight - float64(cellSize.Y)
|
||||||
|
annotationHeight = annotation.Height * float64(cellSize.Y)
|
||||||
|
if annotationHeight > pixelsAvailableY {
|
||||||
|
annotationHeight = pixelsAvailableY
|
||||||
|
}
|
||||||
|
|
||||||
|
lineHeight = pixelsAboveHighlight - annotationHeight
|
||||||
|
if lineHeight > annotationHeight {
|
||||||
|
if annotationHeight > float64(cellSize.Y)*3 {
|
||||||
|
lineHeight = annotationHeight
|
||||||
|
} else {
|
||||||
|
lineHeight = float64(cellSize.Y) * 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
annotationY = float64((highlightMin.Line)*uint64(cellSize.Y)) - lineHeight - float64(padding*2) - annotationHeight
|
||||||
|
lineY = annotationY + annotationHeight + +padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw opaque box below and above highlighted line(s)
|
||||||
|
ebitenutil.DrawRect(tmp, 0, float64(highlightMin.Line*uint64(cellSize.Y)), float64(cellSize.X*int(highlightMin.Col)), float64(cellSize.Y), color.RGBA{A: 0x80})
|
||||||
|
ebitenutil.DrawRect(tmp, float64((cellSize.X)*int(highlightMax.Col+1)), float64(highlightMax.Line*uint64(cellSize.Y)), float64(g.size.X), float64(cellSize.Y), color.RGBA{A: 0x80})
|
||||||
|
ebitenutil.DrawRect(tmp, 0, 0, float64(g.size.X), float64(highlightMin.Line*uint64(cellSize.Y)), color.RGBA{A: 0x80})
|
||||||
|
afterLineY := float64((1 + highlightMax.Line) * uint64(cellSize.Y))
|
||||||
|
ebitenutil.DrawRect(tmp, 0, afterLineY, float64(g.size.X), float64(g.size.Y)-afterLineY, color.RGBA{A: 0x80})
|
||||||
|
|
||||||
|
// annotation border
|
||||||
|
ebitenutil.DrawRect(tmp, float64(annotationX)-padding, annotationY-padding, float64(annotationWidth)+(padding*2), annotationHeight+(padding*2), g.terminal.Theme().SelectionBackground())
|
||||||
|
// annotation background
|
||||||
|
ebitenutil.DrawRect(tmp, 1+float64(annotationX)-padding, 1+annotationY-padding, float64(annotationWidth)+(padding*2)-2, annotationHeight+(padding*2)-2, g.terminal.Theme().DefaultBackground())
|
||||||
|
|
||||||
|
// vertical line
|
||||||
|
ebitenutil.DrawLine(tmp, lineX, float64(lineY), lineX, lineY+lineHeight, g.terminal.Theme().SelectionBackground())
|
||||||
|
|
||||||
|
var tY int
|
||||||
|
var tX int
|
||||||
|
|
||||||
|
if annotation.Image != nil {
|
||||||
|
tY += annotation.Image.Bounds().Dy() + cellSize.Y/2
|
||||||
|
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Translate(float64(annotationX), annotationY)
|
||||||
|
tmp.DrawImage(
|
||||||
|
ebiten.NewImageFromImage(annotation.Image),
|
||||||
|
op,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range annotation.Text {
|
||||||
|
if r == '\n' {
|
||||||
|
tY += cellSize.Y
|
||||||
|
tX = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text.Draw(tmp, string(r), regularFace, annotationX+tX, int(annotationY)+dotDepth+tY, g.terminal.Theme().DefaultForeground())
|
||||||
|
tX += cellSize.X
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.popupMessages) > 0 {
|
||||||
|
pad := cellSize.Y / 2 // horizontal and vertical padding
|
||||||
|
msgEndY := endY
|
||||||
|
for _, msg := range g.popupMessages {
|
||||||
|
|
||||||
|
lines := strings.Split(msg.Text, "\n")
|
||||||
|
|
||||||
|
msgX := pad
|
||||||
|
|
||||||
|
msgY := msgEndY - float64(pad*3) - float64(cellSize.Y*len(lines))
|
||||||
|
|
||||||
|
msgText := msg.Text
|
||||||
|
|
||||||
|
boxWidth := float64(pad*2) + float64(cellSize.X*len(msgText))
|
||||||
|
boxHeight := float64(pad*2) + float64(cellSize.Y*len(lines))
|
||||||
|
|
||||||
|
if boxWidth < endX/8 {
|
||||||
|
boxWidth = endX / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
ebitenutil.DrawRect(tmp, float64(msgX-1), msgY-1, boxWidth+2, boxHeight+2, msg.Foreground)
|
||||||
|
ebitenutil.DrawRect(tmp, float64(msgX), msgY, boxWidth, boxHeight, msg.Background)
|
||||||
|
for y, line := range lines {
|
||||||
|
for x, r := range line {
|
||||||
|
text.Draw(tmp, string(r), regularFace, msgX+pad+(x*cellSize.X), pad+(y*cellSize.Y)+int(msgY)+dotDepth, msg.Foreground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msgEndY = msgEndY - float64(pad*4) - float64(len(lines)*g.CellSize().Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if g.screenshotRequested {
|
if g.screenshotRequested {
|
||||||
g.takeScreenshot(screen)
|
g.takeScreenshot(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opt := &ebiten.DrawImageOptions{}
|
||||||
|
opt.ColorM.Scale(1, 1, 1, g.opacity)
|
||||||
|
screen.DrawImage(tmp, opt)
|
||||||
|
tmp.Dispose()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ package gui
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"image/color"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/font"
|
"github.com/liamg/darktile/internal/app/darktile/font"
|
||||||
"github.com/liamg/darktile/internal/app/darktile/gui/popup"
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/hinters"
|
"github.com/liamg/darktile/internal/app/darktile/hinters"
|
||||||
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
||||||
|
|
||||||
|
@ -34,14 +34,19 @@ type GUI struct {
|
||||||
mousePos termutil.Position
|
mousePos termutil.Position
|
||||||
hinters []hinters.Hinter
|
hinters []hinters.Hinter
|
||||||
activeHinter int
|
activeHinter int
|
||||||
popupMessages []popup.Message
|
popupMessages []PopupMessage
|
||||||
screenshotRequested bool
|
screenshotRequested bool
|
||||||
screenshotFilename string
|
screenshotFilename string
|
||||||
startupFuncs []func(g *GUI)
|
startupFuncs []func(g *GUI)
|
||||||
keyState *keyState
|
keyState *keyState
|
||||||
opacity float64
|
opacity float64
|
||||||
enableLigatures bool
|
}
|
||||||
cursorImage *ebiten.Image
|
|
||||||
|
type PopupMessage struct {
|
||||||
|
Text string
|
||||||
|
Expiry time.Time
|
||||||
|
Foreground color.Color
|
||||||
|
Background color.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
type MouseState uint8
|
type MouseState uint8
|
||||||
|
@ -54,13 +59,12 @@ const (
|
||||||
func New(terminal *termutil.Terminal, options ...Option) (*GUI, error) {
|
func New(terminal *termutil.Terminal, options ...Option) (*GUI, error) {
|
||||||
|
|
||||||
g := &GUI{
|
g := &GUI{
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
size: image.Point{80, 30},
|
size: image.Point{80, 30},
|
||||||
updateChan: make(chan struct{}),
|
updateChan: make(chan struct{}),
|
||||||
fontManager: font.NewManager(),
|
fontManager: font.NewManager(),
|
||||||
activeHinter: -1,
|
activeHinter: -1,
|
||||||
keyState: newKeyState(),
|
keyState: newKeyState(),
|
||||||
enableLigatures: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
|
|
|
@ -219,7 +219,11 @@ func (g *GUI) handleInput() error {
|
||||||
return g.terminal.WriteToPty([]byte(fmt.Sprintf("\x1b[6%s~", g.getModifierStr())))
|
return g.terminal.WriteToPty([]byte(fmt.Sprintf("\x1b[6%s~", g.getModifierStr())))
|
||||||
default:
|
default:
|
||||||
input := ebiten.AppendInputChars(nil)
|
input := ebiten.AppendInputChars(nil)
|
||||||
return g.terminal.WriteToPty([]byte(string(input)))
|
for _, runePressed := range input {
|
||||||
|
if err := g.terminal.WriteToPty([]byte(string(runePressed))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option func(g *GUI) error
|
type Option func(g *GUI) error
|
||||||
|
|
||||||
func WithFontFamily(family string) func(g *GUI) error {
|
func WithFontFamily(family string) func(g *GUI) error {
|
||||||
|
@ -35,20 +29,6 @@ func WithFontDPI(dpi float64) func(g *GUI) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithLigatures(enable bool) func(g *GUI) error {
|
|
||||||
return func(g *GUI) error {
|
|
||||||
g.enableLigatures = enable
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithCursorImage(img image.Image) func(g *GUI) error {
|
|
||||||
return func(g *GUI) error {
|
|
||||||
g.cursorImage = ebiten.NewImageFromImage(img)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithStartupFunc(f func(g *GUI)) Option {
|
func WithStartupFunc(f func(g *GUI)) Option {
|
||||||
return func(g *GUI) error {
|
return func(g *GUI) error {
|
||||||
g.startupFuncs = append(g.startupFuncs, f)
|
g.startupFuncs = append(g.startupFuncs, f)
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package popup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Text string
|
|
||||||
Expiry time.Time
|
|
||||||
Foreground color.Color
|
|
||||||
Background color.Color
|
|
||||||
}
|
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/gui/popup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,7 +12,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *GUI) ShowPopup(msg string, fg color.Color, bg color.Color, duration time.Duration) {
|
func (g *GUI) ShowPopup(msg string, fg color.Color, bg color.Color, duration time.Duration) {
|
||||||
g.popupMessages = append(g.popupMessages, popup.Message{
|
g.popupMessages = append(g.popupMessages, PopupMessage{
|
||||||
Text: msg,
|
Text: msg,
|
||||||
Expiry: time.Now().Add(duration),
|
Expiry: time.Now().Add(duration),
|
||||||
Foreground: fg,
|
Foreground: fg,
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Render) drawAnnotation() {
|
|
||||||
|
|
||||||
// 1. check if we have anything to highlight/annotate
|
|
||||||
highlightStart, highlightEnd, ok := r.buffer.GetViewHighlight()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. make everything outside of the highlighted area opaque
|
|
||||||
dimColour := color.RGBA{A: 0x80} // 50% alpha black overlay to dim non-highlighted area
|
|
||||||
for line := 0; line < int(r.buffer.ViewHeight()); line++ {
|
|
||||||
if line < int(highlightStart.Line) || line > int(highlightEnd.Line) {
|
|
||||||
ebitenutil.DrawRect(
|
|
||||||
r.frame,
|
|
||||||
0,
|
|
||||||
float64(line*r.font.CellSize.Y),
|
|
||||||
float64(r.pixelWidth),
|
|
||||||
float64(r.font.CellSize.Y),
|
|
||||||
dimColour, // 50% alpha black overlay to dim non-highlighted area
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if line == int(highlightStart.Line) && highlightStart.Col > 0 {
|
|
||||||
// we need to dim some content on this line before the highlight starts
|
|
||||||
ebitenutil.DrawRect(
|
|
||||||
r.frame,
|
|
||||||
0,
|
|
||||||
float64(line*r.font.CellSize.Y),
|
|
||||||
float64(int(highlightStart.Col)*r.font.CellSize.X),
|
|
||||||
float64(r.font.CellSize.Y),
|
|
||||||
dimColour,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if line == int(highlightEnd.Line) && highlightEnd.Col < r.buffer.ViewWidth()-2 {
|
|
||||||
// we need to dim some content on this line after the highlight ends
|
|
||||||
ebitenutil.DrawRect(
|
|
||||||
r.frame,
|
|
||||||
float64(int(highlightEnd.Col+1)*r.font.CellSize.X),
|
|
||||||
float64(line*r.font.CellSize.Y),
|
|
||||||
float64(int(r.buffer.ViewWidth()-(highlightEnd.Col+1))*r.font.CellSize.X),
|
|
||||||
float64(r.font.CellSize.Y),
|
|
||||||
dimColour,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. annotate the highlighted area (if there is an annotation)
|
|
||||||
annotation := r.buffer.GetHighlightAnnotation()
|
|
||||||
if annotation == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mousePixelX, _ := ebiten.CursorPosition()
|
|
||||||
padding := float64(r.font.CellSize.X) / 2
|
|
||||||
|
|
||||||
var lineY float64
|
|
||||||
var lineHeight float64
|
|
||||||
var annotationY float64
|
|
||||||
var annotationHeight float64
|
|
||||||
|
|
||||||
if (highlightStart.Line + (highlightEnd.Line-highlightStart.Line)/2) < uint64(r.buffer.ViewHeight()/2) {
|
|
||||||
// annotate underneath max
|
|
||||||
|
|
||||||
pixelsUnderHighlight := float64(r.pixelHeight) - float64((highlightEnd.Line+1)*uint64(r.font.CellSize.Y))
|
|
||||||
// we need to reserve at least one cell height for the label line
|
|
||||||
pixelsAvailableY := pixelsUnderHighlight - float64(r.font.CellSize.Y)
|
|
||||||
annotationHeight = annotation.Height * float64(r.font.CellSize.Y)
|
|
||||||
if annotationHeight > pixelsAvailableY {
|
|
||||||
annotationHeight = pixelsAvailableY
|
|
||||||
}
|
|
||||||
|
|
||||||
lineHeight = pixelsUnderHighlight - padding - annotationHeight
|
|
||||||
if lineHeight > annotationHeight {
|
|
||||||
if annotationHeight > float64(r.font.CellSize.Y)*3 {
|
|
||||||
lineHeight = annotationHeight
|
|
||||||
} else {
|
|
||||||
lineHeight = float64(r.font.CellSize.Y) * 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
annotationY = float64((highlightEnd.Line+1)*uint64(r.font.CellSize.Y)) + lineHeight + float64(padding)
|
|
||||||
lineY = float64((highlightEnd.Line + 1) * uint64(r.font.CellSize.Y))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//annotate above min
|
|
||||||
|
|
||||||
pixelsAboveHighlight := float64((highlightStart.Line) * uint64(r.font.CellSize.Y))
|
|
||||||
// we need to reserve at least one cell height for the label line
|
|
||||||
pixelsAvailableY := pixelsAboveHighlight - float64(r.font.CellSize.Y)
|
|
||||||
annotationHeight = annotation.Height * float64(r.font.CellSize.Y)
|
|
||||||
if annotationHeight > pixelsAvailableY {
|
|
||||||
annotationHeight = pixelsAvailableY
|
|
||||||
}
|
|
||||||
|
|
||||||
lineHeight = pixelsAboveHighlight - annotationHeight
|
|
||||||
if lineHeight > annotationHeight {
|
|
||||||
if annotationHeight > float64(r.font.CellSize.Y)*3 {
|
|
||||||
lineHeight = annotationHeight
|
|
||||||
} else {
|
|
||||||
lineHeight = float64(r.font.CellSize.Y) * 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
annotationY = float64((highlightStart.Line)*uint64(r.font.CellSize.Y)) - lineHeight - float64(padding*2) - annotationHeight
|
|
||||||
lineY = annotationY + annotationHeight + +padding
|
|
||||||
}
|
|
||||||
|
|
||||||
annotationX := mousePixelX - r.font.CellSize.X*2
|
|
||||||
annotationWidth := float64(r.font.CellSize.X) * annotation.Width
|
|
||||||
|
|
||||||
// if the annotation box goes off the right side of the terminal, align it against the right side
|
|
||||||
if annotationX+int(annotationWidth)+int(padding*2) > r.pixelWidth {
|
|
||||||
annotationX = r.pixelWidth - (int(annotationWidth) + int(padding*2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the annotation is too far left, align it against the left side
|
|
||||||
if annotationX < int(padding) {
|
|
||||||
annotationX = int(padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
// annotation border
|
|
||||||
ebitenutil.DrawRect(r.frame, float64(annotationX)-padding, annotationY-padding, float64(annotationWidth)+(padding*2), annotationHeight+(padding*2), r.theme.SelectionBackground())
|
|
||||||
// annotation background
|
|
||||||
ebitenutil.DrawRect(r.frame, 1+float64(annotationX)-padding, 1+annotationY-padding, float64(annotationWidth)+(padding*2)-2, annotationHeight+(padding*2)-2, r.theme.DefaultBackground())
|
|
||||||
|
|
||||||
// vertical line
|
|
||||||
ebitenutil.DrawLine(r.frame, float64(mousePixelX), float64(lineY), float64(mousePixelX), lineY+lineHeight, r.theme.SelectionBackground())
|
|
||||||
|
|
||||||
var tY int
|
|
||||||
var tX int
|
|
||||||
|
|
||||||
if annotation.Image != nil {
|
|
||||||
tY += annotation.Image.Bounds().Dy() + r.font.CellSize.Y/2
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
|
||||||
op.GeoM.Translate(float64(annotationX), annotationY)
|
|
||||||
r.frame.DrawImage(
|
|
||||||
ebiten.NewImageFromImage(annotation.Image),
|
|
||||||
op,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ch := range annotation.Text {
|
|
||||||
if ch == '\n' {
|
|
||||||
tY += r.font.CellSize.Y
|
|
||||||
tX = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
text.Draw(r.frame, string(ch), r.font.Regular, annotationX+tX, int(annotationY)+r.font.DotDepth+tY, r.theme.DefaultForeground())
|
|
||||||
tX += r.font.CellSize.X
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
func (r *Render) drawContent() {
|
|
||||||
// draw base content for each row
|
|
||||||
defBg := r.theme.DefaultBackground()
|
|
||||||
defFg := r.theme.DefaultForeground()
|
|
||||||
for viewY := int(r.buffer.ViewHeight() - 1); viewY >= 0; viewY-- {
|
|
||||||
r.drawRow(viewY, defBg, defFg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Render) drawCursor() {
|
|
||||||
//draw cursor
|
|
||||||
if !r.buffer.IsCursorVisible() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pixelX := float64(int(r.buffer.CursorColumn()) * r.font.CellSize.X)
|
|
||||||
pixelY := float64(int(r.buffer.CursorLine()) * r.font.CellSize.Y)
|
|
||||||
cell := r.buffer.GetCell(r.buffer.CursorColumn(), r.buffer.CursorLine())
|
|
||||||
|
|
||||||
useFace := r.font.Regular
|
|
||||||
if cell != nil {
|
|
||||||
if cell.Bold() && cell.Italic() {
|
|
||||||
useFace = r.font.BoldItalic
|
|
||||||
} else if cell.Bold() {
|
|
||||||
useFace = r.font.Bold
|
|
||||||
} else if cell.Italic() {
|
|
||||||
useFace = r.font.Italic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pixelW, pixelH := float64(r.font.CellSize.X), float64(r.font.CellSize.Y)
|
|
||||||
|
|
||||||
// empty rect without focus
|
|
||||||
if !ebiten.IsFocused() {
|
|
||||||
ebitenutil.DrawRect(r.frame, pixelX, pixelY, pixelW, pixelH, r.theme.CursorBackground())
|
|
||||||
ebitenutil.DrawRect(r.frame, pixelX+1, pixelY+1, pixelW-2, pixelH-2, r.theme.CursorForeground())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the cursor shape
|
|
||||||
switch r.buffer.GetCursorShape() {
|
|
||||||
case termutil.CursorShapeBlinkingBar, termutil.CursorShapeSteadyBar:
|
|
||||||
ebitenutil.DrawRect(r.frame, pixelX, pixelY, 2, pixelH, r.theme.CursorBackground())
|
|
||||||
case termutil.CursorShapeBlinkingUnderline, termutil.CursorShapeSteadyUnderline:
|
|
||||||
ebitenutil.DrawRect(r.frame, pixelX, pixelY+pixelH-2, pixelW, 2, r.theme.CursorBackground())
|
|
||||||
default:
|
|
||||||
// draw a custom cursor if we have one and there are no characters in the way
|
|
||||||
if r.cursorImage != nil && (cell == nil || cell.Rune().Rune == 0) {
|
|
||||||
opt := &ebiten.DrawImageOptions{}
|
|
||||||
_, h := r.cursorImage.Size()
|
|
||||||
ratio := 1 / (float64(h) / float64(r.font.CellSize.Y))
|
|
||||||
actualHeight := float64(h) * ratio
|
|
||||||
offsetY := (float64(r.font.CellSize.Y) - actualHeight) / 2
|
|
||||||
opt.GeoM.Scale(ratio, ratio)
|
|
||||||
opt.GeoM.Translate(pixelX, pixelY+offsetY)
|
|
||||||
r.frame.DrawImage(r.cursorImage, opt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ebitenutil.DrawRect(r.frame, pixelX, pixelY, pixelW, pixelH, r.theme.CursorBackground())
|
|
||||||
|
|
||||||
// we've drawn over the cell contents, so we need to draw it again in the cursor colours
|
|
||||||
if cell != nil && cell.Rune().Rune > 0 {
|
|
||||||
text.Draw(r.frame, string(cell.Rune().Rune), useFace, int(pixelX), int(pixelY)+r.font.DotDepth, r.theme.CursorForeground())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
imagefont "golang.org/x/image/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ligatures = map[string]rune{
|
|
||||||
":=": '≔',
|
|
||||||
"===": '≡',
|
|
||||||
"!=": '≠',
|
|
||||||
"!==": '≢',
|
|
||||||
"<=": '≤',
|
|
||||||
">=": '≥',
|
|
||||||
"=>": '⇒',
|
|
||||||
"->": '→',
|
|
||||||
"<-": '←',
|
|
||||||
"<>": '≷',
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Render) handleLigatures(sx uint16, sy uint16, face imagefont.Face, colour color.Color) (length int) {
|
|
||||||
|
|
||||||
var candidate string
|
|
||||||
for x := sx; x <= sx+2; x++ {
|
|
||||||
cell := r.buffer.GetCell(x, sy)
|
|
||||||
if cell == nil || cell.Rune().Rune == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
candidate += string(cell.Rune().Rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(candidate) > 1 {
|
|
||||||
if ru, ok := ligatures[candidate]; ok {
|
|
||||||
// draw ligature
|
|
||||||
ligX := (int(sx) * r.font.CellSize.X) + (((len(candidate) - 1) * r.font.CellSize.X) / 2)
|
|
||||||
text.Draw(r.frame, string(ru), face, ligX, (int(sy)*r.font.CellSize.Y)+r.font.DotDepth, colour)
|
|
||||||
return len(candidate)
|
|
||||||
}
|
|
||||||
candidate = candidate[:len(candidate)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Render) drawPopups() {
|
|
||||||
|
|
||||||
if len(r.popups) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pad := r.font.CellSize.Y / 2 // horizontal and vertical padding
|
|
||||||
maxPixelX := float64(r.font.CellSize.X * int(r.buffer.ViewWidth()))
|
|
||||||
maxPixelY := float64(r.font.CellSize.Y * int(r.buffer.ViewHeight()))
|
|
||||||
|
|
||||||
for _, msg := range r.popups {
|
|
||||||
|
|
||||||
lines := strings.Split(msg.Text, "\n")
|
|
||||||
msgX := pad
|
|
||||||
msgY := maxPixelY - float64(pad*3) - float64(r.font.CellSize.Y*len(lines))
|
|
||||||
boxWidth := float64(pad*2) + float64(r.font.CellSize.X*len(msg.Text))
|
|
||||||
boxHeight := float64(pad*2) + float64(r.font.CellSize.Y*len(lines))
|
|
||||||
|
|
||||||
if boxWidth < maxPixelX/8 {
|
|
||||||
boxWidth = maxPixelX / 8
|
|
||||||
}
|
|
||||||
|
|
||||||
ebitenutil.DrawRect(r.frame, float64(msgX-1), msgY-1, boxWidth+2, boxHeight+2, msg.Foreground)
|
|
||||||
ebitenutil.DrawRect(r.frame, float64(msgX), msgY, boxWidth, boxHeight, msg.Background)
|
|
||||||
for y, line := range lines {
|
|
||||||
for x, c := range line {
|
|
||||||
text.Draw(r.frame, string(c), r.font.Regular, msgX+pad+(x*r.font.CellSize.X), pad+(y*r.font.CellSize.Y)+int(msgY)+r.font.DotDepth, msg.Foreground)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maxPixelY = maxPixelY - float64(pad*4) - float64(len(lines)*r.font.CellSize.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/font"
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/gui/popup"
|
|
||||||
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
|
||||||
imagefont "golang.org/x/image/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Render struct {
|
|
||||||
frame *ebiten.Image
|
|
||||||
screen *ebiten.Image
|
|
||||||
terminal *termutil.Terminal
|
|
||||||
buffer *termutil.Buffer
|
|
||||||
theme *termutil.Theme
|
|
||||||
fontManager *font.Manager
|
|
||||||
pixelWidth int
|
|
||||||
pixelHeight int
|
|
||||||
font Font
|
|
||||||
opacity float64
|
|
||||||
popups []popup.Message
|
|
||||||
enableLigatures bool
|
|
||||||
cursorImage *ebiten.Image
|
|
||||||
}
|
|
||||||
|
|
||||||
type Font struct {
|
|
||||||
Regular imagefont.Face
|
|
||||||
Bold imagefont.Face
|
|
||||||
Italic imagefont.Face
|
|
||||||
BoldItalic imagefont.Face
|
|
||||||
CellSize image.Point
|
|
||||||
DotDepth int
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(screen *ebiten.Image, terminal *termutil.Terminal, fontManager *font.Manager, popups []popup.Message, opacity float64, enableLigatures bool, cursorImage *ebiten.Image) *Render {
|
|
||||||
w, h := screen.Size()
|
|
||||||
return &Render{
|
|
||||||
screen: screen,
|
|
||||||
frame: ebiten.NewImage(w, h),
|
|
||||||
terminal: terminal,
|
|
||||||
buffer: terminal.GetActiveBuffer(),
|
|
||||||
theme: terminal.Theme(),
|
|
||||||
fontManager: fontManager,
|
|
||||||
pixelWidth: w,
|
|
||||||
pixelHeight: h,
|
|
||||||
font: Font{
|
|
||||||
Regular: fontManager.RegularFontFace(),
|
|
||||||
Bold: fontManager.BoldFontFace(),
|
|
||||||
Italic: fontManager.ItalicFontFace(),
|
|
||||||
BoldItalic: fontManager.BoldItalicFontFace(),
|
|
||||||
CellSize: fontManager.CharSize(),
|
|
||||||
DotDepth: fontManager.DotDepth(),
|
|
||||||
},
|
|
||||||
opacity: opacity,
|
|
||||||
popups: popups,
|
|
||||||
enableLigatures: enableLigatures,
|
|
||||||
cursorImage: cursorImage,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Render) Draw() {
|
|
||||||
|
|
||||||
// 1. fill frame with default background colour
|
|
||||||
r.frame.Fill(r.theme.DefaultBackground())
|
|
||||||
|
|
||||||
// 2. draw content (each row, each cell)
|
|
||||||
r.drawContent()
|
|
||||||
|
|
||||||
// 3. draw cursor
|
|
||||||
r.drawCursor()
|
|
||||||
|
|
||||||
// // 4. draw sixels
|
|
||||||
r.drawSixels()
|
|
||||||
|
|
||||||
// // 5. draw selection
|
|
||||||
r.drawSelection()
|
|
||||||
|
|
||||||
// // 6. draw highlight/annotations
|
|
||||||
r.drawAnnotation()
|
|
||||||
|
|
||||||
// // 7. draw popups
|
|
||||||
r.drawPopups()
|
|
||||||
|
|
||||||
// // 8. apply effects (e.g. transparency)
|
|
||||||
r.finalise()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Render) finalise() {
|
|
||||||
defer r.frame.Dispose()
|
|
||||||
opt := &ebiten.DrawImageOptions{}
|
|
||||||
opt.ColorM.Scale(1, 1, 1, r.opacity)
|
|
||||||
r.screen.DrawImage(r.frame, opt)
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
imagefont "golang.org/x/image/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Render) drawRow(viewY int, defaultBackgroundColour color.Color, defaultForegroundColour color.Color) {
|
|
||||||
|
|
||||||
pixelY := r.font.CellSize.Y * viewY
|
|
||||||
|
|
||||||
// draw a default colour background image across the entire row background
|
|
||||||
ebitenutil.DrawRect(r.frame, 0, float64(pixelY), float64(r.pixelWidth), float64(r.font.CellSize.Y), defaultBackgroundColour)
|
|
||||||
|
|
||||||
var colour color.Color
|
|
||||||
|
|
||||||
// draw background for each cell in row
|
|
||||||
for viewX := uint16(0); viewX < r.buffer.ViewWidth(); viewX++ {
|
|
||||||
cell := r.buffer.GetCell(viewX, uint16(viewY))
|
|
||||||
pixelX := r.font.CellSize.X * int(viewX)
|
|
||||||
if cell != nil {
|
|
||||||
colour = cell.Bg()
|
|
||||||
}
|
|
||||||
if colour == nil {
|
|
||||||
colour = defaultBackgroundColour
|
|
||||||
}
|
|
||||||
|
|
||||||
ebitenutil.DrawRect(r.frame, float64(pixelX), float64(pixelY), float64(r.font.CellSize.X), float64(r.font.CellSize.Y), colour)
|
|
||||||
}
|
|
||||||
|
|
||||||
var useFace imagefont.Face
|
|
||||||
var skipRunes int
|
|
||||||
|
|
||||||
// draw text content of each cell in row
|
|
||||||
for viewX := uint16(0); viewX < r.buffer.ViewWidth(); viewX++ {
|
|
||||||
|
|
||||||
cell := r.buffer.GetCell(viewX, uint16(viewY))
|
|
||||||
|
|
||||||
// we don't need to draw empty cells
|
|
||||||
if cell == nil || cell.Rune().Rune == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
colour = cell.Fg()
|
|
||||||
if colour == nil {
|
|
||||||
colour = defaultForegroundColour
|
|
||||||
}
|
|
||||||
|
|
||||||
// pick a font face for the cell
|
|
||||||
if !cell.Bold() && !cell.Italic() {
|
|
||||||
useFace = r.font.Regular
|
|
||||||
} else if cell.Bold() && cell.Italic() {
|
|
||||||
useFace = r.font.Italic
|
|
||||||
} else if cell.Bold() {
|
|
||||||
useFace = r.font.Bold
|
|
||||||
} else if cell.Italic() {
|
|
||||||
useFace = r.font.Italic
|
|
||||||
}
|
|
||||||
|
|
||||||
pixelX := r.font.CellSize.X * int(viewX)
|
|
||||||
|
|
||||||
// underline the cell content if required
|
|
||||||
if cell.Underline() {
|
|
||||||
underlinePixelY := float64(pixelY + (r.font.DotDepth+r.font.CellSize.Y)/2)
|
|
||||||
ebitenutil.DrawLine(r.frame, float64(pixelX), underlinePixelY, float64(pixelX+r.font.CellSize.X), underlinePixelY, colour)
|
|
||||||
}
|
|
||||||
|
|
||||||
// strikethrough the cell if required
|
|
||||||
if cell.Strikethrough() {
|
|
||||||
ebitenutil.DrawLine(
|
|
||||||
r.frame,
|
|
||||||
float64(pixelX),
|
|
||||||
float64(pixelY+(r.font.CellSize.Y/2)),
|
|
||||||
float64(pixelX+r.font.CellSize.X),
|
|
||||||
float64(pixelY+(r.font.CellSize.Y/2)),
|
|
||||||
colour,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.enableLigatures && skipRunes == 0 {
|
|
||||||
skipRunes = r.handleLigatures(viewX, uint16(viewY), useFace, colour)
|
|
||||||
}
|
|
||||||
|
|
||||||
if skipRunes > 0 {
|
|
||||||
skipRunes--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the text for the cell
|
|
||||||
text.Draw(r.frame, string(cell.Rune().Rune), useFace, pixelX, pixelY+r.font.DotDepth, colour)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Render) drawSelection() {
|
|
||||||
_, selection := r.buffer.GetSelection()
|
|
||||||
if selection == nil {
|
|
||||||
// nothing selected
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bg, fg := r.theme.SelectionBackground(), r.theme.SelectionForeground()
|
|
||||||
|
|
||||||
for y := selection.Start.Line; y <= selection.End.Line; y++ {
|
|
||||||
xStart, xEnd := 0, int(r.buffer.ViewWidth())
|
|
||||||
if y == selection.Start.Line {
|
|
||||||
xStart = int(selection.Start.Col)
|
|
||||||
}
|
|
||||||
if y == selection.End.Line {
|
|
||||||
xEnd = int(selection.End.Col)
|
|
||||||
}
|
|
||||||
for x := xStart; x <= xEnd; x++ {
|
|
||||||
pX, pY := float64(x*r.font.CellSize.X), float64(y*uint64(r.font.CellSize.Y))
|
|
||||||
ebitenutil.DrawRect(r.frame, pX, pY, float64(r.font.CellSize.X), float64(r.font.CellSize.Y), bg)
|
|
||||||
cell := r.buffer.GetCell(uint16(x), uint16(y))
|
|
||||||
if cell == nil || cell.Rune().Rune == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
text.Draw(r.frame, string(cell.Rune().Rune), r.font.Regular, int(pX), int(pY)+r.font.DotDepth, fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import "github.com/hajimehoshi/ebiten/v2"
|
|
||||||
|
|
||||||
func (r *Render) drawSixels() {
|
|
||||||
for _, sixel := range r.buffer.GetVisibleSixels() {
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
|
||||||
op.GeoM.Translate(
|
|
||||||
float64(int(sixel.Sixel.X)*r.font.CellSize.X),
|
|
||||||
float64(sixel.ViewLineOffset*r.font.CellSize.Y),
|
|
||||||
)
|
|
||||||
r.frame.DrawImage(
|
|
||||||
ebiten.NewImageFromImage(sixel.Sixel.Image),
|
|
||||||
op,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/liamg/darktile/internal/app/darktile/gui/popup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *GUI) getModifierStr() string {
|
func (g *GUI) getModifierStr() string {
|
||||||
|
@ -41,7 +40,7 @@ func (g *GUI) Update() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GUI) filterPopupMessages() {
|
func (g *GUI) filterPopupMessages() {
|
||||||
var filtered []popup.Message
|
var filtered []PopupMessage
|
||||||
for _, msg := range g.popupMessages {
|
for _, msg := range g.popupMessages {
|
||||||
if time.Since(msg.Expiry) >= 0 {
|
if time.Since(msg.Expiry) >= 0 {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -3,28 +3,14 @@ package termutil
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const TabSize = 8
|
const TabSize = 8
|
||||||
|
|
||||||
type CursorShape uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
CursorShapeBlinkingBlock CursorShape = iota
|
|
||||||
CursorShapeDefault
|
|
||||||
CursorShapeSteadyBlock
|
|
||||||
CursorShapeBlinkingUnderline
|
|
||||||
CursorShapeSteadyUnderline
|
|
||||||
CursorShapeBlinkingBar
|
|
||||||
CursorShapeSteadyBar
|
|
||||||
)
|
|
||||||
|
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
lines []Line
|
lines []Line
|
||||||
savedCursorPos Position
|
savedCursorPos Position
|
||||||
savedCursorAttr *CellAttributes
|
savedCursorAttr *CellAttributes
|
||||||
cursorShape CursorShape
|
|
||||||
savedCharsets []*map[rune]rune
|
savedCharsets []*map[rune]rune
|
||||||
savedCurrentCharset int
|
savedCurrentCharset int
|
||||||
topMargin uint // see DECSTBM docs - this is for scrollable regions
|
topMargin uint // see DECSTBM docs - this is for scrollable regions
|
||||||
|
@ -45,7 +31,6 @@ type Buffer struct {
|
||||||
highlightEnd *Position
|
highlightEnd *Position
|
||||||
highlightAnnotation *Annotation
|
highlightAnnotation *Annotation
|
||||||
sixels []Sixel
|
sixels []Sixel
|
||||||
selectionMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Annotation struct {
|
type Annotation struct {
|
||||||
|
@ -85,19 +70,10 @@ func NewBuffer(width, height uint16, maxLines uint64, fg color.Color, bg color.C
|
||||||
ShowCursor: true,
|
ShowCursor: true,
|
||||||
SixelScrolling: true,
|
SixelScrolling: true,
|
||||||
},
|
},
|
||||||
cursorShape: CursorShapeDefault,
|
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) SetCursorShape(shape CursorShape) {
|
|
||||||
buffer.cursorShape = shape
|
|
||||||
}
|
|
||||||
|
|
||||||
func (buffer *Buffer) GetCursorShape() CursorShape {
|
|
||||||
return buffer.cursorShape
|
|
||||||
}
|
|
||||||
|
|
||||||
func (buffer *Buffer) IsCursorVisible() bool {
|
func (buffer *Buffer) IsCursorVisible() bool {
|
||||||
return buffer.modes.ShowCursor
|
return buffer.modes.ShowCursor
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,13 @@ func (t *Terminal) handleCSI(readChan chan MeasuredRune) (renderRequired bool) {
|
||||||
|
|
||||||
t.log("CSI P(%q) I(%q) %c", strings.Join(params, ";"), string(intermediate), final)
|
t.log("CSI P(%q) I(%q) %c", strings.Join(params, ";"), string(intermediate), final)
|
||||||
|
|
||||||
|
for _, b := range intermediate {
|
||||||
|
t.processRunes(MeasuredRune{
|
||||||
|
Rune: b,
|
||||||
|
Width: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
switch final {
|
switch final {
|
||||||
case 'c':
|
case 'c':
|
||||||
return t.csiSendDeviceAttributesHandler(params)
|
return t.csiSendDeviceAttributesHandler(params)
|
||||||
|
@ -66,10 +73,6 @@ func (t *Terminal) handleCSI(readChan chan MeasuredRune) (renderRequired bool) {
|
||||||
return t.csiSetMarginsHandler(params)
|
return t.csiSetMarginsHandler(params)
|
||||||
case 't':
|
case 't':
|
||||||
return t.csiWindowManipulation(params)
|
return t.csiWindowManipulation(params)
|
||||||
case 'q':
|
|
||||||
if string(intermediate) == " " {
|
|
||||||
return t.csiCursorSelection(params)
|
|
||||||
}
|
|
||||||
case 'A':
|
case 'A':
|
||||||
return t.csiCursorUpHandler(params)
|
return t.csiCursorUpHandler(params)
|
||||||
case 'B':
|
case 'B':
|
||||||
|
@ -109,22 +112,15 @@ func (t *Terminal) handleCSI(readChan chan MeasuredRune) (renderRequired bool) {
|
||||||
return t.csiSoftResetHandler(params)
|
return t.csiSoftResetHandler(params)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
default:
|
||||||
|
// TODO review this:
|
||||||
|
// if this is an unknown CSI sequence, write it to stdout as we can't handle it?
|
||||||
|
//_ = t.writeToRealStdOut(append([]rune{0x1b, '['}, raw...)...)
|
||||||
|
_ = raw
|
||||||
|
t.log("UNKNOWN CSI P(%s) I(%s) %c", strings.Join(params, ";"), string(intermediate), final)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range intermediate {
|
|
||||||
t.processRunes(MeasuredRune{
|
|
||||||
Rune: b,
|
|
||||||
Width: 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO review this:
|
|
||||||
// if this is an unknown CSI sequence, write it to stdout as we can't handle it?
|
|
||||||
//_ = t.writeToRealStdOut(append([]rune{0x1b, '['}, raw...)...)
|
|
||||||
_ = raw
|
|
||||||
t.log("UNKNOWN CSI P(%s) I(%s) %c", strings.Join(params, ";"), string(intermediate), final)
|
|
||||||
return false
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindowState uint8
|
type WindowState uint8
|
||||||
|
@ -967,12 +963,6 @@ func (t *Terminal) sgrSequenceHandler(params []string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x := t.GetActiveBuffer().CursorColumn()
|
|
||||||
y := t.GetActiveBuffer().CursorLine()
|
|
||||||
if cell := t.GetActiveBuffer().GetCell(x, y); cell != nil {
|
|
||||||
cell.attr = t.GetActiveBuffer().cursorAttr
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -980,15 +970,3 @@ func (t *Terminal) csiSoftResetHandler(params []string) bool {
|
||||||
t.reset()
|
t.reset()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) csiCursorSelection(params []string) (renderRequired bool) {
|
|
||||||
if len(params) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(params[0])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t.GetActiveBuffer().SetCursorShape(CursorShape(i))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,10 +8,6 @@ type Option func(t *Terminal)
|
||||||
|
|
||||||
func WithLogFile(path string) Option {
|
func WithLogFile(path string) Option {
|
||||||
return func(t *Terminal) {
|
return func(t *Terminal) {
|
||||||
if path == "-" {
|
|
||||||
t.logFile = os.Stdout
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.logFile, _ = os.Create(path)
|
t.logFile, _ = os.Create(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package termutil
|
package termutil
|
||||||
|
|
||||||
func (buffer *Buffer) ClearSelection() {
|
func (buffer *Buffer) ClearSelection() {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
buffer.selectionStart = nil
|
buffer.selectionStart = nil
|
||||||
buffer.selectionEnd = nil
|
buffer.selectionEnd = nil
|
||||||
}
|
}
|
||||||
|
@ -15,9 +13,6 @@ func (buffer *Buffer) GetBoundedTextAtPosition(pos Position) (start Position, en
|
||||||
|
|
||||||
// if the selection is invalid - e.g. lines are selected that no longer exist in the buffer
|
// if the selection is invalid - e.g. lines are selected that no longer exist in the buffer
|
||||||
func (buffer *Buffer) fixSelection() bool {
|
func (buffer *Buffer) fixSelection() bool {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
|
|
||||||
if buffer.selectionStart == nil || buffer.selectionEnd == nil {
|
if buffer.selectionStart == nil || buffer.selectionEnd == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -49,9 +44,6 @@ func (buffer *Buffer) ExtendSelectionToEntireLines() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
|
|
||||||
buffer.selectionStart.Col = 0
|
buffer.selectionStart.Col = 0
|
||||||
buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
|
buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
|
||||||
}
|
}
|
||||||
|
@ -158,8 +150,6 @@ FORWARD:
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) SetSelectionStart(pos Position) {
|
func (buffer *Buffer) SetSelectionStart(pos Position) {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
buffer.selectionStart = &Position{
|
buffer.selectionStart = &Position{
|
||||||
Col: pos.Col,
|
Col: pos.Col,
|
||||||
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
|
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
|
||||||
|
@ -167,14 +157,10 @@ func (buffer *Buffer) SetSelectionStart(pos Position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) setRawSelectionStart(pos Position) {
|
func (buffer *Buffer) setRawSelectionStart(pos Position) {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
buffer.selectionStart = &pos
|
buffer.selectionStart = &pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) SetSelectionEnd(pos Position) {
|
func (buffer *Buffer) SetSelectionEnd(pos Position) {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
buffer.selectionEnd = &Position{
|
buffer.selectionEnd = &Position{
|
||||||
Col: pos.Col,
|
Col: pos.Col,
|
||||||
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
|
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
|
||||||
|
@ -182,8 +168,6 @@ func (buffer *Buffer) SetSelectionEnd(pos Position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) setRawSelectionEnd(pos Position) {
|
func (buffer *Buffer) setRawSelectionEnd(pos Position) {
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
buffer.selectionEnd = &pos
|
buffer.selectionEnd = &pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,9 +176,6 @@ func (buffer *Buffer) GetSelection() (string, *Selection) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
|
|
||||||
start := *buffer.selectionStart
|
start := *buffer.selectionStart
|
||||||
end := *buffer.selectionEnd
|
end := *buffer.selectionEnd
|
||||||
|
|
||||||
|
@ -206,9 +187,6 @@ func (buffer *Buffer) GetSelection() (string, *Selection) {
|
||||||
|
|
||||||
var text string
|
var text string
|
||||||
for y := start.Line; y <= end.Line; y++ {
|
for y := start.Line; y <= end.Line; y++ {
|
||||||
if y >= uint64(len(buffer.lines)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
line := buffer.lines[y]
|
line := buffer.lines[y]
|
||||||
startX := 0
|
startX := 0
|
||||||
endX := len(line.cells) - 1
|
endX := len(line.cells) - 1
|
||||||
|
@ -222,13 +200,7 @@ func (buffer *Buffer) GetSelection() (string, *Selection) {
|
||||||
text += "\n"
|
text += "\n"
|
||||||
}
|
}
|
||||||
for x := startX; x <= endX; x++ {
|
for x := startX; x <= endX; x++ {
|
||||||
if x >= len(line.cells) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
mr := line.cells[x].Rune()
|
mr := line.cells[x].Rune()
|
||||||
if mr.Width == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
x += mr.Width - 1
|
x += mr.Width - 1
|
||||||
text += string(mr.Rune)
|
text += string(mr.Rune)
|
||||||
}
|
}
|
||||||
|
@ -249,8 +221,6 @@ func (buffer *Buffer) InSelection(pos Position) bool {
|
||||||
if !buffer.fixSelection() {
|
if !buffer.fixSelection() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
buffer.selectionMu.Lock()
|
|
||||||
defer buffer.selectionMu.Unlock()
|
|
||||||
|
|
||||||
start := *buffer.selectionStart
|
start := *buffer.selectionStart
|
||||||
end := *buffer.selectionEnd
|
end := *buffer.selectionEnd
|
||||||
|
@ -286,30 +256,31 @@ func (buffer *Buffer) GetHighlightAnnotation() *Annotation {
|
||||||
return buffer.highlightAnnotation
|
return buffer.highlightAnnotation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) GetViewHighlight() (start Position, end Position, exists bool) {
|
// takes view coords
|
||||||
|
func (buffer *Buffer) IsHighlighted(pos Position) bool {
|
||||||
|
|
||||||
if buffer.highlightStart == nil || buffer.highlightEnd == nil {
|
if buffer.highlightStart == nil || buffer.highlightEnd == nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer.highlightStart.Line >= uint64(len(buffer.lines)) {
|
if buffer.highlightStart.Line >= uint64(len(buffer.lines)) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer.highlightEnd.Line >= uint64(len(buffer.lines)) {
|
if buffer.highlightEnd.Line >= uint64(len(buffer.lines)) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer.highlightStart.Col >= uint16(len(buffer.lines[buffer.highlightStart.Line].cells)) {
|
if buffer.highlightStart.Col >= uint16(len(buffer.lines[buffer.highlightStart.Line].cells)) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer.highlightEnd.Col >= uint16(len(buffer.lines[buffer.highlightEnd.Line].cells)) {
|
if buffer.highlightEnd.Col >= uint16(len(buffer.lines[buffer.highlightEnd.Line].cells)) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
start = *buffer.highlightStart
|
start := *buffer.highlightStart
|
||||||
end = *buffer.highlightEnd
|
end := *buffer.highlightEnd
|
||||||
|
|
||||||
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
|
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
|
||||||
swap := end
|
swap := end
|
||||||
|
@ -317,8 +288,23 @@ func (buffer *Buffer) GetViewHighlight() (start Position, end Position, exists b
|
||||||
start = swap
|
start = swap
|
||||||
}
|
}
|
||||||
|
|
||||||
start.Line = uint64(buffer.convertRawLineToViewLine(start.Line))
|
rY := buffer.convertViewLineToRawLine(uint16(pos.Line))
|
||||||
end.Line = uint64(buffer.convertRawLineToViewLine(end.Line))
|
if rY < start.Line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rY > end.Line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rY == start.Line {
|
||||||
|
if pos.Col < start.Col {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rY == end.Line {
|
||||||
|
if pos.Col > end.Col {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return start, end, true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,16 +25,7 @@ var fontDirs = []string{
|
||||||
"/usr/local/share/fonts",
|
"/usr/local/share/fonts",
|
||||||
"/usr/share/fonts",
|
"/usr/share/fonts",
|
||||||
filepath.Join(os.Getenv("XDG_DATA_HOME"), "fonts"),
|
filepath.Join(os.Getenv("XDG_DATA_HOME"), "fonts"),
|
||||||
}
|
filepath.Join(os.Getenv("XDG_DATA_DIRS"), "fonts"),
|
||||||
|
|
||||||
func init() {
|
|
||||||
dataDirs := strings.Split(os.Getenv("XDG_DATA_DIRS"), string(os.PathListSeparator))
|
|
||||||
for _, dir := range dataDirs {
|
|
||||||
if dir == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fontDirs = append(fontDirs, filepath.Join(dir, "fonts"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match finds all fonts installed on the system which match the provided matchers
|
// Match finds all fonts installed on the system which match the provided matchers
|
||||||
|
|
|
@ -60,7 +60,7 @@ github.com/hajimehoshi/ebiten/v2/internal/uidriver/mobile
|
||||||
github.com/hajimehoshi/ebiten/v2/text
|
github.com/hajimehoshi/ebiten/v2/text
|
||||||
# github.com/inconshreveable/mousetrap v1.0.0
|
# github.com/inconshreveable/mousetrap v1.0.0
|
||||||
github.com/inconshreveable/mousetrap
|
github.com/inconshreveable/mousetrap
|
||||||
# github.com/liamg/fontinfo v0.1.1
|
# github.com/liamg/fontinfo v0.1.1-0.20210518075346-15a4f7cd9383
|
||||||
## explicit
|
## explicit
|
||||||
github.com/liamg/fontinfo
|
github.com/liamg/fontinfo
|
||||||
# github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1
|
# github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1
|
||||||
|
|
Loading…
Reference in New Issue